From 9ea9eb1f680b7f89034f610ef91b8678716cedf7 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 12:19:24 +0300 Subject: [PATCH 01/10] Add avoid_unnecessary_type_casts_rule --- .../avoid_unnecessary_type_casts_rule.dart | 44 +++++++ .../avoid_unnecessary_type_casts_visitor.dart | 111 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart create mode 100644 lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart new file mode 100644 index 00000000..4f6b86b2 --- /dev/null +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart @@ -0,0 +1,44 @@ +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:solid_lints/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart'; +import 'package:solid_lints/models/rule_config.dart'; +import 'package:solid_lints/models/solid_lint_rule.dart'; + +/// A `avoid-unnecessary-type-casts` rule which +/// warns about unnecessary usage of `is` and `whereType` operators +class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { + /// The [LintCode] of this lint rule that represents + /// the error whether we use bad formatted double literals. + static const lintName = 'avoid-unnecessary-type-casts'; + + AvoidUnnecessaryTypeCastsRule._(super.config); + + /// 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.", + ); + + return AvoidUnnecessaryTypeCastsRule._(rule); + } + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + final visitor = AvoidUnnecessaryTypeCastsVisitor(); + + context.registry.addAsExpression((node) { + visitor.visitAsExpression(node); + + for (final element in visitor.expressions.entries) { + reporter.reportErrorForNode(code, element.key); + } + }); + } +} diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart new file mode 100644 index 00000000..78026d8f --- /dev/null +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart @@ -0,0 +1,111 @@ +// 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:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:solid_lints/utils/typecast_utils.dart'; + +/// AST Visitor which finds all as expressions and checks if they are +/// necessary +class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor { + final _expressions = {}; + + /// All as expressions + Map get expressions => _expressions; + + @override + void visitAsExpression(AsExpression node) { + super.visitAsExpression(node); + + final objectType = node.expression.staticType; + final castedType = node.type.type; + if (_isUselessTypeCheck(objectType, castedType)) { + _expressions[node] = 'as'; + } + } + + bool _isUselessTypeCheck(DartType? objectType, DartType? castedType) { + if (objectType == null || castedType == null) { + return false; + } + + if (_checkNullableCompatibility(objectType, castedType)) { + return false; + } + + final objectCastedType = + _foundCastedTypeInObjectTypeHierarchy(objectType, castedType); + if (objectCastedType == null) { + return false; + } + + if (!_checkGenerics(objectCastedType, castedType)) { + return false; + } + + return true; + } + + bool _checkNullableCompatibility(DartType objectType, DartType castedType) { + final isObjectTypeNullable = + objectType.nullabilitySuffix != NullabilitySuffix.none; + final isCastedTypeNullable = + castedType.nullabilitySuffix != NullabilitySuffix.none; + + // Only one case `Type? is Type` always valid assertion case. + return isObjectTypeNullable && !isCastedTypeNullable; + } + + DartType? _foundCastedTypeInObjectTypeHierarchy( + DartType objectType, + DartType castedType, + ) { + final typeCast = TypeCast(source: objectType, target: castedType); + + return typeCast.castTypeInHierarchy(); + } + + bool _checkGenerics(DartType objectType, DartType castedType) { + if (objectType is! ParameterizedType || castedType is! ParameterizedType) { + return false; + } + + final length = objectType.typeArguments.length; + if (length != castedType.typeArguments.length) { + return false; + } + + for (var argumentIndex = 0; argumentIndex < length; argumentIndex++) { + if (!_isUselessTypeCheck( + objectType.typeArguments[argumentIndex], + castedType.typeArguments[argumentIndex], + )) { + return false; + } + } + + return true; + } +} From 5716a4d19548d2d8cb138c80c3ed3fb57cb232d7 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 12:57:28 +0300 Subject: [PATCH 02/10] Refactor types_utils, extract common methods --- ...void_unnecessary_type_assertions_rule.dart | 81 ++++--------------- .../avoid_unnecessary_type_casts_visitor.dart | 69 ++-------------- lib/utils/typecast_utils.dart | 62 ++++++++++++++ 3 files changed, 83 insertions(+), 129 deletions(-) diff --git a/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart b/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart index c6774d42..00614d89 100644 --- a/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart +++ b/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart @@ -3,7 +3,6 @@ import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/source/source_range.dart'; -import 'package:collection/collection.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:solid_lints/models/rule_config.dart'; import 'package:solid_lints/models/solid_lint_rule.dart'; @@ -75,15 +74,20 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule { final objectType = node.expression.staticType; final castedType = node.type.type; + if (objectType == null || castedType == null) { + return false; + } + + final typeCast = TypeCast(source: objectType, target: castedType); + if (node.notOperator != null && - objectType != null && objectType is! TypeParameterType && objectType is! DynamicType && !objectType.isDartCoreObject && - _isUnnecessaryTypeCheck(objectType, castedType, isReversed: true)) { + typeCast.isUnnecessaryTypeCheck(isReversed: true)) { return true; } else { - return _isUnnecessaryTypeCheck(objectType, castedType); + return typeCast.isUnnecessaryTypeCheck(); } } @@ -98,74 +102,17 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule { when targetType is ParameterizedType && isIterable(realTargetType) && arguments.isNotEmpty) { - return _isUnnecessaryTypeCheck( - targetType.typeArguments.first, - arguments.first.type, - ); - } else { - return false; - } - } - - /// Checks that type checking is unnecessary - /// [objectType] is the source expression type - /// [castedType] is the type against which the expression type is compared - /// [isReversed] true for opposite comparison, i.e 'is!' - /// and false for positive comparison, i.e. 'is' or 'whereType' - bool _isUnnecessaryTypeCheck( - DartType? objectType, - DartType? castedType, { - bool isReversed = false, - }) { - if (objectType == null || castedType == null) { - return false; - } - - final typeCast = TypeCast( - source: objectType, - target: castedType, - ); - if (_isNullableCompatibility(typeCast)) { - return false; - } - - final objectCastedType = typeCast.castTypeInHierarchy(); - - if (objectCastedType == null) { - return isReversed; - } + final objectType = targetType.typeArguments.first; + final castedType = arguments.first.type; - final objectTypeCast = TypeCast( - source: objectCastedType, - target: castedType, - ); - if (!_areGenericsWithSameTypeArgs(objectTypeCast)) { - return false; - } - - return !isReversed; - } - - bool _isNullableCompatibility(TypeCast typeCast) { - final isObjectTypeNullable = isNullableType(typeCast.source); - final isCastedTypeNullable = isNullableType(typeCast.target); - - // Only one case `Type? is Type` always valid assertion case. - return isObjectTypeNullable && !isCastedTypeNullable; - } - - bool _areGenericsWithSameTypeArgs(TypeCast typeCast) { - if (typeCast - case TypeCast(source: final objectType, target: final castedType) - when objectType is ParameterizedType && - castedType is ParameterizedType) { - if (objectType.typeArguments.length != castedType.typeArguments.length) { + if (castedType == null) { return false; } - return IterableZip([objectType.typeArguments, castedType.typeArguments]) - .every((e) => _isUnnecessaryTypeCheck(e[0], e[1])); + final typeCast = TypeCast(source: objectType, target: castedType); + + return typeCast.isUnnecessaryTypeCheck(); } else { return false; } diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart index 78026d8f..0facb95a 100644 --- a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart @@ -23,8 +23,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:analyzer/dart/element/nullability_suffix.dart'; -import 'package:analyzer/dart/element/type.dart'; import 'package:solid_lints/utils/typecast_utils.dart'; /// AST Visitor which finds all as expressions and checks if they are @@ -41,71 +39,18 @@ class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor { final objectType = node.expression.staticType; final castedType = node.type.type; - if (_isUselessTypeCheck(objectType, castedType)) { - _expressions[node] = 'as'; - } - } - bool _isUselessTypeCheck(DartType? objectType, DartType? castedType) { if (objectType == null || castedType == null) { - return false; - } - - if (_checkNullableCompatibility(objectType, castedType)) { - return false; - } - - final objectCastedType = - _foundCastedTypeInObjectTypeHierarchy(objectType, castedType); - if (objectCastedType == null) { - return false; - } - - if (!_checkGenerics(objectCastedType, castedType)) { - return false; - } - - return true; - } - - bool _checkNullableCompatibility(DartType objectType, DartType castedType) { - final isObjectTypeNullable = - objectType.nullabilitySuffix != NullabilitySuffix.none; - final isCastedTypeNullable = - castedType.nullabilitySuffix != NullabilitySuffix.none; - - // Only one case `Type? is Type` always valid assertion case. - return isObjectTypeNullable && !isCastedTypeNullable; - } - - DartType? _foundCastedTypeInObjectTypeHierarchy( - DartType objectType, - DartType castedType, - ) { - final typeCast = TypeCast(source: objectType, target: castedType); - - return typeCast.castTypeInHierarchy(); - } - - bool _checkGenerics(DartType objectType, DartType castedType) { - if (objectType is! ParameterizedType || castedType is! ParameterizedType) { - return false; + return; } - final length = objectType.typeArguments.length; - if (length != castedType.typeArguments.length) { - return false; - } + final typeCast = TypeCast( + source: objectType, + target: castedType, + ); - for (var argumentIndex = 0; argumentIndex < length; argumentIndex++) { - if (!_isUselessTypeCheck( - objectType.typeArguments[argumentIndex], - castedType.typeArguments[argumentIndex], - )) { - return false; - } + if (typeCast.isUnnecessaryTypeCheck()) { + _expressions[node] = 'as'; } - - return true; } } diff --git a/lib/utils/typecast_utils.dart b/lib/utils/typecast_utils.dart index d329fd17..ed7ed321 100644 --- a/lib/utils/typecast_utils.dart +++ b/lib/utils/typecast_utils.dart @@ -1,3 +1,4 @@ +import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; @@ -36,4 +37,65 @@ class TypeCast { return null; } + + /// Checks for nullable type casts + /// Only one case `Type? is Type` always valid assertion case. + bool isNullableCompatibility() { + final isObjectTypeNullable = + source.nullabilitySuffix != NullabilitySuffix.none; + final isCastedTypeNullable = + target.nullabilitySuffix != NullabilitySuffix.none; + + return isObjectTypeNullable && !isCastedTypeNullable; + } + + /// Checks that type checking is unnecessary + /// [source] is the source expression type + /// [target] is the type against which the expression type is compared + /// [isReversed] true for opposite comparison, i.e 'is!' + /// and false for positive comparison, i.e. 'is', 'as' or 'whereType' + bool isUnnecessaryTypeCheck({ + bool isReversed = false, + }) { + if (isNullableCompatibility()) { + return false; + } + + final objectCastedType = castTypeInHierarchy(); + + if (objectCastedType == null) { + return isReversed; + } + + final objectTypeCast = TypeCast( + source: objectCastedType, + target: target, + ); + + if (!objectTypeCast.areGenericsWithSameTypeArgs()) { + return false; + } + + return !isReversed; + } + + + /// Checks for type arguments and compares them + bool areGenericsWithSameTypeArgs() { + if (this + case TypeCast(source: final objectType, target: final castedType) + when objectType is ParameterizedType && castedType is ParameterizedType + ) { + if (objectType.typeArguments.length != castedType.typeArguments.length) { + return false; + } + + return IterableZip([objectType.typeArguments, castedType.typeArguments]) + .every( + (e) => TypeCast(source: e[0], target: e[1]).isUnnecessaryTypeCheck(), + ); + } else { + return false; + } + } } From e2c2d037272d4822fc5789024dc21ccc660b16a7 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 13:04:50 +0300 Subject: [PATCH 03/10] Add fix for avoid_unnecessary_type_casts_rule --- .../avoid_unnecessary_type_casts_fix.dart | 39 +++++++++++++++++++ .../avoid_unnecessary_type_casts_rule.dart | 10 ++++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart new file mode 100644 index 00000000..fc96cb1b --- /dev/null +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart @@ -0,0 +1,39 @@ +part of 'avoid_unnecessary_type_casts_rule.dart'; + +/// A Quick fix for `avoid-unnecessary-type-assertions` rule +/// Suggests to remove unnecessary assertions +class _UnnecessaryTypeCastsFix extends DartFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + context.registry.addIsExpression((node) { + if (analysisError.sourceRange.intersects(node.sourceRange)) { + _addDeletion(reporter, 'as', node, node.isOperator.offset); + } + }); + } + + 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/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart index 4f6b86b2..c70c03d5 100644 --- a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart @@ -1,11 +1,16 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; +import 'package:analyzer/source/source_range.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:solid_lints/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart'; import 'package:solid_lints/models/rule_config.dart'; import 'package:solid_lints/models/solid_lint_rule.dart'; +part 'avoid_unnecessary_type_casts_fix.dart'; + /// A `avoid-unnecessary-type-casts` rule which -/// warns about unnecessary usage of `is` and `whereType` operators +/// warns about unnecessary usage of `as` operator class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { /// The [LintCode] of this lint rule that represents /// the error whether we use bad formatted double literals. @@ -41,4 +46,7 @@ class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { } }); } + + @override + List getFixes() => [_UnnecessaryTypeCastsFix()]; } From 9961e0c54eb5555f634843afad1925beeffa0930 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 13:20:26 +0300 Subject: [PATCH 04/10] Add AvoidUnnecessaryTypeCastsRule.createRule to solid_lints.dart --- lib/solid_lints.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solid_lints.dart b/lib/solid_lints.dart index 41088533..5b437b10 100644 --- a/lib/solid_lints.dart +++ b/lib/solid_lints.dart @@ -7,6 +7,7 @@ import 'package:solid_lints/lints/avoid_non_null_assertion/avoid_non_null_assert import 'package:solid_lints/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; import 'package:solid_lints/lints/avoid_unnecessary_setstate/avoid_unnecessary_set_state_rule.dart'; import 'package:solid_lints/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; +import 'package:solid_lints/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart'; import 'package:solid_lints/lints/cyclomatic_complexity/cyclomatic_complexity_metric.dart'; import 'package:solid_lints/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/lints/function_lines_of_code/function_lines_of_code_metric.dart'; @@ -31,6 +32,7 @@ class _SolidLints extends PluginBase { DoubleLiteralFormatRule.createRule(configs), AvoidUnnecessaryTypeAssertions.createRule(configs), AvoidUnnecessarySetStateRule.createRule(configs), + AvoidUnnecessaryTypeCastsRule.createRule(configs), ]; // Return only enabled rules From b58e2963ce59ca3b51e8d76033fa57e1c36c2ba0 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 13:25:55 +0300 Subject: [PATCH 05/10] Fix 'quickFix' for avoid-unnecessary-type-casts --- .../avoid_unnecessary_type_casts_fix.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart index fc96cb1b..4c8c578c 100644 --- a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_fix.dart @@ -11,9 +11,9 @@ class _UnnecessaryTypeCastsFix extends DartFix { AnalysisError analysisError, List others, ) { - context.registry.addIsExpression((node) { + context.registry.addAsExpression((node) { if (analysisError.sourceRange.intersects(node.sourceRange)) { - _addDeletion(reporter, 'as', node, node.isOperator.offset); + _addDeletion(reporter, 'as', node, node.asOperator.offset); } }); } From c062e40c955fed04fcc1dc90de2fb46bc4e8bdc7 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 13:27:04 +0300 Subject: [PATCH 06/10] Add tests for avoid-unnecessary-type-casts --- lint_test/analysis_options.yaml | 1 + .../avoid_unnecessary_type_casts_test.dart | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 lint_test/avoid_unnecessary_type_casts_test.dart diff --git a/lint_test/analysis_options.yaml b/lint_test/analysis_options.yaml index f940145c..dd0e156d 100644 --- a/lint_test/analysis_options.yaml +++ b/lint_test/analysis_options.yaml @@ -17,3 +17,4 @@ custom_lint: - avoid_unnecessary_setstate - double-literal-format - avoid-unnecessary-type-assertions + - avoid-unnecessary-type-casts diff --git a/lint_test/avoid_unnecessary_type_casts_test.dart b/lint_test/avoid_unnecessary_type_casts_test.dart new file mode 100644 index 00000000..f4a69b0a --- /dev/null +++ b/lint_test/avoid_unnecessary_type_casts_test.dart @@ -0,0 +1,18 @@ +// 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; +} From 95e8816c7e06391c631656f745cda3495481596b Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 13:45:06 +0300 Subject: [PATCH 07/10] Fix formatting --- ...avoid_unnecessary_type_assertions_rule.dart | 1 - .../avoid_unnecessary_type_casts_rule.dart | 8 ++++---- lib/utils/typecast_utils.dart | 18 ++++++++---------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart b/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart index 00614d89..acd8c186 100644 --- a/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart +++ b/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart @@ -102,7 +102,6 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule { when targetType is ParameterizedType && isIterable(realTargetType) && arguments.isNotEmpty) { - final objectType = targetType.typeArguments.first; final castedType = arguments.first.type; diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart index c70c03d5..97a3690c 100644 --- a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart @@ -32,10 +32,10 @@ class AvoidUnnecessaryTypeCastsRule extends SolidLintRule { @override void run( - CustomLintResolver resolver, - ErrorReporter reporter, - CustomLintContext context, - ) { + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { final visitor = AvoidUnnecessaryTypeCastsVisitor(); context.registry.addAsExpression((node) { diff --git a/lib/utils/typecast_utils.dart b/lib/utils/typecast_utils.dart index ed7ed321..2a94c643 100644 --- a/lib/utils/typecast_utils.dart +++ b/lib/utils/typecast_utils.dart @@ -55,8 +55,8 @@ class TypeCast { /// [isReversed] true for opposite comparison, i.e 'is!' /// and false for positive comparison, i.e. 'is', 'as' or 'whereType' bool isUnnecessaryTypeCheck({ - bool isReversed = false, - }) { + bool isReversed = false, + }) { if (isNullableCompatibility()) { return false; } @@ -79,21 +79,19 @@ class TypeCast { return !isReversed; } - /// Checks for type arguments and compares them bool areGenericsWithSameTypeArgs() { - if (this - case TypeCast(source: final objectType, target: final castedType) - when objectType is ParameterizedType && castedType is ParameterizedType - ) { + if (this case TypeCast(source: final objectType, target: final castedType) + when objectType is ParameterizedType && + castedType is ParameterizedType) { if (objectType.typeArguments.length != castedType.typeArguments.length) { return false; } return IterableZip([objectType.typeArguments, castedType.typeArguments]) - .every( - (e) => TypeCast(source: e[0], target: e[1]).isUnnecessaryTypeCheck(), - ); + .every( + (e) => TypeCast(source: e[0], target: e[1]).isUnnecessaryTypeCheck(), + ); } else { return false; } From 10912b1e3e669e4d25c3e0ae17e0d887e8bec0ed Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 14:11:33 +0300 Subject: [PATCH 08/10] Add more test cases --- .../avoid_unnecessary_type_casts_test.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lint_test/avoid_unnecessary_type_casts_test.dart b/lint_test/avoid_unnecessary_type_casts_test.dart index f4a69b0a..f969009f 100644 --- a/lint_test/avoid_unnecessary_type_casts_test.dart +++ b/lint_test/avoid_unnecessary_type_casts_test.dart @@ -15,4 +15,21 @@ void fun() { 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; +} \ No newline at end of file From 21c78afb453fab5b45d8344c39f207155b5dd38c Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 14:13:55 +0300 Subject: [PATCH 09/10] Group code for more readability --- lib/utils/typecast_utils.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/utils/typecast_utils.dart b/lib/utils/typecast_utils.dart index 2a94c643..ab4d14e8 100644 --- a/lib/utils/typecast_utils.dart +++ b/lib/utils/typecast_utils.dart @@ -62,7 +62,6 @@ class TypeCast { } final objectCastedType = castTypeInHierarchy(); - if (objectCastedType == null) { return isReversed; } @@ -71,7 +70,6 @@ class TypeCast { source: objectCastedType, target: target, ); - if (!objectTypeCast.areGenericsWithSameTypeArgs()) { return false; } From 64dc8b0e99c7b34bbf85274ba43758fee37be812 Mon Sep 17 00:00:00 2001 From: Denis Bogatirov Date: Wed, 13 Sep 2023 14:22:35 +0300 Subject: [PATCH 10/10] Switch to getters, refactor areGenericsWithSameTypeArgs --- ...void_unnecessary_type_assertions_rule.dart | 13 ++++++--- .../avoid_unnecessary_type_casts_visitor.dart | 2 +- lib/utils/typecast_utils.dart | 27 ++++++++++--------- .../avoid_unnecessary_type_casts_test.dart | 4 +-- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart b/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart index acd8c186..84745b4a 100644 --- a/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart +++ b/lib/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart @@ -78,16 +78,21 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule { return false; } - final typeCast = TypeCast(source: objectType, target: castedType); + final typeCast = TypeCast( + source: objectType, + target: castedType, + isReversed: true, + ); if (node.notOperator != null && objectType is! TypeParameterType && objectType is! DynamicType && !objectType.isDartCoreObject && - typeCast.isUnnecessaryTypeCheck(isReversed: true)) { + typeCast.isUnnecessaryTypeCheck) { return true; } else { - return typeCast.isUnnecessaryTypeCheck(); + final typeCast = TypeCast(source: objectType, target: castedType); + return typeCast.isUnnecessaryTypeCheck; } } @@ -111,7 +116,7 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule { final typeCast = TypeCast(source: objectType, target: castedType); - return typeCast.isUnnecessaryTypeCheck(); + return typeCast.isUnnecessaryTypeCheck; } else { return false; } diff --git a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart index 0facb95a..69d01579 100644 --- a/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart +++ b/lib/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_visitor.dart @@ -49,7 +49,7 @@ class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor { target: castedType, ); - if (typeCast.isUnnecessaryTypeCheck()) { + if (typeCast.isUnnecessaryTypeCheck) { _expressions[node] = 'as'; } } diff --git a/lib/utils/typecast_utils.dart b/lib/utils/typecast_utils.dart index ab4d14e8..5de93092 100644 --- a/lib/utils/typecast_utils.dart +++ b/lib/utils/typecast_utils.dart @@ -17,9 +17,16 @@ class TypeCast { /// The type being tested for final DartType target; + /// Set to true for opposite comparison, i.e 'is!' + final bool isReversed; + /// Creates a new Typecast object with a given expression's or object's type /// and a tested type - TypeCast({required this.source, required this.target}); + TypeCast({ + required this.source, + required this.target, + this.isReversed = false, + }); /// Returns the first type from source's supertypes /// which is corresponding to target or null @@ -40,7 +47,7 @@ class TypeCast { /// Checks for nullable type casts /// Only one case `Type? is Type` always valid assertion case. - bool isNullableCompatibility() { + bool get isNullableCompatibility { final isObjectTypeNullable = source.nullabilitySuffix != NullabilitySuffix.none; final isCastedTypeNullable = @@ -52,12 +59,9 @@ class TypeCast { /// Checks that type checking is unnecessary /// [source] is the source expression type /// [target] is the type against which the expression type is compared - /// [isReversed] true for opposite comparison, i.e 'is!' /// and false for positive comparison, i.e. 'is', 'as' or 'whereType' - bool isUnnecessaryTypeCheck({ - bool isReversed = false, - }) { - if (isNullableCompatibility()) { + bool get isUnnecessaryTypeCheck { + if (isNullableCompatibility) { return false; } @@ -70,7 +74,7 @@ class TypeCast { source: objectCastedType, target: target, ); - if (!objectTypeCast.areGenericsWithSameTypeArgs()) { + if (!objectTypeCast.areGenericsWithSameTypeArgs) { return false; } @@ -78,7 +82,7 @@ class TypeCast { } /// Checks for type arguments and compares them - bool areGenericsWithSameTypeArgs() { + bool get areGenericsWithSameTypeArgs { if (this case TypeCast(source: final objectType, target: final castedType) when objectType is ParameterizedType && castedType is ParameterizedType) { @@ -87,9 +91,8 @@ class TypeCast { } return IterableZip([objectType.typeArguments, castedType.typeArguments]) - .every( - (e) => TypeCast(source: e[0], target: e[1]).isUnnecessaryTypeCheck(), - ); + .map((e) => TypeCast(source: e[0], target: e[1])) + .every((cast) => cast.isUnnecessaryTypeCheck); } else { return false; } diff --git a/lint_test/avoid_unnecessary_type_casts_test.dart b/lint_test/avoid_unnecessary_type_casts_test.dart index f969009f..302353c1 100644 --- a/lint_test/avoid_unnecessary_type_casts_test.dart +++ b/lint_test/avoid_unnecessary_type_casts_test.dart @@ -16,7 +16,7 @@ void fun() { // casting `Type? is Type` is allowed final castedD = nullableD as double; - final testMap = { 'A': 'B' }; + final testMap = {'A': 'B'}; // expect_lint: avoid-unnecessary-type-casts final castedMapValue = testMap['A'] as String?; @@ -32,4 +32,4 @@ void fun() { void _testFun(String a) { // expect_lint: avoid-unnecessary-type-casts final result = (a as String).length; -} \ No newline at end of file +}