-
-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add unnecessary_hook_widget
lint rule and quick fix
#271
base: main
Are you sure you want to change the base?
Changes from all commits
d803815
f5cb8ca
3837994
c014b96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
packages/nilts/.gitignore |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
packages/nilts/CHANGELOG.md |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
packages/nilts/README.md |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
packages/nilts/analysis_options.yaml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Refer to [nilts_example](https://github.com/dassssshers/nilts/tree/main/examples/nilts_example) for example project. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import 'package:custom_lint_builder/custom_lint_builder.dart'; | ||
import 'package:nilts_flutter_hooks/src/lints/unnecessary_hook_widget.dart'; | ||
|
||
/// custom_lint integrates the nilts's plugin from this method on | ||
/// `custom_lint_client.dart` which is generated by custom_lint CLI. | ||
PluginBase createPlugin() => _NiltsFlutterHooksLint(); | ||
|
||
/// A class for defining all lint rules and assists | ||
/// managed by nilts_flutter_hooks. | ||
class _NiltsFlutterHooksLint extends PluginBase { | ||
@override | ||
List<LintRule> getLintRules(CustomLintConfigs configs) => [ | ||
const UnnecessaryHookWidget(), | ||
]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// ignore_for_file: comment_references because referencing to private classes | ||
|
||
/// A class for defining priorities of quick fix and assist in nilts. | ||
/// | ||
/// Fixes to ignore rules are defined on [IgnoreCode]. | ||
/// These priorities are 34 and 35. | ||
/// | ||
/// See also: | ||
/// - [IgnoreCode](https://github.com/invertase/dart_custom_lint/blob/1df2851a80ccdc5a2bda4418006560f49c03b8ec/packages/custom_lint_builder/lib/src/ignore.dart#L102) | ||
class ChangePriority { | ||
/// The priority for [_ReplaceWithStatelessWidget] | ||
static const int replaceWithStatelessWidget = 100; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// ignore_for_file: comment_references to avoid unnecessary imports | ||
|
||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/ast/visitor.dart'; | ||
import 'package:analyzer/error/error.dart' hide LintCode; | ||
import 'package:analyzer/error/listener.dart'; | ||
import 'package:analyzer/source/source_range.dart'; | ||
import 'package:custom_lint_builder/custom_lint_builder.dart'; | ||
import 'package:nilts_flutter_hooks/src/change_priority.dart'; | ||
|
||
/// A class for `unnecessary_hook_widget` rule. | ||
/// | ||
/// This rule checks if [HookWidget] is used without any hooks. | ||
/// If no hooks are found, it suggests replacing it with [StatelessWidget]. | ||
/// | ||
/// - Target SDK : Any versions nilts_flutter_hooks supports | ||
/// - Rule type : Practice | ||
/// - Maturity level : Experimental | ||
/// - Quick fix : ✅ | ||
/// | ||
/// **Consider** using `StatelessWidget` instead of `HookWidget` when no hooks | ||
/// are used within the widget. | ||
/// | ||
/// **BAD:** | ||
/// ```dart | ||
/// class NoHooksWidget extends HookWidget { | ||
/// const NoHooksWidget({super.key}); | ||
/// | ||
/// @override | ||
/// Widget build(BuildContext context) => const Text('Hello World!'); | ||
/// } | ||
/// ``` | ||
/// | ||
/// **GOOD:** | ||
/// ```dart | ||
/// class NoHooksWidget extends StatelessWidget { | ||
/// const NoHooksWidget({super.key}); | ||
/// | ||
/// @override | ||
/// Widget build(BuildContext context) => const Text('Hello World!'); | ||
/// } | ||
/// ``` | ||
/// | ||
/// **Note: [HookWidget] usage is appropriate in the following cases:** | ||
/// - When using hooks within the widget | ||
/// - When extending [HookWidget] for a base widget class | ||
/// - When mixing in hook functionality with other widgets | ||
/// | ||
/// See also: | ||
/// - [HookWidget class - flutter_hooks API](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html) | ||
/// - [StatelessWidget class - widgets library - Dart API](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) | ||
class UnnecessaryHookWidget extends DartLintRule { | ||
/// Create a new instance of [UnnecessaryHookWidget]. | ||
const UnnecessaryHookWidget() | ||
: super( | ||
code: const LintCode( | ||
name: 'unnecessary_hook_widget', | ||
problemMessage: | ||
'Consider using StatelessWidget instead of HookWidget ' | ||
'when no hooks are used within the widget.', | ||
url: 'https://github.com/dassssshers/nilts#unnecessary_hook_widget', | ||
), | ||
); | ||
|
||
@override | ||
void run( | ||
CustomLintResolver _, | ||
ErrorReporter reporter, | ||
CustomLintContext context, | ||
) { | ||
context.registry.addClassDeclaration((node) { | ||
final superclass = node.extendsClause?.superclass; | ||
if (superclass == null || superclass.toString() != 'HookWidget') return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
var hasHooks = false; | ||
node.visitChildren( | ||
_Visitor(() { | ||
hasHooks = true; | ||
}), | ||
); | ||
|
||
if (!hasHooks) { | ||
reporter.atNode(superclass, code); | ||
} | ||
}); | ||
} | ||
|
||
@override | ||
List<Fix> getFixes() => [ | ||
_ReplaceWithStatelessWidget(), | ||
]; | ||
} | ||
|
||
class _Visitor extends RecursiveAstVisitor<void> { | ||
const _Visitor(this.onHookFound); | ||
|
||
final void Function() onHookFound; | ||
|
||
@override | ||
void visitMethodInvocation(MethodInvocation node) { | ||
if (node.methodName.name.startsWith('use')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
onHookFound(); | ||
} | ||
super.visitMethodInvocation(node); | ||
} | ||
|
||
@override | ||
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (node.function.toString().startsWith('use')) { | ||
onHookFound(); | ||
} | ||
super.visitFunctionExpressionInvocation(node); | ||
} | ||
} | ||
|
||
class _ReplaceWithStatelessWidget extends DartFix { | ||
@override | ||
void run( | ||
CustomLintResolver _, | ||
ChangeReporter reporter, | ||
CustomLintContext context, | ||
AnalysisError analysisError, | ||
List<AnalysisError> __, | ||
) { | ||
context.registry.addClassDeclaration((declaration) { | ||
final superclass = declaration.extendsClause?.superclass; | ||
if (superclass == null || | ||
!analysisError.sourceRange.intersects(superclass.sourceRange)) { | ||
return; | ||
} | ||
|
||
final replacement = _getReplacementWidget(superclass.toString()); | ||
if (replacement == null) return; | ||
|
||
reporter | ||
.createChangeBuilder( | ||
message: 'Replace With $replacement', | ||
priority: ChangePriority.replaceWithStatelessWidget, | ||
) | ||
.addDartFileEdit((builder) { | ||
builder.addSimpleReplacement( | ||
SourceRange(superclass.offset, superclass.length), | ||
replacement, | ||
); | ||
}); | ||
}); | ||
} | ||
|
||
String? _getReplacementWidget(String className) => switch (className) { | ||
'HookWidget' => 'StatelessWidget', | ||
_ => null, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,34 @@ | ||||||
name: nilts_flutter_hooks | ||||||
version: 0.19.0 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
description: nilts is lint rules, quick fixes and assists for Dart and Flutter projects that helps you enforce best practices, and avoid errors. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
homepage: https://github.com/dassssshers/nilts | ||||||
repository: https://github.com/dassssshers/nilts | ||||||
issue_tracker: https://github.com/dassssshers/nilts/issues | ||||||
platforms: | ||||||
android: | ||||||
ios: | ||||||
linux: | ||||||
macos: | ||||||
web: | ||||||
windows: | ||||||
funding: | ||||||
- https://github.com/sponsors/ronnnnn | ||||||
topics: | ||||||
- lints | ||||||
- lint | ||||||
- analysis | ||||||
- code-style | ||||||
- tools | ||||||
|
||||||
environment: | ||||||
sdk: '>=3.0.0 <4.0.0' | ||||||
|
||||||
dependencies: | ||||||
nilts_core: ^0.1.0 | ||||||
|
||||||
analyzer: ^7.0.0 | ||||||
analyzer_plugin: ^0.13.0 | ||||||
custom_lint_builder: ^0.7.3 | ||||||
|
||||||
dev_dependencies: | ||||||
very_good_analysis: ^7.0.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't add this in README on nilts.
You should separate README file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can write details after your PR merged.
Write minimal documentations please.