From 7083372225918af34a6d79f722dc77782b312a3c Mon Sep 17 00:00:00 2001 From: ronnnnn Date: Sun, 1 Oct 2023 18:44:17 +0900 Subject: [PATCH 1/4] feat: defined_void_callback_type --- packages/nilts/lib/nilts.dart | 2 + packages/nilts/lib/src/change_priority.dart | 3 + .../src/lints/defined_void_callback_type.dart | 71 +++++++++++++++++++ .../lints/defined_void_callback_type.dart | 69 ++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 packages/nilts/lib/src/lints/defined_void_callback_type.dart create mode 100644 packages/nilts_test/test/lints/defined_void_callback_type.dart diff --git a/packages/nilts/lib/nilts.dart b/packages/nilts/lib/nilts.dart index d32fa1e..80893d4 100644 --- a/packages/nilts/lib/nilts.dart +++ b/packages/nilts/lib/nilts.dart @@ -1,4 +1,5 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:nilts/src/lints/defined_void_callback_type.dart'; import 'package:nilts/src/lints/fixed_text_scale_factor_rich_text.dart'; import 'package:nilts/src/lints/flaky_tests_with_set_up_all.dart'; import 'package:nilts/src/lints/unnecessary_rebuilds_from_media_query.dart'; @@ -11,6 +12,7 @@ PluginBase createPlugin() => _NiltsLint(); class _NiltsLint extends PluginBase { @override List getLintRules(CustomLintConfigs configs) => [ + const DefinedVoidCallbackType(), const FixedTextScaleFactorRichText(), const FlakyTestsWithSetUpAll(), const UnnecessaryRebuildsFromMediaQuery(), diff --git a/packages/nilts/lib/src/change_priority.dart b/packages/nilts/lib/src/change_priority.dart index 0edf684..6bfe56e 100644 --- a/packages/nilts/lib/src/change_priority.dart +++ b/packages/nilts/lib/src/change_priority.dart @@ -11,6 +11,9 @@ class ChangePriority { /// The priority for [_ReplaceWithSetUp]. static const int replaceWithSetUp = 100; + /// The priority for [_ReplaceWithVoidCallback]. + static const int replaceWithVoidCallback = 100; + /// The priority for [_UnwrapSetUpAll]. static const int unwrapSetUpAll = 90; } diff --git a/packages/nilts/lib/src/lints/defined_void_callback_type.dart b/packages/nilts/lib/src/lints/defined_void_callback_type.dart new file mode 100644 index 0000000..0a60d53 --- /dev/null +++ b/packages/nilts/lib/src/lints/defined_void_callback_type.dart @@ -0,0 +1,71 @@ +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:nilts/src/change_priority.dart'; + +class DefinedVoidCallbackType extends DartLintRule { + /// Create a new instance of [DefinedVoidCallbackType]. + const DefinedVoidCallbackType() : super(code: _code); + + static const _code = LintCode( + name: 'defined_void_callback_type', + problemMessage: 'VoidCallback type is defined in SDK.', + url: 'https://github.com/ronnnnn/nilts#defined_void_callback_type', + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addTypeAnnotation((node) { + final type = node.type; + // Do nothing if the type is not Function. + if (type is! FunctionType) return; + + // Do nothing if Function has parameters. + if (type.parameters.isNotEmpty) return; + + // Do nothing if the return type is not void. + final returnType = type.returnType; + if (returnType is! VoidType) return; + + reporter.reportErrorForNode(_code, node); + }); + } + + @override + List getFixes() => [ + _ReplaceWithVoidCallbackType(), + ]; +} + +class _ReplaceWithVoidCallbackType extends DartFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + context.registry.addTypeAnnotation((node) { + if (!node.sourceRange.intersects(analysisError.sourceRange)) return; + + reporter + .createChangeBuilder( + message: 'Replace with VoidCallback', + priority: ChangePriority.replaceWithVoidCallback, + ) + .addDartFileEdit((builder) { + final delta = node.question != null ? -1 : 0; + builder.addSimpleReplacement( + node.sourceRange.getMoveEnd(delta), + 'VoidCallback', + ); + }); + }); + } +} diff --git a/packages/nilts_test/test/lints/defined_void_callback_type.dart b/packages/nilts_test/test/lints/defined_void_callback_type.dart new file mode 100644 index 0000000..be8ac77 --- /dev/null +++ b/packages/nilts_test/test/lints/defined_void_callback_type.dart @@ -0,0 +1,69 @@ +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: type_init_formals +// ignore_for_file: unused_element + +import 'package:flutter/material.dart'; + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: MainButton(() {}), + ), + ), + ); + } +} + +class MainButton extends StatelessWidget { + const MainButton( + // expect_lint: defined_void_callback_type + void Function() this.onPressed, { + // expect_lint: defined_void_callback_type + void Function()? this.onNullablePressed, + super.key, + }); + + // expect_lint: defined_void_callback_type + final void Function() onPressed; + // expect_lint: defined_void_callback_type + final void Function()? onNullablePressed; + + void _onPressed( + // expect_lint: defined_void_callback_type + void Function() onPressed, { + // expect_lint: defined_void_callback_type + void Function()? onNullablePressed, + }) {} + + @override + Widget build(BuildContext context) { + return FilledButton( + onPressed: () { + _onPressed(() {}); + onPressed(); + }, + child: const Text('Hello World!'), + ); + } +} + +// expect_lint: defined_void_callback_type +final void Function() globalFunction = () {}; +// expect_lint: defined_void_callback_type +const void Function()? globalNullableFunction = null; + +void _globalFunction( + // expect_lint: defined_void_callback_type + void Function() onPressed, { + // expect_lint: defined_void_callback_type + void Function()? onNullablePressed, +}) {} From 5f75a56e08db3c8816b1ace71b14f8b88a40f9fa Mon Sep 17 00:00:00 2001 From: ronnnnn Date: Sun, 1 Oct 2023 21:37:11 +0900 Subject: [PATCH 2/4] docs: add docs for defined_void_callback_type --- packages/nilts/README.md | 23 +++++++++++++++++++ .../src/lints/defined_void_callback_type.dart | 23 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/nilts/README.md b/packages/nilts/README.md index 96f83ce..5f8f84a 100644 --- a/packages/nilts/README.md +++ b/packages/nilts/README.md @@ -79,12 +79,35 @@ Some of lint rules support quick fixes on IDE. | Rule name | Overview | Target SDK | Rule type | Maturity level | Quick fix | | :-- | :-- | :--: | :--: | :--: | :--: | +| [defined\_void\_callback\_type](#defined_void_callback_type) | Checks `void Function()` definitions. | Any versions nilts supports | Practice | Experimental | ✅️ | | [fixed\_text\_scale\_factor\_rich\_text](#fixed_text_scale_factor_rich_text) | Checks usage of `textScaleFactor` in `RichText` constructor. | Any versions nilts supports | Practice | Experimental | ✅️ | | [flaky\_tests\_with\_set\_up\_all](#flaky_tests_with_set_up_all) | Checks `setUpAll` usages. | Any versions nilts supports | Practice | Experimental | ✅️ | | [unnecessary\_rebuilds\_from\_media\_query](#unnecessary_rebuilds_from_media_query) | Checks `MediaQuery.xxxOf(context)` or `MediaQuery.maybeXxxOf(context)` usages. | >= Flutter 3.10.0 (Dart 3.0.0) | Practice | Experimental | ✅️ | ### Details +#### defined_void_callback_type + +- Target SDK: >= Flutter 3.10.0 (Dart 3.0.0) +- Rule type: Practice +- Maturity level: Experimental +- Quick fix: ✅ + +**Consider** replace `void Function()` with `VoidCallback` which is defined in Flutter SDK. + +**BAD:** + +```dart +final void Function() callback; +``` + + +**GOOD:** + +```dart +final VoidCallback callback; +``` + #### fixed_text_scale_factor_rich_text - Target SDK: Any versions nilts supports diff --git a/packages/nilts/lib/src/lints/defined_void_callback_type.dart b/packages/nilts/lib/src/lints/defined_void_callback_type.dart index 0a60d53..31853b7 100644 --- a/packages/nilts/lib/src/lints/defined_void_callback_type.dart +++ b/packages/nilts/lib/src/lints/defined_void_callback_type.dart @@ -4,13 +4,34 @@ import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:nilts/src/change_priority.dart'; +/// A class for `defined_void_callback_type` rule. +/// +/// This rule checks defining `void Function()` type. +/// +/// - Target SDK: Any versions nilts supports +/// - Rule type: Practice +/// - Maturity level: Experimental +/// - Quick fix: ✅ +/// +/// **Consider** replace `void Function()` with `VoidCallback` which is defined +/// in Flutter SDK. +/// +/// **BAD:** +/// ```dart +/// final void Function() callback; +/// ``` +/// +/// **GOOD:** +/// ```dart +/// final VoidCallback callback; +/// ``` class DefinedVoidCallbackType extends DartLintRule { /// Create a new instance of [DefinedVoidCallbackType]. const DefinedVoidCallbackType() : super(code: _code); static const _code = LintCode( name: 'defined_void_callback_type', - problemMessage: 'VoidCallback type is defined in SDK.', + problemMessage: 'VoidCallback type is defined in Flutter SDK.', url: 'https://github.com/ronnnnn/nilts#defined_void_callback_type', ); From 945e360328eaa5e6e1a01d5e60e8f8903af74981 Mon Sep 17 00:00:00 2001 From: ronnnnn Date: Sun, 1 Oct 2023 21:42:12 +0900 Subject: [PATCH 3/4] test: add not lint test patterns --- .../test/lints/defined_void_callback_type.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/nilts_test/test/lints/defined_void_callback_type.dart b/packages/nilts_test/test/lints/defined_void_callback_type.dart index be8ac77..15cb175 100644 --- a/packages/nilts_test/test/lints/defined_void_callback_type.dart +++ b/packages/nilts_test/test/lints/defined_void_callback_type.dart @@ -29,6 +29,8 @@ class MainButton extends StatelessWidget { void Function() this.onPressed, { // expect_lint: defined_void_callback_type void Function()? this.onNullablePressed, + void Function(int)? this.onParamPressed, + int Function()? this.onNotVoidPressed, super.key, }); @@ -36,12 +38,16 @@ class MainButton extends StatelessWidget { final void Function() onPressed; // expect_lint: defined_void_callback_type final void Function()? onNullablePressed; + final void Function(int)? onParamPressed; + final int Function()? onNotVoidPressed; void _onPressed( // expect_lint: defined_void_callback_type void Function() onPressed, { // expect_lint: defined_void_callback_type void Function()? onNullablePressed, + void Function(int)? onParamPressed, + int Function()? onNotVoidPressed, }) {} @override @@ -60,10 +66,14 @@ class MainButton extends StatelessWidget { final void Function() globalFunction = () {}; // expect_lint: defined_void_callback_type const void Function()? globalNullableFunction = null; +const void Function(int)? globalParamFunction = null; +const int Function()? globalNotVoidFunction = null; void _globalFunction( // expect_lint: defined_void_callback_type void Function() onPressed, { // expect_lint: defined_void_callback_type void Function()? onNullablePressed, + void Function(int)? onParamPressed, + int Function()? onNotVoidPressed, }) {} From 934fa898d0d0f0ee458911f5336ec424fa4ca6c0 Mon Sep 17 00:00:00 2001 From: ronnnnn Date: Sun, 1 Oct 2023 21:44:23 +0900 Subject: [PATCH 4/4] docs: fix target sdk --- packages/nilts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nilts/README.md b/packages/nilts/README.md index 5f8f84a..f62424a 100644 --- a/packages/nilts/README.md +++ b/packages/nilts/README.md @@ -88,7 +88,7 @@ Some of lint rules support quick fixes on IDE. #### defined_void_callback_type -- Target SDK: >= Flutter 3.10.0 (Dart 3.0.0) +- Target SDK: Any versions nilts supports - Rule type: Practice - Maturity level: Experimental - Quick fix: ✅