diff --git a/lib/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart b/lib/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart index 0b63cc37..6bb9c2d3 100644 --- a/lib/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart +++ b/lib/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart @@ -1,50 +1,41 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer/analysis_rule/analysis_rule.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_unrelated_type_assertions/visitors/avoid_unrelated_type_assertions_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// A `avoid_unrelated_type_assertions` rule which /// warns about unnecessary usage of `as` operator -class AvoidUnrelatedTypeAssertionsRule extends SolidLintRule { - /// This lint rule represents - /// the error whether we use bad formatted double literals. - static const lintName = 'avoid_unrelated_type_assertions'; +class AvoidUnrelatedTypeAssertionsRule extends AnalysisRule { + /// The name of the lint rule. + static const _lintName = 'avoid_unrelated_type_assertions'; - AvoidUnrelatedTypeAssertionsRule._(super.config); + /// The message shown when the lint rule is triggered. + static const _lintMessage = + 'Avoid unrelated "is" assertion. The result is always "{0}".'; - /// Creates a new instance of [AvoidUnrelatedTypeAssertionsRule] - /// based on the lint configuration. - factory AvoidUnrelatedTypeAssertionsRule.createRule( - CustomLintConfigs configs, - ) { - final rule = RuleConfig( - configs: configs, - name: lintName, - problemMessage: (_) => - 'Avoid unrelated "is" assertion. The result is always "{0}".', - ); + /// Lint code for this rule. + static const LintCode _code = LintCode( + _lintName, + _lintMessage, + ); - return AvoidUnrelatedTypeAssertionsRule._(rule); - } + /// Creates a new instance of [AvoidUnrelatedTypeAssertionsRule]. + AvoidUnrelatedTypeAssertionsRule() + : super( + name: _lintName, + description: _lintMessage, + ); @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, - ) { - context.registry.addIsExpression((node) { - final visitor = AvoidUnrelatedTypeAssertionsVisitor(); - visitor.visitIsExpression(node); + LintCode get diagnosticCode => _code; - for (final element in visitor.expressions.entries) { - reporter.atNode( - element.key, - code, - arguments: [element.value.toString()], - ); - } - }); + @override + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + final visitor = AvoidUnrelatedTypeAssertionsVisitor(this); + registry.addIsExpression(this, visitor); } } diff --git a/lib/src/lints/avoid_unrelated_type_assertions/visitors/avoid_unrelated_type_assertions_visitor.dart b/lib/src/lints/avoid_unrelated_type_assertions/visitors/avoid_unrelated_type_assertions_visitor.dart index e0b4aa6a..2f0e33e3 100644 --- a/lib/src/lints/avoid_unrelated_type_assertions/visitors/avoid_unrelated_type_assertions_visitor.dart +++ b/lib/src/lints/avoid_unrelated_type_assertions/visitors/avoid_unrelated_type_assertions_visitor.dart @@ -26,14 +26,15 @@ import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; +import 'package:solid_lints/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart'; -/// AST Visitor which finds all is expressions and checks if they are -/// unrelated (result always false) -class AvoidUnrelatedTypeAssertionsVisitor extends RecursiveAstVisitor { - final _expressions = {}; +/// Visitor for [AvoidUnrelatedTypeAssertionsRule]. +class AvoidUnrelatedTypeAssertionsVisitor extends SimpleAstVisitor { + /// The rule associated with this visitor. + final AvoidUnrelatedTypeAssertionsRule _rule; - /// Map of unrelated type checks and their results - Map get expressions => _expressions; + /// Creates an instance of [AvoidUnrelatedTypeAssertionsVisitor]. + AvoidUnrelatedTypeAssertionsVisitor(this._rule); @override void visitIsExpression(IsExpression node) { @@ -47,7 +48,10 @@ class AvoidUnrelatedTypeAssertionsVisitor extends RecursiveAstVisitor { final objectType = node.expression.staticType; if (_isUnrelatedTypeCheck(objectType, castedType)) { - _expressions[node] = node.notOperator != null; + _rule.reportAtNode( + node, + arguments: [if (node.notOperator != null) 'true' else 'false'], + ); } } @@ -64,10 +68,14 @@ class AvoidUnrelatedTypeAssertionsVisitor extends RecursiveAstVisitor { return false; } - final objectCastedType = - _foundCastedTypeInObjectTypeHierarchy(objectType, castedType); - final castedObjectType = - _foundCastedTypeInObjectTypeHierarchy(castedType, objectType); + final objectCastedType = _foundCastedTypeInObjectTypeHierarchy( + objectType, + castedType, + ); + final castedObjectType = _foundCastedTypeInObjectTypeHierarchy( + castedType, + objectType, + ); if (objectCastedType == null && castedObjectType == null) { return true; } @@ -94,8 +102,8 @@ class AvoidUnrelatedTypeAssertionsVisitor extends RecursiveAstVisitor { final correctObjectType = objectType is InterfaceType && objectType.isDartAsyncFutureOr - ? objectType.typeArguments.first - : objectType; + ? objectType.typeArguments.first + : objectType; if ((correctObjectType.element == castedType.element) || castedType is DynamicType || @@ -105,8 +113,9 @@ class AvoidUnrelatedTypeAssertionsVisitor extends RecursiveAstVisitor { } if (correctObjectType is InterfaceType) { - return correctObjectType.allSupertypes - .firstWhereOrNull((value) => value.element == castedType.element); + return correctObjectType.allSupertypes.firstWhereOrNull( + (value) => value.element == castedType.element, + ); } return null; diff --git a/lint_test/avoid_unrelated_type_assertions_test.dart b/lint_test/avoid_unrelated_type_assertions_test.dart deleted file mode 100644 index 209a5615..00000000 --- a/lint_test/avoid_unrelated_type_assertions_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -// ignore_for_file: prefer_const_declarations, prefer_match_file_name, unused_element -// ignore_for_file: unnecessary_nullable_for_final_variable_declarations -// ignore_for_file: unused_local_variable - -/// Check the `avoid_unrelated_type_assertions` rule -class Foo {} - -class Bar {} - -class ChildFoo extends Foo {} - -void fun() { - final testString = ''; - final testList = [1, 2, 3]; - final testMap = {'A': 'B'}; - final Foo foo = Foo(); - final childFoo = ChildFoo(); - - // expect_lint: avoid_unrelated_type_assertions - final result = testString is int; - - // expect_lint: avoid_unrelated_type_assertions - final result2 = testList is List; - - // expect_lint: avoid_unrelated_type_assertions - final result3 = foo is Bar; - - // expect_lint: avoid_unrelated_type_assertions - final result4 = childFoo is Bar; - - // expect_lint: avoid_unrelated_type_assertions - final result5 = testMap['A'] is double; -} - -class _A {} - -class _B extends _A {} - -class _C {} - -void lint() { - final _A a = _B(); - // Always false - // expect_lint: avoid_unrelated_type_assertions - if (a is _C) return; - // Always true - // expect_lint: avoid_unrelated_type_assertions - if (a is! _C) return; -} diff --git a/pubspec.yaml b/pubspec.yaml index f85b1309..70168359 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,7 +8,7 @@ documentation: https://solid-software.github.io/solid_lints/docs/intro topics: [lints, linter, lint, analysis, analyzer] environment: - sdk: ">=3.5.0 <4.0.0" + sdk: ">=3.9.0 <4.0.0" dependencies: analyzer: ^10.0.1 diff --git a/test/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule_test.dart b/test/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule_test.dart new file mode 100644 index 00000000..a2260724 --- /dev/null +++ b/test/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule_test.dart @@ -0,0 +1,148 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidUnrelatedTypeAssertionsRuleTest); + }); +} + +@reflectiveTest +class AvoidUnrelatedTypeAssertionsRuleTest extends AnalysisRuleTest { + @override + void setUp() { + rule = AvoidUnrelatedTypeAssertionsRule(); + super.setUp(); + } + + @override + String get analysisRule => rule.name; + + void test_reports_unrelated_string_is_int() async { + await assertDiagnostics( + r''' +void fun() { + final testString = ''; + + final result = testString is int; +} +''', + [ + lint(56, 17, messageContainsAll: ['false']), + ], + ); + } + + void test_reports_unrelated_int_list_is_string_list() async { + await assertDiagnostics( + r''' +void fun() { + final testList = [1, 2, 3]; + + final result = testList is List; +} +''', + [ + lint(61, 24, messageContainsAll: ['false']), + ], + ); + } + + void test_reports_unrelated_string_map_is_double_map() async { + await assertDiagnostics( + r''' +void fun() { + final testMap = {'A': 'B'}; + + final result = testMap['A'] is double; +} +''', + [ + lint(61, 22, messageContainsAll: ['false']), + ], + ); + } + + void test_reports_unrelated_class_is_another_class() async { + await assertDiagnostics( + r''' +class Foo {} + +class Bar {} + +void fun() { + final Foo foo = Foo(); + + final result = foo is Bar; +} +''', + [ + lint(84, 10, messageContainsAll: ['false']), + ], + ); + } + + void test_reports_unrelated_child_class_is_another_class() async { + await assertDiagnostics( + r''' +class Foo {} + +class Bar {} + +class ChildFoo extends Foo {} + +void fun() { + final childFoo = ChildFoo(); + + final result = childFoo is Bar; +} +''', + [ + lint(121, 15, messageContainsAll: ['false']), + ], + ); + } + + void test_reports_unrelated_is_condition() async { + await assertDiagnostics( + r''' +class _A {} + +class _B extends _A {} + +class _C {} + +void lint() { + final _A a = _B(); + + if (a is _C) return; +} +''', + [ + lint(92, 7, messageContainsAll: ['false']), + ], + ); + } + + void test_reports_unrelated_is_not_condition() async { + await assertDiagnostics( + r''' +class _A {} + +class _B extends _A {} + +class _C {} + +void lint() { + final _A a = _B(); + + if (a is! _C) return; +} +''', + [ + lint(92, 8, messageContainsAll: ['true']), + ], + ); + } +}