blob: d42449905e8dc0eee86d779e6a9bb9877d16aaf6 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import '../analyzer.dart';
import '../util/unrelated_types_visitor.dart';
const _desc = 'Invocation of various collection methods with arguments of '
'unrelated types.';
const _details = r'''
**DON'T** invoke certain collection method with an argument with an unrelated
type.
Doing this will invoke `==` on the collection's elements and most likely will
return `false`.
An argument passed to a collection method should relate to the collection type
as follows:
* an argument to `Iterable<E>.contains` should be related to `E`
* an argument to `List<E>.remove` should be related to `E`
* an argument to `Map<K, V>.containsKey` should be related to `K`
* an argument to `Map<K, V>.containsValue` should be related to `V`
* an argument to `Map<K, V>.remove` should be related to `K`
* an argument to `Map<K, V>.[]` should be related to `K`
* an argument to `Queue<E>.remove` should be related to `E`
* an argument to `Set<E>.lookup` should be related to `E`
* an argument to `Set<E>.remove` should be related to `E`
**BAD:**
```dart
void someFunction() {
var list = <int>[];
if (list.contains('1')) print('someFunction'); // LINT
}
```
**BAD:**
```dart
void someFunction() {
var set = <int>{};
set.remove('1'); // LINT
}
```
**GOOD:**
```dart
void someFunction() {
var list = <int>[];
if (list.contains(1)) print('someFunction'); // OK
}
```
**GOOD:**
```dart
void someFunction() {
var set = <int>{};
set.remove(1); // OK
}
```
''';
class CollectionMethodsUnrelatedType extends LintRule {
static const LintCode code = LintCode('collection_methods_unrelated_type',
"The argument type '{0}' isn't related to '{1}'.");
CollectionMethodsUnrelatedType()
: super(
name: 'collection_methods_unrelated_type',
description: _desc,
details: _details,
group: Group.errors);
@override
LintCode get lintCode => code;
@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this, context.typeSystem, context.typeProvider);
registry.addIndexExpression(this, visitor);
registry.addMethodInvocation(this, visitor);
}
}
class _Visitor extends UnrelatedTypesProcessors {
_Visitor(super.rule, super.typeSystem, super.typeProvider);
@override
List<MethodDefinition> get methods => [
// Argument to `Iterable<E>.contains` should be assignable to `E`.
MethodDefinitionForElement(
typeProvider.iterableElement,
'contains',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `List<E>.remove` should be assignable to `E`.
MethodDefinitionForElement(
typeProvider.listElement,
'remove',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `Map<K, V>.containsKey` should be assignable to `K`.
MethodDefinitionForElement(
typeProvider.mapElement,
'containsKey',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `Map<K, V>.containsValue` should be assignable to `V`.
MethodDefinitionForElement(
typeProvider.mapElement,
'containsValue',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
typeArgumentIndex: 1,
),
// Argument to `Map<K, V>.remove` should be assignable to `K`.
MethodDefinitionForElement(
typeProvider.mapElement,
'remove',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `Queue<E>.remove` should be assignable to `E`.
MethodDefinitionForName(
'dart.collection',
'Queue',
'remove',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `Set<E>.lookup` should be assignable to `E`.
MethodDefinitionForElement(
typeProvider.setElement,
'lookup',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `Set<E>.remove` should be assignable to `E`.
MethodDefinitionForElement(
typeProvider.setElement,
'remove',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
];
@override
List<MethodDefinition> get indexOperators => [
// Argument to `Map<K, V>.[]` should be assignable to `K`.
MethodDefinitionForElement(
typeProvider.mapElement,
'[]',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
];
}