From 2abe6390af074362f807f0e1fc93867875c8882f Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Mon, 15 Jun 2026 21:30:29 +0300 Subject: [PATCH 1/7] refactor: migrate avoid_unnecessary_type_casts --- lib/main.dart | 9 ++ .../avoid_unnecessary_type_casts_rule.dart | 54 ++++----- .../avoid_unnecessary_type_casts_fix.dart | 69 +++++++----- .../avoid_unnecessary_type_casts_visitor.dart | 13 ++- .../avoid_unnecessary_type_casts_test.dart | 35 ------ ...void_unnecessary_type_casts_rule_test.dart | 106 ++++++++++++++++++ 6 files changed, 181 insertions(+), 105 deletions(-) delete mode 100644 lint_test/avoid_unnecessary_type_casts_test.dart create mode 100644 test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart diff --git a/lib/main.dart b/lib/main.dart index 708b9fce..bb97ee5d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,8 @@ import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_as import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart'; import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart'; @@ -35,8 +37,11 @@ class SolidLintsPlugin extends Plugin { final avoidUnnecessaryTypeAssertionsRule = AvoidUnnecessaryTypeAssertionsRule(); final doubleLiteralFormatRule = DoubleLiteralFormatRule(); + final avoidUnnecessaryTypeCastsRule = AvoidUnnecessaryTypeCastsRule(); + final lintRules = [ AvoidFinalWithGetterRule(), + avoidUnnecessaryTypeCastsRule, AvoidGlobalStateRule(), AvoidNonNullAssertionRule(), avoidUnnecessaryTypeAssertionsRule, @@ -67,5 +72,9 @@ class SolidLintsPlugin extends Plugin { avoidUnnecessaryTypeAssertionsRule.diagnosticCode, AvoidUnnecessaryTypeAssertionsFix.new, ); + registry.registerFixForRule( + avoidUnnecessaryTypeCastsRule.diagnosticCode, + AvoidUnnecessaryTypeCastsFix.new, + ); } } diff --git a/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart b/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart index 7db9ff71..2fba7fb9 100644 --- a/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart +++ b/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart @@ -1,14 +1,9 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/diagnostic/diagnostic.dart'; -import 'package:analyzer/error/listener.dart' as error_listener; -import 'package:analyzer/source/source_range.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_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; -part 'fixes/avoid_unnecessary_type_casts_fix.dart'; - /// An `avoid_unnecessary_type_casts` rule which /// warns about unnecessary usage of `as` operator class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { @@ -16,36 +11,27 @@ class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { /// the error whether we use bad formatted double literals. static const lintName = 'avoid_unnecessary_type_casts'; - AvoidUnnecessaryTypeCastsRule._(super.config); + static const LintCode _code = LintCode( + lintName, + "Avoid unnecessary usage of as operator.", + ); - /// Creates a new instance of [AvoidUnnecessaryTypeCastsRule] - /// based on the lint configuration. - factory AvoidUnnecessaryTypeCastsRule.createRule(CustomLintConfigs configs) { - final rule = RuleConfig( - configs: configs, - name: lintName, - problemMessage: (_) => "Avoid unnecessary usage of as operator.", - ); + @override + LintCode get diagnosticCode => _code; - return AvoidUnnecessaryTypeCastsRule._(rule); - } + /// Creates a new instance of [AvoidUnnecessaryTypeCastsRule] + AvoidUnnecessaryTypeCastsRule() + : super( + name: lintName, + description: _code.problemMessage, + ); @override - void run( - CustomLintResolver resolver, - error_listener.DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addAsExpression((node) { - final visitor = AvoidUnnecessaryTypeCastsVisitor(); - visitor.visitAsExpression(node); - - for (final element in visitor.expressions.entries) { - reporter.atNode(element.key, code); - } - }); + final visitor = AvoidUnnecessaryTypeCastsVisitor(this); + registry.addAsExpression(this, visitor); } - - @override - List getFixes() => [_UnnecessaryTypeCastsFix()]; } diff --git a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart index d03b6fcc..84ed2cf2 100644 --- a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart +++ b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart @@ -1,38 +1,47 @@ -part of '../avoid_unnecessary_type_casts_rule.dart'; +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; +import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/source/source_range.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; /// A Quick fix for `avoid_unnecessary_type_casts` rule /// Suggests to remove unnecessary assertions -class _UnnecessaryTypeCastsFix extends DartFix { +class AvoidUnnecessaryTypeCastsFix extends ParsedCorrectionProducer { + static const _avoidUnnecessaryTypeCastsKind = FixKind( + 'solid_lints.fix.${AvoidUnnecessaryTypeCastsRule.lintName}', + DartFixKindPriority.standard, + "Remove unnecessary type cast", + ); + @override - void run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - Diagnostic analysisError, - List others, - ) { - context.registry.addAsExpression((node) { - if (analysisError.sourceRange.intersects(node.sourceRange)) { - _addDeletion(reporter, 'as', node, node.asOperator.offset); - } - }); - } + FixKind get fixKind => _avoidUnnecessaryTypeCastsKind; + + @override + FixKind get multiFixKind => const FixKind( + 'solid_lints.fix.multi.${AvoidUnnecessaryTypeCastsRule.lintName}', + DartFixKindPriority.standard, + "Remove unnecessary type cast across files", + ); + + /// Creates a new instance of [AvoidUnnecessaryTypeCastsFix] + AvoidUnnecessaryTypeCastsFix({required super.context}); + + @override + CorrectionApplicability get applicability => + CorrectionApplicability.automatically; + + @override + Future compute(ChangeBuilder builder) async { + final asExpressionNode = node.thisOrAncestorOfType(); + if (asExpressionNode == null) return; + + await builder.addDartFileEdit(file, (builder) { + final operatorOffset = asExpressionNode.asOperator.offset; + final targetNameLength = operatorOffset - node.offset; + final removedPartLength = node.length - targetNameLength; - void _addDeletion( - ChangeReporter reporter, - String itemToDelete, - Expression node, - int operatorOffset, - ) { - final targetNameLength = operatorOffset - node.offset; - final removedPartLength = node.length - targetNameLength; - - final changeBuilder = reporter.createChangeBuilder( - message: "Remove unnecessary '$itemToDelete'", - priority: 1, - ); - - changeBuilder.addDartFileEdit((builder) { builder.addDeletion(SourceRange(operatorOffset, removedPartLength)); }); } diff --git a/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart b/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart index a7b39e87..ed6e0b51 100644 --- a/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart @@ -23,15 +23,16 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; import 'package:solid_lints/src/utils/typecast_utils.dart'; /// AST Visitor which finds all as expressions and checks if they are /// necessary class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor { - final _expressions = {}; + final AvoidUnnecessaryTypeCastsRule _rule; - /// All as expressions - Map get expressions => _expressions; + /// Creates a new instance of [AvoidUnnecessaryTypeCastsVisitor] + AvoidUnnecessaryTypeCastsVisitor(this._rule); @override void visitAsExpression(AsExpression node) { @@ -49,8 +50,8 @@ class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor { target: castedType, ); - if (typeCast.isUnnecessaryTypeCheck) { - _expressions[node] = 'as'; - } + if (!typeCast.isUnnecessaryTypeCheck) return; + + _rule.reportAtNode(node); } } diff --git a/lint_test/avoid_unnecessary_type_casts_test.dart b/lint_test/avoid_unnecessary_type_casts_test.dart deleted file mode 100644 index dffa6903..00000000 --- a/lint_test/avoid_unnecessary_type_casts_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -// ignore_for_file: prefer_const_declarations -// ignore_for_file: unnecessary_nullable_for_final_variable_declarations -// ignore_for_file: unnecessary_cast -// ignore_for_file: unused_local_variable - -/// Check the `avoid_unnecessary_type_casts` rule - -void fun() { - final testList = [1.0, 2.0, 3.0]; - - // to check quick-fix => testList - // expect_lint: avoid_unnecessary_type_casts - final result = testList as List; - - final double? nullableD = 2.0; - // casting `Type? is Type` is allowed - final castedD = nullableD as double; - - final testMap = {'A': 'B'}; - - // expect_lint: avoid_unnecessary_type_casts - final castedMapValue = testMap['A'] as String?; - - // casting `Type? is Type` is allowed - final castedNotNullMapValue = testMap['A'] as String; - - final testString = 'String'; - // expect_lint: avoid_unnecessary_type_casts - _testFun(testString as String); -} - -void _testFun(String a) { - // expect_lint: avoid_unnecessary_type_casts - final result = (a as String).length; -} diff --git a/test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart b/test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart new file mode 100644 index 00000000..d2e8cee2 --- /dev/null +++ b/test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart @@ -0,0 +1,106 @@ +import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidUnnecessaryTypeCastsRuleTest); + }); +} + +@reflectiveTest +class AvoidUnnecessaryTypeCastsRuleTest extends AnalysisRuleTest { + @override + void setUp() { + rule = AvoidUnnecessaryTypeCastsRule(); + super.setUp(); + } + + @override + String get analysisRule => rule.name; + + void test_reports_on_list_cast() async { + await assertDiagnostics( + r''' +void fun() { + final testList = [1.0, 2.0, 3.0]; + + final result = testList as List; +} +''', + [ + error(diag.unnecessaryCast, 67, 24), + lint(67, 24), + ], + ); + } + + void test_reports_on_map_value_cast_to_nullable() async { + await assertDiagnostics( + r''' +void fun() { + final testMap = {'A': 'B'}; + + final castedMapValue = testMap['A'] as String?; +} +''', + [ + error(diag.unnecessaryCast, 69, 23), + lint(69, 23), + ], + ); + } + + void test_reports_on_argument_cast() async { + await assertDiagnostics( + r''' +void fun() { + final testString = 'String'; + + _testFun(testString as String); +} + +void _testFun(String a) {} +''', + [ + error(diag.unnecessaryCast, 56, 20), + lint(56, 20), + ], + ); + } + + void test_reports_on_parenthesized_cast() async { + await assertDiagnostics( + r''' +void fun(String a) { + final result = (a as String).length; +} +''', + [ + error(diag.unnecessaryCast, 39, 11), + lint(39, 11), + ], + ); + } + + void test_does_not_report_on_nullable_to_non_nullable_cast() async { + await assertNoDiagnostics(r''' +void fun() { + final double? nullableD = 2.0; + + final castedD = nullableD as double; +} +'''); + } + + void test_does_not_report_on_map_value_cast_to_non_nullable() async { + await assertNoDiagnostics(r''' +void fun() { + final testMap = {'A': 'B'}; + + final castedNotNullMapValue = testMap['A'] as String; +} +'''); + } +} From ad2b5ffb8321c21b1c0808a1f7446112faaa711a Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Mon, 15 Jun 2026 21:33:44 +0300 Subject: [PATCH 2/7] fix: incorrect node --- .../fixes/avoid_unnecessary_type_casts_fix.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart index 84ed2cf2..198a0992 100644 --- a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart +++ b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart @@ -39,8 +39,8 @@ class AvoidUnnecessaryTypeCastsFix extends ParsedCorrectionProducer { await builder.addDartFileEdit(file, (builder) { final operatorOffset = asExpressionNode.asOperator.offset; - final targetNameLength = operatorOffset - node.offset; - final removedPartLength = node.length - targetNameLength; + final targetNameLength = operatorOffset - asExpressionNode.offset; + final removedPartLength = asExpressionNode.length - targetNameLength; builder.addDeletion(SourceRange(operatorOffset, removedPartLength)); }); From f93e070d082bc2875d12ac6512fc50b5829eb8af Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Mon, 15 Jun 2026 21:38:06 +0300 Subject: [PATCH 3/7] docs: improve code documentation --- .../avoid_unnecessary_type_casts_rule.dart | 54 ++++++++++++++++--- .../avoid_unnecessary_type_casts_fix.dart | 26 +++++++-- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart b/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart index 2fba7fb9..90695c95 100644 --- a/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart +++ b/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart @@ -5,25 +5,67 @@ import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/visitors/avoi import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// An `avoid_unnecessary_type_casts` rule which -/// warns about unnecessary usage of `as` operator +/// warns about unnecessary usage of the `as` operator. +/// +/// A cast is unnecessary when the static type of the expression is already +/// compatible with the target type. Casting from a nullable type to a +/// non-nullable type is allowed, because it can change nullability. +/// +/// {@template solid_lints.avoid_unnecessary_type_casts.example} +/// ### Example +/// +/// #### BAD: +/// +/// ```dart +/// final testList = [1.0, 2.0, 3.0]; +/// final result = testList as List; // LINT +/// +/// final testMap = {'A': 'B'}; +/// final castedMapValue = testMap['A'] as String?; // LINT +/// +/// final testString = 'String'; +/// _testFun(testString as String); // LINT +/// +/// void fun(String a) { +/// final result = (a as String).length; // LINT +/// } +/// ``` +/// +/// #### GOOD: +/// +/// ```dart +/// final double? nullableD = 2.0; +/// // casting `Type? as Type` is allowed +/// final castedD = nullableD as double; +/// +/// final testMap = {'A': 'B'}; +/// final castedNotNullMapValue = testMap['A'] as String; +/// ``` +/// {@endtemplate} class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { - /// This lint rule represents - /// the error whether we use bad formatted double literals. + /// The name of this lint rule. static const lintName = 'avoid_unnecessary_type_casts'; + /// Reported when the `as` operator is used on an expression whose static type + /// is already compatible with the cast target. + /// + /// {@macro solid_lints.avoid_unnecessary_type_casts.example} static const LintCode _code = LintCode( lintName, - "Avoid unnecessary usage of as operator.", + 'Avoid unnecessary usage of as operator.', + correctionMessage: 'Remove the unnecessary type cast.', ); @override LintCode get diagnosticCode => _code; - /// Creates a new instance of [AvoidUnnecessaryTypeCastsRule] + /// Creates a new instance of [AvoidUnnecessaryTypeCastsRule]. AvoidUnnecessaryTypeCastsRule() : super( name: lintName, - description: _code.problemMessage, + description: + 'Warns about unnecessary usage of the `as` operator when the ' + 'static type already satisfies the cast.', ); @override diff --git a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart index 198a0992..5cea5843 100644 --- a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart +++ b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart @@ -6,13 +6,29 @@ import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dar import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; -/// A Quick fix for `avoid_unnecessary_type_casts` rule -/// Suggests to remove unnecessary assertions +/// A quick fix for [AvoidUnnecessaryTypeCastsRule]. +/// +/// Removes the `as Type` suffix from an unnecessary cast expression while +/// keeping the original expression unchanged. +/// +/// ### Example +/// +/// Given: +/// +/// ```dart +/// final result = testList as List; +/// ``` +/// +/// The fix produces: +/// +/// ```dart +/// final result = testList; +/// ``` class AvoidUnnecessaryTypeCastsFix extends ParsedCorrectionProducer { static const _avoidUnnecessaryTypeCastsKind = FixKind( 'solid_lints.fix.${AvoidUnnecessaryTypeCastsRule.lintName}', DartFixKindPriority.standard, - "Remove unnecessary type cast", + 'Remove unnecessary type cast', ); @override @@ -22,10 +38,10 @@ class AvoidUnnecessaryTypeCastsFix extends ParsedCorrectionProducer { FixKind get multiFixKind => const FixKind( 'solid_lints.fix.multi.${AvoidUnnecessaryTypeCastsRule.lintName}', DartFixKindPriority.standard, - "Remove unnecessary type cast across files", + 'Remove unnecessary type cast across files', ); - /// Creates a new instance of [AvoidUnnecessaryTypeCastsFix] + /// Creates a new instance of [AvoidUnnecessaryTypeCastsFix]. AvoidUnnecessaryTypeCastsFix({required super.context}); @override From ad8eba2af5adff67ee333df3fd053c05efd53386 Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Fri, 19 Jun 2026 12:46:31 +0300 Subject: [PATCH 4/7] remove avoid_unnecessary_type_casts use dart linter's `unnecessary_cast` instead --- lib/main.dart | 8 -- .../avoid_unnecessary_type_casts_rule.dart | 79 ------------- .../avoid_unnecessary_type_casts_fix.dart | 64 ----------- .../avoid_unnecessary_type_casts_visitor.dart | 57 ---------- ...void_unnecessary_type_casts_rule_test.dart | 106 ------------------ 5 files changed, 314 deletions(-) delete mode 100644 lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart delete mode 100644 lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart delete mode 100644 lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart delete mode 100644 test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart diff --git a/lib/main.dart b/lib/main.dart index bb97ee5d..e6473d17 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,8 +9,6 @@ import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_as import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart'; -import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; -import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart'; import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart'; @@ -37,11 +35,9 @@ class SolidLintsPlugin extends Plugin { final avoidUnnecessaryTypeAssertionsRule = AvoidUnnecessaryTypeAssertionsRule(); final doubleLiteralFormatRule = DoubleLiteralFormatRule(); - final avoidUnnecessaryTypeCastsRule = AvoidUnnecessaryTypeCastsRule(); final lintRules = [ AvoidFinalWithGetterRule(), - avoidUnnecessaryTypeCastsRule, AvoidGlobalStateRule(), AvoidNonNullAssertionRule(), avoidUnnecessaryTypeAssertionsRule, @@ -72,9 +68,5 @@ class SolidLintsPlugin extends Plugin { avoidUnnecessaryTypeAssertionsRule.diagnosticCode, AvoidUnnecessaryTypeAssertionsFix.new, ); - registry.registerFixForRule( - avoidUnnecessaryTypeCastsRule.diagnosticCode, - AvoidUnnecessaryTypeCastsFix.new, - ); } } diff --git a/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart b/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart deleted file mode 100644 index 90695c95..00000000 --- a/lib/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart +++ /dev/null @@ -1,79 +0,0 @@ -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_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; - -/// An `avoid_unnecessary_type_casts` rule which -/// warns about unnecessary usage of the `as` operator. -/// -/// A cast is unnecessary when the static type of the expression is already -/// compatible with the target type. Casting from a nullable type to a -/// non-nullable type is allowed, because it can change nullability. -/// -/// {@template solid_lints.avoid_unnecessary_type_casts.example} -/// ### Example -/// -/// #### BAD: -/// -/// ```dart -/// final testList = [1.0, 2.0, 3.0]; -/// final result = testList as List; // LINT -/// -/// final testMap = {'A': 'B'}; -/// final castedMapValue = testMap['A'] as String?; // LINT -/// -/// final testString = 'String'; -/// _testFun(testString as String); // LINT -/// -/// void fun(String a) { -/// final result = (a as String).length; // LINT -/// } -/// ``` -/// -/// #### GOOD: -/// -/// ```dart -/// final double? nullableD = 2.0; -/// // casting `Type? as Type` is allowed -/// final castedD = nullableD as double; -/// -/// final testMap = {'A': 'B'}; -/// final castedNotNullMapValue = testMap['A'] as String; -/// ``` -/// {@endtemplate} -class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { - /// The name of this lint rule. - static const lintName = 'avoid_unnecessary_type_casts'; - - /// Reported when the `as` operator is used on an expression whose static type - /// is already compatible with the cast target. - /// - /// {@macro solid_lints.avoid_unnecessary_type_casts.example} - static const LintCode _code = LintCode( - lintName, - 'Avoid unnecessary usage of as operator.', - correctionMessage: 'Remove the unnecessary type cast.', - ); - - @override - LintCode get diagnosticCode => _code; - - /// Creates a new instance of [AvoidUnnecessaryTypeCastsRule]. - AvoidUnnecessaryTypeCastsRule() - : super( - name: lintName, - description: - 'Warns about unnecessary usage of the `as` operator when the ' - 'static type already satisfies the cast.', - ); - - @override - void registerNodeProcessors( - RuleVisitorRegistry registry, - RuleContext context, - ) { - final visitor = AvoidUnnecessaryTypeCastsVisitor(this); - registry.addAsExpression(this, visitor); - } -} diff --git a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart b/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart deleted file mode 100644 index 5cea5843..00000000 --- a/lib/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_fix.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; -import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/source/source_range.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; -import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; - -/// A quick fix for [AvoidUnnecessaryTypeCastsRule]. -/// -/// Removes the `as Type` suffix from an unnecessary cast expression while -/// keeping the original expression unchanged. -/// -/// ### Example -/// -/// Given: -/// -/// ```dart -/// final result = testList as List; -/// ``` -/// -/// The fix produces: -/// -/// ```dart -/// final result = testList; -/// ``` -class AvoidUnnecessaryTypeCastsFix extends ParsedCorrectionProducer { - static const _avoidUnnecessaryTypeCastsKind = FixKind( - 'solid_lints.fix.${AvoidUnnecessaryTypeCastsRule.lintName}', - DartFixKindPriority.standard, - 'Remove unnecessary type cast', - ); - - @override - FixKind get fixKind => _avoidUnnecessaryTypeCastsKind; - - @override - FixKind get multiFixKind => const FixKind( - 'solid_lints.fix.multi.${AvoidUnnecessaryTypeCastsRule.lintName}', - DartFixKindPriority.standard, - 'Remove unnecessary type cast across files', - ); - - /// Creates a new instance of [AvoidUnnecessaryTypeCastsFix]. - AvoidUnnecessaryTypeCastsFix({required super.context}); - - @override - CorrectionApplicability get applicability => - CorrectionApplicability.automatically; - - @override - Future compute(ChangeBuilder builder) async { - final asExpressionNode = node.thisOrAncestorOfType(); - if (asExpressionNode == null) return; - - await builder.addDartFileEdit(file, (builder) { - final operatorOffset = asExpressionNode.asOperator.offset; - final targetNameLength = operatorOffset - asExpressionNode.offset; - final removedPartLength = asExpressionNode.length - targetNameLength; - - builder.addDeletion(SourceRange(operatorOffset, removedPartLength)); - }); - } -} diff --git a/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart b/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart deleted file mode 100644 index ed6e0b51..00000000 --- a/lib/src/lints/avoid_unnecessary_type_casts/visitors/avoid_unnecessary_type_casts_visitor.dart +++ /dev/null @@ -1,57 +0,0 @@ -// MIT License -// -// Copyright (c) 2020-2021 Dart Code Checker team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; -import 'package:solid_lints/src/utils/typecast_utils.dart'; - -/// AST Visitor which finds all as expressions and checks if they are -/// necessary -class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor { - final AvoidUnnecessaryTypeCastsRule _rule; - - /// Creates a new instance of [AvoidUnnecessaryTypeCastsVisitor] - AvoidUnnecessaryTypeCastsVisitor(this._rule); - - @override - void visitAsExpression(AsExpression node) { - super.visitAsExpression(node); - - final objectType = node.expression.staticType; - final castedType = node.type.type; - - if (objectType == null || castedType == null) { - return; - } - - final typeCast = TypeCast( - source: objectType, - target: castedType, - ); - - if (!typeCast.isUnnecessaryTypeCheck) return; - - _rule.reportAtNode(node); - } -} diff --git a/test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart b/test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart deleted file mode 100644 index d2e8cee2..00000000 --- a/test/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule_test.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; -import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; -import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; -import 'package:test_reflective_loader/test_reflective_loader.dart'; - -void main() { - defineReflectiveSuite(() { - defineReflectiveTests(AvoidUnnecessaryTypeCastsRuleTest); - }); -} - -@reflectiveTest -class AvoidUnnecessaryTypeCastsRuleTest extends AnalysisRuleTest { - @override - void setUp() { - rule = AvoidUnnecessaryTypeCastsRule(); - super.setUp(); - } - - @override - String get analysisRule => rule.name; - - void test_reports_on_list_cast() async { - await assertDiagnostics( - r''' -void fun() { - final testList = [1.0, 2.0, 3.0]; - - final result = testList as List; -} -''', - [ - error(diag.unnecessaryCast, 67, 24), - lint(67, 24), - ], - ); - } - - void test_reports_on_map_value_cast_to_nullable() async { - await assertDiagnostics( - r''' -void fun() { - final testMap = {'A': 'B'}; - - final castedMapValue = testMap['A'] as String?; -} -''', - [ - error(diag.unnecessaryCast, 69, 23), - lint(69, 23), - ], - ); - } - - void test_reports_on_argument_cast() async { - await assertDiagnostics( - r''' -void fun() { - final testString = 'String'; - - _testFun(testString as String); -} - -void _testFun(String a) {} -''', - [ - error(diag.unnecessaryCast, 56, 20), - lint(56, 20), - ], - ); - } - - void test_reports_on_parenthesized_cast() async { - await assertDiagnostics( - r''' -void fun(String a) { - final result = (a as String).length; -} -''', - [ - error(diag.unnecessaryCast, 39, 11), - lint(39, 11), - ], - ); - } - - void test_does_not_report_on_nullable_to_non_nullable_cast() async { - await assertNoDiagnostics(r''' -void fun() { - final double? nullableD = 2.0; - - final castedD = nullableD as double; -} -'''); - } - - void test_does_not_report_on_map_value_cast_to_non_nullable() async { - await assertNoDiagnostics(r''' -void fun() { - final testMap = {'A': 'B'}; - - final castedNotNullMapValue = testMap['A'] as String; -} -'''); - } -} From 249f81af5a95de4041d14a28f659a05755dcf337 Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Fri, 19 Jun 2026 12:48:01 +0300 Subject: [PATCH 5/7] docs: update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6c4c5ae..b569fb4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.0 + +- feat!: migrate to analyzer_server_plugin +- BREAKING CHANGE: removed `avoid_unnecessary_type_casts` rule, use dart linter's `unnecessary_cast` instead + ## 0.3.3 - Fix pub.dev analysis issue From 433edd4cf5c3818ea9dfa049fc2acc9c036aa44d Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Fri, 19 Jun 2026 16:42:03 +0300 Subject: [PATCH 6/7] replace avoid_unnecessary_type_casts rule with dart analyzer's unnecessary_cast docs: update changelog --- CHANGELOG.md | 2 +- lib/analysis_options.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b569fb4f..20743373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 1.0.0 - feat!: migrate to analyzer_server_plugin -- BREAKING CHANGE: removed `avoid_unnecessary_type_casts` rule, use dart linter's `unnecessary_cast` instead +- refactor: replace `avoid_unnecessary_type_casts` rule with dart analyzer's `unnecessary_cast` ## 0.3.3 diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index 59b28dd2..64fe33d4 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -33,6 +33,7 @@ analyzer: missing_required_param: error missing_return: error parameter_assignments: error + unnecessary_cast: warning custom_lint: rules: From 9c6cd1104bfde07db8f9727d63167cb3c5abc589 Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Fri, 19 Jun 2026 17:00:31 +0300 Subject: [PATCH 7/7] remove no deleted rule --- lib/analysis_options.yaml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index 64fe33d4..21857abc 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -3,21 +3,21 @@ analyzer: - custom_lint exclude: # General generated files - - '**/*.g.dart' - - '**/*.gr.dart' + - "**/*.g.dart" + - "**/*.gr.dart" # Flutter - - 'lib/generated_plugin_registrant.dart' + - "lib/generated_plugin_registrant.dart" # mockito - - '*.mocks.dart' - - '**/*.mocks.dart' + - "*.mocks.dart" + - "**/*.mocks.dart" # freezed - - '**/*.freezed.dart' + - "**/*.freezed.dart" # protobuf - - '**/*.pb.dart' + - "**/*.pb.dart" # test_coverage - test/.test_coverage.dart @@ -49,7 +49,6 @@ custom_lint: - avoid_unnecessary_return_variable - avoid_unnecessary_setstate - avoid_unnecessary_type_assertions - - avoid_unnecessary_type_casts - avoid_unrelated_type_assertions - avoid_unused_parameters - avoid_debug_print_in_release @@ -88,7 +87,7 @@ custom_lint: - number_of_parameters: max_parameters: 7 exclude: - - method_name: copyWith + - method_name: copyWith - prefer_conditional_expressions: ignore_nested: true