Skip to content

Conversation

@Artur-Wisniewski
Copy link
Contributor

No description provided.

@Artur-Wisniewski Artur-Wisniewski self-assigned this Aug 1, 2025
@github-actions github-actions bot added the p: leancode_lint Related to the leancode_lint package label Aug 1, 2025
@Artur-Wisniewski Artur-Wisniewski added enhancement New feature or request and removed p: leancode_lint Related to the leancode_lint package labels Aug 1, 2025
@github-actions github-actions bot added the p: leancode_lint Related to the leancode_lint package label Aug 1, 2025
Comment on lines 204 to 210
bool _isDisposable(InterfaceType type) =>
type.methods2.any((method) => method.name3 == 'dispose') ||
type.element3.inheritedMembers.entries.any(
(entry) =>
entry.key.name == 'dispose' &&
entry.value.baseElement is MethodElement2,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's very nice. I'd also consider adding close and cancel methods but I'm not sure because it might give more false-positive cases 🙈

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 (#469 (comment)) — I don't think there'll be false positives 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make 2 other lints for closeable and for cancelable resources based on this code in this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe this one could be configurable? 😏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added configuration for this close, cancel, and dispose

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new lint rule called missing_cleanup that enforces proper disposal of resources in Flutter StatefulWidget classes. The lint detects when disposable resources (controllers, streams, timers, etc.) are not properly cleaned up in the dispose() method, helping prevent memory leaks.

  • Implements the missing_cleanup lint rule with configurable cleanup methods and ignored types
  • Adds comprehensive test cases covering various scenarios including StatefulWidget, StatelessWidget, and Bloc classes
  • Includes detailed documentation with configuration options and usage examples

Reviewed Changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/leancode_lint/lib/lints/missing_cleanup.dart Main implementation of the missing_cleanup lint rule with configuration support
packages/leancode_lint/test/lints_test_app/lib/missing_cleanup/missing_cleanup_test.dart Test cases for the lint rule covering various disposal scenarios
packages/leancode_lint/test/lints_test_app/lib/missing_cleanup/missing_cleanup_dispose_bloc_test.dart Test cases specifically for Bloc disposal patterns
packages/leancode_lint/lib/leancode_lint.dart Registration of the new lint rule in the plugin
packages/leancode_lint/README.md Documentation for the new lint rule with examples and configuration
packages/leancode_lint/pubspec.yaml Added yaml dependency for configuration parsing
packages/leancode_lint/test/lints_test_app/analysis_options.yaml Configuration example for the lint rule
packages/leancode_lint/test/lints_test_app/lib/avoid_conditional_hooks_test.dart Added ignore directive for the new lint
Comments suppressed due to low confidence (2)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@Artur-Wisniewski Artur-Wisniewski force-pushed the feat/avoid-missing-dispose branch from 30ecd4a to 6783459 Compare August 22, 2025 12:12

Resources such as controllers, stream controllers or focus nodes must be cleanup in the `dispose()` method to prevent memory leaks.

**BAD:**`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
**BAD:**`
**BAD:**

Comment on lines +508 to +512
@override
void initState() {
super.initState();
controller = TextEditingController();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this GOOD example confusing: is it good because of the dispose in the void dispose() method of the Widget or is it good because the controller is initialized in void initState()? I would minimize the diff between the BAD and GOOD example

Comment on lines +543 to +546
- `ignored_types` - an optional YamlList - skips dispose checks for specified types. This allows disabling the lint rule for classes where dispose method checks are not needed.
- `ignore` - A required String - name of the instance to ignore
- `from_package` - A required String - name of the source package
- `cleanup_methods` - an optional YamlMap - controls which disposal methods the lint rule should recognize and check for. By default, the rule looks for `dispose`, `close`, and `cancel` methods. You can selectively enable or disable checking for each of these methods.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not use package:yaml jargon here (YamlList, YamlMap). End users don't know what that is. Just saying it is a list and respectively a map with keys X Y Z is enough

- `ignored_types` - an optional YamlList - skips dispose checks for specified types. This allows disabling the lint rule for classes where dispose method checks are not needed.
- `ignore` - A required String - name of the instance to ignore
- `from_package` - A required String - name of the source package
- `cleanup_methods` - an optional YamlMap - controls which disposal methods the lint rule should recognize and check for. By default, the rule looks for `dispose`, `close`, and `cancel` methods. You can selectively enable or disable checking for each of these methods.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this configuration? What is the usecase of disabling some of them globally (rather than putting a type in the ignored_types list)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also write a custom class that has a cleanup method to see if it is also detected by the lint

);

return widgetTypeChecker.isExactlyType(type) ||
widgetTypeChecker.isSuperTypeOf(type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't being exactly the type imply being a supertype? i.e. the check for isExactlyType is redundant?

Comment on lines +115 to +119
if (_isWidgetClass(classNode) &&
!_isFieldUsedByConstructor(classNode, node)) {
reporter.atNode(node, code);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this check work? Why it matters if something is used in the constructor?

Comment on lines +310 to +312
void visitExpressionStatement(ExpressionStatement node) {
if (node.expression
case MethodInvocation(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you only care about expressions that are MethodInvocations, you can just override the visitMethodInvocation method of the visitor

Comment on lines +316 to +318
target: SimpleIdentifier(:final name),
) &&
final invocation when name == targetName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't compare by name. Variables can be shadowed. I am pretty sure something in the AST gives identity to variables (element?)


final String targetName;

final List<InvocationExpression> _disposeExpressions = [];
Copy link
Contributor

@shilangyu shilangyu Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result does not seem to be ever used. Since this visitor is already quite expensive, I would make it return early. Namely, if a cleanup is found, immediately stop the visitor and just return bool true

For that you might need to override the RecursiveAstVisitor which can stop the recursing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request p: leancode_lint Related to the leancode_lint package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants