diff --git a/lib/main.dart b/lib/main.dart index 010dcaff..c7aca7eb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,8 @@ import 'package:analysis_server_plugin/registry.dart'; import 'package:solid_lints/src/lints/avoid_debug_print_in_release/avoid_debug_print_in_release_rule.dart'; import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart'; import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_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/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart'; import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart'; @@ -41,5 +43,15 @@ class SolidLintsPlugin extends Plugin { for (final code in doubleLiteralFormatRule.diagnosticCodes) { registry.registerFixForRule(code, DoubleLiteralFormatFix.new); } + + final avoidUnnecessaryTypeAssertionsRule = + AvoidUnnecessaryTypeAssertionsRule(); + registry.registerLintRule( + avoidUnnecessaryTypeAssertionsRule, + ); + registry.registerFixForRule( + avoidUnnecessaryTypeAssertionsRule.diagnosticCode, + AvoidUnnecessaryTypeAssertionsFix.new, + ); } } diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart b/lib/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart index 6f95a11d..906c934b 100644 --- a/lib/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart +++ b/lib/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart @@ -1,134 +1,52 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:analyzer/diagnostic/diagnostic.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/src/models/rule_config.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; -import 'package:solid_lints/src/utils/typecast_utils.dart'; -import 'package:solid_lints/src/utils/types_utils.dart'; - -part 'fixes/avoid_unnecessary_type_assertions_fix.dart'; - -/// The name of 'is' operator -const operatorIsName = 'is'; - -/// The name of 'whereType' method -const whereTypeMethodName = 'whereType'; +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_unnecessary_type_assertions/visitors/unnecessary_is_expression_visitor.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart'; /// Warns about unnecessary usage of `is` and `whereType` operators. /// /// ### Example: -/// #### BAD: -/// ```dart -/// final testList = [1.0, 2.0, 3.0]; -/// final result = testList is List; // LINT -/// final negativeResult = testList is! List; // LINT -/// testList.whereType(); // LINT -/// -/// final double d = 2.0; -/// final casted = d is double; // LINT -/// ``` -/// -/// #### GOOD: -/// ```dart -/// final dynamicList = [1.0, 2.0]; -/// dynamicList.whereType(); -/// -/// final double? nullableD = 2.0; -/// // casting `Type? is Type` is allowed -/// final castedD = nullableD is double; -/// ``` -class AvoidUnnecessaryTypeAssertions extends SolidLintRule { +/// {@macro solid_lints.avoid_unnecessary_type_assertions.example_is} +/// {@macro solid_lints.avoid_unnecessary_type_assertions.example_where} +class AvoidUnnecessaryTypeAssertionsRule extends AnalysisRule { + /// The name of 'is' operator + static const operatorIsName = 'is'; + + /// The name of 'whereType' method + static const whereTypeMethodName = 'whereType'; + /// This lint rule represents /// the error whether we use bad formatted double literals. static const lintName = 'avoid_unnecessary_type_assertions'; - static const _unnecessaryIsCode = LintCode( - name: lintName, - problemMessage: "Unnecessary usage of the '$operatorIsName' operator.", + static const _unnecessaryTypeAssertionsCode = LintCode( + lintName, + "Unnecessary usage of the {0}.", ); - static const _unnecessaryWhereTypeCode = LintCode( - name: lintName, - problemMessage: "Unnecessary usage of the '$whereTypeMethodName' method.", - ); - - AvoidUnnecessaryTypeAssertions._(super.config); - - /// Creates a new instance of [AvoidUnnecessaryTypeAssertions] - /// based on the lint configuration. - factory AvoidUnnecessaryTypeAssertions.createRule(CustomLintConfigs configs) { - final rule = RuleConfig( - configs: configs, - name: lintName, - problemMessage: (_) => "Unnecessary usage of typecast operators.", - ); - - return AvoidUnnecessaryTypeAssertions._(rule); - } - @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, - ) { - context.registry.addIsExpression((node) { - if (_isUnnecessaryIsExpression(node)) { - reporter.atNode(node, _unnecessaryIsCode); - } - }); + DiagnosticCode get diagnosticCode => _unnecessaryTypeAssertionsCode; - context.registry.addMethodInvocation((node) { - if (_isUnnecessaryWhereType(node)) { - reporter.atNode(node, _unnecessaryWhereTypeCode); - } - }); - } + /// Creates a new instance of [AvoidUnnecessaryTypeAssertionsRule] + AvoidUnnecessaryTypeAssertionsRule() + : super( + name: lintName, + description: "Unnecessary usage of typecast operators.", + ); @override - List getFixes() => [_UnnecessaryTypeAssertionsFix()]; - - bool _isUnnecessaryIsExpression(IsExpression node) { - final objectType = node.expression.staticType; - final castedType = node.type.type; - - if (objectType == null || castedType == null) { - return false; - } - final typeCast = TypeCast( - source: objectType, - target: castedType, - isReversed: node.notOperator != null, - ); - return typeCast.isUnnecessaryTypeCheck; - } - - bool _isUnnecessaryWhereType(MethodInvocation node) { - if (node - case MethodInvocation( - methodName: Identifier(name: whereTypeMethodName), - target: Expression(staticType: final targetType), - realTarget: Expression(staticType: final realTargetType), - typeArguments: TypeArgumentList(arguments: final arguments), - ) - when targetType is ParameterizedType && - isIterable(realTargetType) && - arguments.isNotEmpty) { - final objectType = targetType.typeArguments.first; - final castedType = arguments.first.type; - - if (castedType == null) { - return false; - } + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, + ) { + super.registerNodeProcessors(registry, context); - final typeCast = TypeCast(source: objectType, target: castedType); + final unnecessaryIsExpressionVisitor = UnnecessaryIsExpressionVisitor(this); + registry.addIsExpression(this, unnecessaryIsExpressionVisitor); - return typeCast.isUnnecessaryTypeCheck; - } else { - return false; - } + final unnecessaryWhereTypeVisitor = UnnecessaryWhereTypeVisitor(this); + registry.addMethodInvocation(this, unnecessaryWhereTypeVisitor); } } diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart b/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart index 3c429e66..814520c1 100644 --- a/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart +++ b/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart @@ -1,50 +1,68 @@ -part of '../avoid_unnecessary_type_assertions_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_assertions/avoid_unnecessary_type_assertions_rule.dart'; /// A Quick fix for `avoid_unnecessary_type_assertions` rule /// Suggests to remove unnecessary assertions -class _UnnecessaryTypeAssertionsFix extends DartFix { +class AvoidUnnecessaryTypeAssertionsFix extends ResolvedCorrectionProducer { + static const _avoidUnnecessaryTypeAssertionsFixKind = FixKind( + 'solid_lints.fix.${AvoidUnnecessaryTypeAssertionsRule.lintName}', + DartFixKindPriority.standard, + 'Remove the unnecessary {0}', + ); + + SourceRange? _partToRemove; + + @override + List? get fixArguments => [ + if (_partToRemove case final partToRemove?) + '"${utils.getRangeText(partToRemove).trim()}"' + else + 'type assertion', + ]; + @override - void run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - Diagnostic analysisError, - List others, - ) { - context.registry.addIsExpression((node) { - if (analysisError.sourceRange.intersects(node.sourceRange)) { - _addDeletion(reporter, operatorIsName, node, node.isOperator.offset); - } - }); - - context.registry.addMethodInvocation((node) { - if (analysisError.sourceRange.intersects(node.sourceRange)) { - _addDeletion( - reporter, - whereTypeMethodName, - node, - node.operator?.offset ?? node.offset, - ); - } - }); + FixKind get fixKind => _avoidUnnecessaryTypeAssertionsFixKind; + + @override + CorrectionApplicability get applicability => + CorrectionApplicability.automatically; + + /// Creates a new instance of [AvoidUnnecessaryTypeAssertionsFix] + AvoidUnnecessaryTypeAssertionsFix({required super.context}); + + @override + Future compute(ChangeBuilder builder) async { + final isExpressionNode = node.thisOrAncestorOfType(); + if (isExpressionNode != null) { + final operatorOffset = isExpressionNode.isOperator.offset - 1; + _partToRemove = _removedPartRange(isExpressionNode, operatorOffset); + } + + final whereTypeNode = node.thisOrAncestorOfType(); + if (whereTypeNode != null && _partToRemove == null) { + final operatorOffset = + whereTypeNode.operator?.offset ?? whereTypeNode.offset; + _partToRemove = _removedPartRange(whereTypeNode, operatorOffset); + } + + final partToRemove = _partToRemove; + if (partToRemove == null) return; + + await builder.addDartFileEdit( + file, + (builder) => builder.addDeletion(partToRemove), + ); } - void _addDeletion( - ChangeReporter reporter, - String itemToDelete, - Expression node, - int operatorOffset, - ) { + SourceRange _removedPartRange(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)); - }); + return SourceRange(operatorOffset, removedPartLength); } } diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_is_expression_visitor.dart b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_is_expression_visitor.dart new file mode 100644 index 00000000..7fd7b1d2 --- /dev/null +++ b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_is_expression_visitor.dart @@ -0,0 +1,63 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; +import 'package:solid_lints/src/utils/typecast_utils.dart'; + +/// Visitor for [AvoidUnnecessaryTypeAssertionsRule]. +/// Reports on unnecessary usage of 'is' operator. +/// +/// ### Example: +/// {@template solid_lints.avoid_unnecessary_type_assertions.example_is} +/// #### BAD: +/// ```dart +/// final testList = [1.0, 2.0, 3.0]; +/// final result = testList is List; // LINT +/// final negativeResult = testList is! List; // LINT +/// +/// final double d = 2.0; +/// final casted = d is double; // LINT +/// ``` +/// +/// #### GOOD: +/// ```dart +/// final double? nullableD = 2.0; +/// // casting `Type? is Type` is allowed +/// final castedD = nullableD is double; +/// ``` +/// {@endtemplate} +class UnnecessaryIsExpressionVisitor extends SimpleAstVisitor { + final AvoidUnnecessaryTypeAssertionsRule _rule; + + /// Creates a new instance of [UnnecessaryIsExpressionVisitor]. + UnnecessaryIsExpressionVisitor(this._rule); + + @override + void visitIsExpression(IsExpression node) { + super.visitIsExpression(node); + + if (!_isUnnecessaryIsExpression(node)) return; + + _rule.reportAtNode( + node, + arguments: [ + "'${AvoidUnnecessaryTypeAssertionsRule.operatorIsName}' operator", + ], + ); + } + + bool _isUnnecessaryIsExpression(IsExpression node) { + final objectType = node.expression.staticType; + final castedType = node.type.type; + + if (objectType == null || castedType == null) { + return false; + } + + final typeCast = TypeCast( + source: objectType, + target: castedType, + isReversed: node.notOperator != null, + ); + return typeCast.isUnnecessaryTypeCheck; + } +} diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart new file mode 100644 index 00000000..13bca7f5 --- /dev/null +++ b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart @@ -0,0 +1,74 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:collection/collection.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; +import 'package:solid_lints/src/utils/typecast_utils.dart'; + +/// Visitor for [AvoidUnnecessaryTypeAssertionsRule]. +/// Reports on unnecessary usage of 'whereType' method. +/// +/// ### Example: +/// {@template solid_lints.avoid_unnecessary_type_assertions.example_where} +/// #### BAD: +/// ```dart +/// final testList = [1.0, 2.0, 3.0]; +/// testList.whereType(); // LINT +/// ``` +/// +/// #### GOOD: +/// ```dart +/// final dynamicList = [1.0, 2.0]; +/// dynamicList.whereType(); +/// ``` +/// {@endtemplate} +class UnnecessaryWhereTypeVisitor extends SimpleAstVisitor { + final AvoidUnnecessaryTypeAssertionsRule _rule; + + /// Creates a new instance of [UnnecessaryWhereTypeVisitor] + UnnecessaryWhereTypeVisitor(this._rule); + + @override + void visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); + + if (!_isUnnecessaryWhereType(node)) return; + + _rule.reportAtNode( + node, + arguments: [ + "'${AvoidUnnecessaryTypeAssertionsRule.whereTypeMethodName}' method", + ], + ); + } + + bool _isUnnecessaryWhereType(MethodInvocation node) { + if (node case MethodInvocation( + methodName: Identifier( + name: AvoidUnnecessaryTypeAssertionsRule.whereTypeMethodName, + ), + target: Expression(staticType: final InterfaceType targetType), + typeArguments: TypeArgumentList(:final arguments), + ) when arguments.isNotEmpty) { + final targetIterableType = switch (targetType) { + InterfaceType(isDartCoreIterable: true) => targetType, + InterfaceType(:final allSupertypes) => allSupertypes.firstWhereOrNull( + (e) => e.isDartCoreIterable, + ), + }; + + final objectType = targetIterableType?.typeArguments.firstOrNull; + final castedType = arguments.first.type; + + if (castedType == null || objectType == null) { + return false; + } + + final typeCast = TypeCast(source: objectType, target: castedType); + + return typeCast.isUnnecessaryTypeCheck; + } + + return false; + } +} diff --git a/lint_test/avoid_unnecessary_type_assertions_test.dart b/lint_test/avoid_unnecessary_type_assertions_test.dart deleted file mode 100644 index b3f2f3c6..00000000 --- a/lint_test/avoid_unnecessary_type_assertions_test.dart +++ /dev/null @@ -1,47 +0,0 @@ -// ignore_for_file: prefer_const_declarations, prefer_match_file_name, cyclomatic_complexity, unused_element -// ignore_for_file: unnecessary_nullable_for_final_variable_declarations -// ignore_for_file: unnecessary_type_check -// ignore_for_file: unused_local_variable - -/// Check the `avoid_unnecessary_type_assertions` rule - -void fun() { - final testList = [1.0, 2.0, 3.0]; - // expect_lint: avoid_unnecessary_type_assertions - final result = testList is List; - - // expect_lint: avoid_unnecessary_type_assertions - final negativeResult = testList is! List; - - // to check quick-fix => testList.length - // expect_lint: avoid_unnecessary_type_assertions - testList.whereType().length; - - final dynamicList = [1.0, 2.0]; - dynamicList.whereType(); - - // expect_lint: avoid_unnecessary_type_assertions - [1.0, 2.0].whereType(); - - final double d = 2.0; - // expect_lint: avoid_unnecessary_type_assertions - final casted = d is double; - - // expect_lint: avoid_unnecessary_type_assertions - final negativeCasted = d is! double; - - final double? nullableD = 2.0; - // casting `Type? is Type` is allowed - final castedD = nullableD is double; -} - -class _A {} - -class _B extends _A {} - -class _C extends _A {} - -void noLint() { - final _A a = _B(); - if (a is! _C) return; -} diff --git a/test/lints/auto_test_lint_offsets.dart b/test/lints/auto_test_lint_offsets.dart new file mode 100644 index 00000000..1f5ebfe0 --- /dev/null +++ b/test/lints/auto_test_lint_offsets.dart @@ -0,0 +1,23 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; + +mixin AutoTestLintOffsets on AnalysisRuleTest { + List _expectedCodeFragments = []; + + Future assertAutoDiagnostics(String source) async { + try { + final expectedDiagnostics = [ + for (final codeFragment in _expectedCodeFragments) + lint(source.indexOf(codeFragment), codeFragment.length), + ]; + + await assertDiagnostics(source, expectedDiagnostics); + } finally { + _expectedCodeFragments = []; + } + } + + String l(String code) { + _expectedCodeFragments.add(code); + return code; + } +} diff --git a/test/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule_test.dart b/test/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule_test.dart new file mode 100644 index 00000000..e8863be1 --- /dev/null +++ b/test/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule_test.dart @@ -0,0 +1,199 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../auto_test_lint_offsets.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidUnnecessaryTypeAssertionsRuleTest); + }); +} + +@reflectiveTest +class AvoidUnnecessaryTypeAssertionsRuleTest extends AnalysisRuleTest + with AutoTestLintOffsets { + @override + void setUp() { + rule = AvoidUnnecessaryTypeAssertionsRule(); + super.setUp(); + } + + @override + String get analysisRule => AvoidUnnecessaryTypeAssertionsRule.lintName; + + Future + test_does_not_report_if_is_expression_checks_nullable_source() async { + await assertNoDiagnostics(r''' +void fun() { + final double? nullableD = 2.0; + final castedD = nullableD is double; +} +'''); + } + + Future + test_does_not_report_if_is_not_expression_checks_unrelated_subtype() async { + await assertNoDiagnostics(r''' +class _A {} + +class _B extends _A {} + +class _C extends _A {} + +void fun() { + final _A a = _B(); + if (a is! _C) return; +} +'''); + } + + Future + test_does_not_report_if_is_expression_has_different_generic_type_argument() async { + await assertNoDiagnostics(r''' +void fun() { + final nums = [1, 2, 3]; + final result = nums is List; +} +'''); + } + + Future + test_does_not_report_if_where_type_filters_dynamic_iterable() async { + await assertNoDiagnostics(r''' +void fun() { + final dynamicList = [1.0, 2.0]; + dynamicList.whereType(); +} +'''); + } + + Future + test_does_not_report_if_where_type_has_different_generic_type_argument() async { + await assertNoDiagnostics(r''' +void fun() { + final nums = [1, 2, 3]; + nums.whereType(); +} +'''); + } + + Future test_does_not_report_if_where_type_omits_type_argument() async { + await assertNoDiagnostics(r''' +void fun() { + final values = [1.0, 2.0, 3.0]; + values.whereType(); +} +'''); + } + + Future test_reports_if_is_expression_checks_exact_generic_type() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +void fun() { + final testList = [1.0, 2.0, 3.0]; + final result = ${l('testList is List')}; +} +'''); + } + + Future + test_reports_if_is_not_expression_checks_exact_generic_type() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +void fun() { + final testList = [1.0, 2.0, 3.0]; + final result = ${l('testList is! List')}; +} +'''); + } + + Future test_reports_if_is_expression_checks_exact_scalar_type() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +void fun() { + final double d = 2.0; + final casted = ${l('d is double')}; +} +'''); + } + + Future + test_reports_if_is_not_expression_checks_exact_scalar_type() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +void fun() { + final double d = 2.0; + final negativeCasted = ${l('d is! double')}; +} +'''); + } + + Future test_reports_if_is_expression_checks_nullable_target() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +void fun() { + final double d = 2.0; + final casted = ${l('d is double?')}; +} +'''); + } + + Future test_reports_if_is_expression_checks_supertype() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +void fun() { + final ints = [1, 2, 3]; + final result = ${l('ints is Iterable')}; +} +'''); + } + + Future test_reports_if_where_type_filters_exact_type() async { + await assertAutoDiagnostics(''' +void fun() { + final testList = [1.0, 2.0, 3.0]; + ${l('testList.whereType()')}.length; +} +'''); + } + + Future test_reports_if_where_type_filters_nullable_type() async { + await assertAutoDiagnostics(''' +void fun() { + ${l('[1.0, 2.0].whereType()')}; +} +'''); + } + + Future test_reports_on_custom_types_with_default_generics() async { + await assertAutoDiagnostics(''' +// ignore_for_file: unnecessary_type_check + +abstract class MyIterable implements Iterable {} + +void test(MyIterable iterable) { + ${l('iterable.whereType()')}; +} +'''); + } + + Future test_does_not_report_custom_types_when_using_subtype() async { + await assertNoDiagnostics(r''' +abstract class _A {} +abstract class _B extends _A {} + +abstract class AIterable implements Iterable<_A> {} + +void test(AIterable iterable) { + iterable.whereType<_B>(); +} +'''); + } +}