diff --git a/lib/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart b/lib/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart index 5110bbf7..0b288a0d 100644 --- a/lib/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart +++ b/lib/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart @@ -1,10 +1,9 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; +import 'package:analyzer/error/error.dart'; import 'package:solid_lints/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; +import 'package:solid_lints/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; -import 'package:solid_lints/src/utils/types_utils.dart'; /// Avoid `late` keyword /// @@ -48,62 +47,37 @@ import 'package:solid_lints/src/utils/types_utils.dart'; /// } /// ``` class AvoidLateKeywordRule extends SolidLintRule { - /// This lint rule represents - /// the error whether we use `late` keyword. - static const lintName = 'avoid_late_keyword'; + static const String _lintName = 'avoid_late_keyword'; - AvoidLateKeywordRule._(super.config); + static const LintCode _code = LintCode( + _lintName, + 'Avoid using the "late" keyword. It may result in runtime exceptions.', + ); - /// Creates a new instance of [AvoidLateKeywordRule] - /// based on the lint configuration. - factory AvoidLateKeywordRule.createRule(CustomLintConfigs configs) { - final rule = RuleConfig( - configs: configs, - name: lintName, - paramsParser: AvoidLateKeywordParameters.fromJson, - problemMessage: (_) => 'Avoid using the "late" keyword. ' - 'It may result in runtime exceptions.', - ); + /// Creates an instance of [AvoidLateKeywordRule]. + AvoidLateKeywordRule({required super.analysisOptionsLoader}) + : super.withParameters( + name: _lintName, + description: 'Warns against using the late keyword.', + parametersParser: AvoidLateKeywordParameters.fromJson, + ); - return AvoidLateKeywordRule._(rule); - } + @override + LintCode get diagnosticCode => _code; @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addVariableDeclaration((node) { - if (_shouldLint(node)) { - reporter.atNode(node, code); - } - }); - } - - bool _shouldLint(VariableDeclaration node) { - final isLateDeclaration = node.isLate; - if (!isLateDeclaration) return false; - - final hasIgnoredType = _hasIgnoredType(node); - if (hasIgnoredType) return false; - - final allowInitialized = config.parameters.allowInitialized; - if (!allowInitialized) return true; - - final hasInitializer = node.initializer != null; - return !hasInitializer; - } - - bool _hasIgnoredType(VariableDeclaration node) { - final ignoredTypes = config.parameters.ignoredTypes.toSet(); - if (ignoredTypes.isEmpty) return false; + final parameters = + getParametersForContext(context) ?? const AvoidLateKeywordParameters(); - final variableType = node.declaredFragment?.element.type; - if (variableType == null) return false; + final visitor = AvoidLateKeywordVisitor(this, parameters); - return variableType.hasIgnoredType( - ignoredTypes: ignoredTypes, + registry.addVariableDeclaration( + this, + visitor, ); } } diff --git a/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart b/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart new file mode 100644 index 00000000..fc1fa960 --- /dev/null +++ b/lib/src/lints/avoid_late_keyword/visitors/avoid_late_keyword_visitor.dart @@ -0,0 +1,48 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart'; +import 'package:solid_lints/src/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart'; +import 'package:solid_lints/src/utils/types_utils.dart'; + +/// Visitor for [AvoidLateKeywordRule]. +class AvoidLateKeywordVisitor extends SimpleAstVisitor { + final AvoidLateKeywordRule _rule; + + final AvoidLateKeywordParameters _parameters; + + /// Creates an instance of [AvoidLateKeywordVisitor]. + AvoidLateKeywordVisitor(this._rule, this._parameters); + + @override + void visitVariableDeclaration(VariableDeclaration node) { + if (!_shouldReport(node)) return; + + _rule.reportAtNode(node); + } + + bool _shouldReport(VariableDeclaration node) { + final isLateDeclaration = node.isLate; + if (!isLateDeclaration) return false; + + final hasIgnoredType = _hasIgnoredType(node); + if (hasIgnoredType) return false; + + final allowInitialized = _parameters.allowInitialized; + if (!allowInitialized) return true; + + final hasInitializer = node.initializer != null; + return !hasInitializer; + } + + bool _hasIgnoredType(VariableDeclaration node) { + final ignoredTypes = _parameters.ignoredTypes.toSet(); + if (ignoredTypes.isEmpty) return false; + + final variableType = node.declaredFragment?.element.type; + if (variableType == null) return false; + + return variableType.hasIgnoredType( + ignoredTypes: ignoredTypes, + ); + } +} diff --git a/lint_test/avoid_late_keyword/allow_initialized/analysis_options.yaml b/lint_test/avoid_late_keyword/allow_initialized/analysis_options.yaml deleted file mode 100644 index 7f92efe8..00000000 --- a/lint_test/avoid_late_keyword/allow_initialized/analysis_options.yaml +++ /dev/null @@ -1,10 +0,0 @@ -analyzer: - plugins: - - ../custom_lint - -custom_lint: - rules: - - avoid_late_keyword: - allow_initialized: false - ignored_types: - - Animation diff --git a/lint_test/avoid_late_keyword/allow_initialized/avoid_late_keyword_allow_initialized_test.dart b/lint_test/avoid_late_keyword/allow_initialized/avoid_late_keyword_allow_initialized_test.dart deleted file mode 100644 index 05af24d1..00000000 --- a/lint_test/avoid_late_keyword/allow_initialized/avoid_late_keyword_allow_initialized_test.dart +++ /dev/null @@ -1,84 +0,0 @@ -// ignore_for_file: prefer_const_declarations, unused_local_variable, prefer_match_file_name -// ignore_for_file: avoid_global_state - -abstract class Animation {} - -class AnimationController implements Animation {} - -class SubAnimationController extends AnimationController {} - -class ColorTween {} - -/// Check "late" keyword fail -/// -/// `avoid_late_keyword` -/// allow_initialized option disabled -class AvoidLateKeyword { - /// ignored_types: Animation - late final Animation animation1; - - /// ignored_types: Animation - late final animation2 = AnimationController(); - - /// ignored_types: Animation - late final animation3 = SubAnimationController(); - - /// expect_lint: avoid_late_keyword - late final ColorTween colorTween1; - - /// expect_lint: avoid_late_keyword - late final colorTween2 = ColorTween(); - - /// expect_lint: avoid_late_keyword - late final colorTween3 = colorTween2; - - /// ignored_types: Animation - late final AnimationController controller1; - - /// expect_lint: avoid_late_keyword - late final field1 = 'string'; - - /// expect_lint: avoid_late_keyword - late final String field2; - - /// expect_lint: avoid_late_keyword - late final String field3 = 'string'; - - /// expect_lint: avoid_late_keyword - late final field4; - - void test() { - /// ignored_types: Animation - late final Animation animation1; - - /// ignored_types: Animation - late final animation2 = AnimationController(); - - /// ignored_types: Animation - late final animation3 = SubAnimationController(); - - /// expect_lint: avoid_late_keyword - late final ColorTween colorTween1; - - /// expect_lint: avoid_late_keyword - late final colorTween2 = ColorTween(); - - /// expect_lint: avoid_late_keyword - late final colorTween3 = colorTween2; - - /// ignored_types: Animation - late final AnimationController controller1; - - /// expect_lint: avoid_late_keyword - late final local1 = 'string'; - - /// expect_lint: avoid_late_keyword - late final String local2; - - /// expect_lint: avoid_late_keyword - late final String local4 = 'string'; - - /// expect_lint: avoid_late_keyword - late final local3; - } -} diff --git a/lint_test/avoid_late_keyword/no_generics/analysis_options.yaml b/lint_test/avoid_late_keyword/no_generics/analysis_options.yaml deleted file mode 100644 index da6add2f..00000000 --- a/lint_test/avoid_late_keyword/no_generics/analysis_options.yaml +++ /dev/null @@ -1,10 +0,0 @@ -analyzer: - plugins: - - ../custom_lint - -custom_lint: - rules: - - avoid_late_keyword: - allow_initialized: false - ignored_types: - - Subscription diff --git a/lint_test/avoid_late_keyword/no_generics/avoid_late_keyword_no_generics_test.dart b/lint_test/avoid_late_keyword/no_generics/avoid_late_keyword_no_generics_test.dart deleted file mode 100644 index 7c166749..00000000 --- a/lint_test/avoid_late_keyword/no_generics/avoid_late_keyword_no_generics_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -// ignore_for_file: prefer_const_declarations, unused_local_variable, prefer_match_file_name -// ignore_for_file: avoid_global_state - -class Subscription {} - -class ConcreteTypeWithNoGenerics {} - -class NotAllowed {} - -/// Check "late" keyword fail -/// -/// `avoid_late_keyword` -/// allow_initialized option disabled -class AvoidLateKeyword { - /// expect_lint: avoid_late_keyword - late final NotAllowed na1; - - /// ignored_types: Subscription - late final Subscription subscription1; - - /// ignored_types: Subscription - late final Subscription subscription2; - - /// ignored_types: Subscription - late final Subscription> subscription3; - - /// ignored_types: Subscription - late final Subscription>> subscription4; - - /// ignored_types: Subscription - late final Subscription> subscription5; - - void test() { - /// expect_lint: avoid_late_keyword - late final NotAllowed na1; - - /// ignored_types: Subscription - late final Subscription subscription1; - - /// ignored_types: Subscription - late final Subscription subscription2; - - /// ignored_types: Subscription - late final Subscription> subscription3; - - /// ignored_types: Subscription - late final Subscription>> subscription4; - - /// ignored_types: Subscription - late final Subscription> subscription5; - } -} diff --git a/lint_test/avoid_late_keyword/with_generics/analysis_options.yaml b/lint_test/avoid_late_keyword/with_generics/analysis_options.yaml deleted file mode 100644 index 47219688..00000000 --- a/lint_test/avoid_late_keyword/with_generics/analysis_options.yaml +++ /dev/null @@ -1,14 +0,0 @@ -analyzer: - plugins: - - ../custom_lint - -custom_lint: - rules: - - avoid_late_keyword: - allow_initialized: true - ignored_types: - - ColorTween - - AnimationController - - Subscription> - - Subscription> - - Subscription diff --git a/lint_test/avoid_late_keyword/with_generics/avoid_late_keyword_with_generics_test.dart b/lint_test/avoid_late_keyword/with_generics/avoid_late_keyword_with_generics_test.dart deleted file mode 100644 index 9778438e..00000000 --- a/lint_test/avoid_late_keyword/with_generics/avoid_late_keyword_with_generics_test.dart +++ /dev/null @@ -1,114 +0,0 @@ -// ignore_for_file: prefer_const_declarations, unused_local_variable, prefer_match_file_name -// ignore_for_file: avoid_global_state - -class ColorTween {} - -class AnimationController {} - -class SubAnimationController extends AnimationController {} - -class Allowed {} - -class NotAllowed {} - -class Subscription {} - -class ConcreteTypeWithNoGenerics {} - -/// Check "late" keyword fail -/// -/// `avoid_late_keyword` -/// allow_initialized option enabled -class AvoidLateKeyword { - /// ignored_types: ColorTween - late final ColorTween colorTween; - - /// ignored_types: AnimationController - late final AnimationController controller1; - - /// ignored_types: AnimationController - late final SubAnimationController controller2; - - /// ignored_types: AnimationController - late final controller3 = AnimationController(); - - /// ignored_types: AnimationController - late final controller4 = SubAnimationController(); - - /// allow_initialized: true - late final field1 = 'string'; - - /// expect_lint: avoid_late_keyword - late final String field2; - - /// expect_lint: avoid_late_keyword - late final field3; - - /// expect_lint: avoid_late_keyword - late final NotAllowed na1; - - /// allow_initialized: true - late final a = Allowed(); - - /// expect_lint: avoid_late_keyword - late final Subscription subscription1; - - /// ignored_types: Subscription - late final Subscription subscription2; - - /// ignored_types: Subscription> - late final Subscription> subscription3; - - /// ignored_types: Subscription> - late final Subscription>> subscription4; - - /// ignored_types: Subscription> - late final Subscription> subscription5; - - /// ignored_types: Subscription> - late final Subscription> subscription6; - - /// expect_lint: avoid_late_keyword - late final Subscription> subscription7; - - void test() { - /// ignored_types: ColorTween - late final ColorTween colorTween; - - /// ignored_types: AnimationController - late final AnimationController controller1; - - /// ignored_types: AnimationController - late final SubAnimationController controller2; - - /// ignored_types: AnimationController - late final controller3 = AnimationController(); - - /// ignored_types: AnimationController - late final controller4 = SubAnimationController(); - - /// allow_initialized: true - late final local1 = 'string'; - - /// expect_lint: avoid_late_keyword - late final String local2; - - /// expect_lint: avoid_late_keyword - late final local3; - - /// expect_lint: avoid_late_keyword - late final NotAllowed na1; - - /// allow_initialized: true - late final a = Allowed(); - - /// expect_lint: avoid_late_keyword - late final Subscription subscription1; - - /// ignored_types: Subscription - late final Subscription subscription2; - - /// ignored_types: Subscription> - late final Subscription> subscription3; - } -} diff --git a/test/avoid_late_keyword_rule_test.dart b/test/avoid_late_keyword_rule_test.dart new file mode 100644 index 00000000..778f9f9d --- /dev/null +++ b/test/avoid_late_keyword_rule_test.dart @@ -0,0 +1,350 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:analyzer_testing/utilities/utilities.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; +import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidLateKeywordRuleTest); + defineReflectiveTests(AvoidLateKeywordNoGenericsTest); + defineReflectiveTests(AvoidLateKeywordWithGenericsTest); + }); +} + +@reflectiveTest +class AvoidLateKeywordRuleTest extends AnalysisRuleTest { + final String _typesDefinitions = ''' +abstract class Animation {} + +class AnimationController implements Animation {} + +class SubAnimationController extends AnimationController {} + +class ColorTween {} +'''; + + @override + void setUp() { + rule = AvoidLateKeywordRule( + analysisOptionsLoader: + AnalysisOptionsLoader(resourceProvider: resourceProvider), + ); + super.setUp(); + + newAnalysisOptionsYamlFile( + testPackageRootPath, + ''' +${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + ${rule.name}: + allow_initialized: false + ignored_types: + - Animation +''', + ); + } + + Future test_does_not_report_ignored_types_fields() async { + await assertNoDiagnostics( + ''' +class Test { + late final Animation animation1; + late final animation2 = AnimationController(); + late final animation3 = SubAnimationController(); + late final AnimationController controller1; +} +$_typesDefinitions + ''', + ); + } + + Future test_does_not_report_ignored_types_local_variables() async { + await assertNoDiagnostics( + ''' +void test() { + late final Animation animation1; + late final animation2 = AnimationController(); + late final animation3 = SubAnimationController(); + late final AnimationController controller1; +} +$_typesDefinitions + ''', + ); + } + + Future test_reports_non_ignored_types_fields() async { + await assertDiagnostics( + ''' +class Test { + late final ColorTween colorTween1; + late final colorTween2 = ColorTween(); + late final colorTween3 = colorTween2; + late final field1 = 'string'; + late final String field2; + late final String field3 = 'string'; + late final field4; +} +$_typesDefinitions + ''', + [ + lint(37, 11), + lint(63, 26), + lint(104, 25), + lint(144, 17), + lint(183, 6), + lint(211, 17), + lint(243, 6), + ], + ); + } + + Future test_reports_non_ignored_types_local_variables() async { + await assertDiagnostics( + ''' +void test() { + late final ColorTween colorTween1; + late final colorTween2 = ColorTween(); + late final colorTween3 = colorTween2; + late final local1 = 'string'; + late final String local2; + late final String local4 = 'string'; + late final local3; +} +$_typesDefinitions + ''', + [ + lint(38, 11), + lint(64, 26), + lint(105, 25), + lint(145, 17), + lint(184, 6), + lint(212, 17), + lint(244, 6), + ], + ); + } +} + +@reflectiveTest +class AvoidLateKeywordNoGenericsTest extends AnalysisRuleTest { + final String _typesDefinitions = ''' +class Subscription {} + +class ConcreteTypeWithNoGenerics {} + +class NotAllowed {} +'''; + + @override + void setUp() { + rule = AvoidLateKeywordRule( + analysisOptionsLoader: + AnalysisOptionsLoader(resourceProvider: resourceProvider), + ); + super.setUp(); + + newAnalysisOptionsYamlFile( + testPackageRootPath, + ''' +${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + ${rule.name}: + allow_initialized: false + ignored_types: + - Subscription +''', + ); + } + + Future test_does_not_report_ignored_types_fields() async { + await assertNoDiagnostics( + ''' +class Test { + late final Subscription subscription1; + late final Subscription subscription2; + late final Subscription> subscription3; + late final Subscription>> subscription4; + late final Subscription> subscription5; +} +$_typesDefinitions + ''', + ); + } + + Future test_does_not_report_ignored_types_local_variables() async { + await assertNoDiagnostics( + ''' +void test() { + late final Subscription subscription1; + late final Subscription subscription2; + late final Subscription> subscription3; + late final Subscription>> subscription4; + late final Subscription> subscription5; +} +$_typesDefinitions + ''', + ); + } + + Future test_reports_non_ignored_types_fields() async { + await assertDiagnostics( + ''' +class Test { + late final NotAllowed na1; +} +$_typesDefinitions + ''', + [ + lint(37, 3), + ], + ); + } + + Future test_reports_non_ignored_types_local_variables() async { + await assertDiagnostics( + ''' +void test() { + late final NotAllowed na1; +} +$_typesDefinitions + ''', + [ + lint(38, 3), + ], + ); + } +} + +@reflectiveTest +class AvoidLateKeywordWithGenericsTest extends AnalysisRuleTest { + final String _typesDefinitions = ''' +class ColorTween {} + +class AnimationController {} + +class SubAnimationController extends AnimationController {} + +class Allowed {} + +class NotAllowed {} + +class Subscription {} + +class ConcreteTypeWithNoGenerics {} +'''; + + @override + void setUp() { + rule = AvoidLateKeywordRule( + analysisOptionsLoader: + AnalysisOptionsLoader(resourceProvider: resourceProvider), + ); + super.setUp(); + + newAnalysisOptionsYamlFile( + testPackageRootPath, + ''' +${analysisOptionsContent(rules: [rule.name])} +plugins: + solid_lints: + diagnostics: + ${rule.name}: + allow_initialized: true + ignored_types: + - ColorTween + - AnimationController + - Subscription> + - Subscription> + - Subscription +''', + ); + } + + Future test_does_not_report_ignored_types_fields() async { + await assertNoDiagnostics( + ''' +class Test { + late final ColorTween colorTween; + late final AnimationController controller1; + late final SubAnimationController controller2; + late final controller3 = AnimationController(); + late final controller4 = SubAnimationController(); + late final Subscription subscription2; + late final Subscription> subscription3; + late final Subscription>> subscription4; + late final Subscription> subscription5; + late final Subscription> subscription6; + late final field1 = 'string'; + late final a = Allowed(); +} +$_typesDefinitions + ''', + ); + } + + Future test_does_not_report_ignored_types_local_variables() async { + await assertNoDiagnostics( + ''' +void test() { + late final ColorTween colorTween; + late final AnimationController controller1; + late final SubAnimationController controller2; + late final controller3 = AnimationController(); + late final controller4 = SubAnimationController(); + late final Subscription subscription2; + late final Subscription> subscription3; + late final local1 = 'string'; + late final a = Allowed(); +} +$_typesDefinitions + ''', + ); + } + + Future test_reports_non_ignored_types_fields() async { + await assertDiagnostics( + ''' +class Test { + late final String field2; + late final field3; + late final NotAllowed na1; + late final Subscription subscription1; + late final Subscription> subscription7; +} +$_typesDefinitions + ''', + [ + lint(33, 6), + lint(54, 6), + lint(86, 3), + lint(125, 13), + lint(188, 13), + ], + ); + } + + Future test_reports_non_ignored_types_local_variables() async { + await assertDiagnostics( + ''' +void test() { + late final String local2; + late final local3; + late final NotAllowed na1; + late final Subscription subscription1; +} +$_typesDefinitions + ''', + [ + lint(34, 6), + lint(55, 6), + lint(87, 3), + lint(126, 13), + ], + ); + } +}