Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ linter:
- cascade_invocations
- cast_nullable_to_non_nullable
- close_sinks
- collection_methods_unrelated_type
- combinators_ordering
- comment_references
- conditional_uri_does_not_exist
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import 'rules/cancel_subscriptions.dart';
import 'rules/cascade_invocations.dart';
import 'rules/cast_nullable_to_non_nullable.dart';
import 'rules/close_sinks.dart';
import 'rules/collection_methods_unrelated_type.dart';
import 'rules/combinators_ordering.dart';
import 'rules/comment_references.dart';
import 'rules/conditional_uri_does_not_exist.dart';
Expand Down Expand Up @@ -274,6 +275,7 @@ void registerLintRules({bool inTestMode = false}) {
..register(CascadeInvocations())
..register(CastNullableToNonNullable())
..register(CloseSinks())
..register(CollectionMethodsUnrelatedType())
..register(CombinatorsOrdering())
..register(CommentReferences())
..register(ConditionalUriDoesNotExist())
Expand Down
189 changes: 189 additions & 0 deletions lib/src/rules/collection_methods_unrelated_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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>.containsAll` should be related to `Iterable<E>`
* an argument to `Set<E>.difference` should be related to `Set<E>`
* an argument to `Set<E>.intersection` should be related to `Set<E>`
* an argument to `Set<E>.lookup` should be related to `E`
* an argument to `Set<E>.remove` should be related to `E`
* an argument to `Set<E>.removeAll` should be related to `Iterable<E>`
* an argument to `Set<E>.retainAll` should be related to `Iterable<E>`

**BAD:**
```dart
void someFunction() {
var list = <int>[];
if (list.contains('1')) print('someFunction'); // LINT
}
```

**BAD:**
```dart
void someFunction() {
var set = <int>{};
set.removeAll({'1'}); // LINT
}
```

**GOOD:**
```dart
void someFunction() {
var list = <int>[];
if (list.contains(1)) print('someFunction'); // OK
}
```

**GOOD:**
```dart
void someFunction() {
var set = <int>{};
set.removeAll({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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only have accessors in TypeProvide for the types from the SDK that are referenced in the spec (because those are the ones that analyzer needs to use to validate the code. You'll need to add it to the mock sdk and then use something like typeProvider.objectElement.library.getClass('Queue') to get the element.

MethodDefinitionForName(
'dart.collection',
'Queue',
'remove',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
// Argument to `Set<E>.containsAll` should be assignable to `Set<E>`.
MethodDefinitionForElement(
typeProvider.setElement,
'containsAll',
ExpectedArgumentKind.assignableToIterableOfTypeArgument,
),
// Argument to `Set<E>.difference` should be assignable to `Set<E>`.
MethodDefinitionForElement(
typeProvider.setElement,
'difference',
ExpectedArgumentKind.assignableToCollection,
),
// Argument to `Set<E>.intersection` should be assignable to `Set<E>`.
MethodDefinitionForElement(
typeProvider.setElement,
'intersection',
ExpectedArgumentKind.assignableToCollection,
),
// 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,
),
// Argument to `Set<E>.removeAll` should be assignable to `Set<E>`.
MethodDefinitionForElement(
typeProvider.setElement,
'removeAll',
ExpectedArgumentKind.assignableToIterableOfTypeArgument,
),
// Argument to `Set<E>.retainAll` should be assignable to `Set<E>`.
MethodDefinitionForElement(
typeProvider.setElement,
'retainAll',
ExpectedArgumentKind.assignableToIterableOfTypeArgument,
),
];

@override
List<MethodDefinition> get indexOperators => [
// Argument to `Map<K, V>.[]` should be assignable to `K`.
MethodDefinitionForElement(
typeProvider.mapElement,
'[]',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
];
}
15 changes: 8 additions & 7 deletions lib/src/rules/iterable_contains_unrelated_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// 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 'package:analyzer/dart/element/element.dart';

import '../analyzer.dart';
import '../util/unrelated_types_visitor.dart';

Expand Down Expand Up @@ -121,7 +119,7 @@ class DerivedClass3 extends ClassBase implements Mixin {}

class IterableContainsUnrelatedType extends LintRule {
static const LintCode code = LintCode('iterable_contains_unrelated_type',
"The type of the argument of 'Iterable<{0}>.contains' isn't a subtype of '{0}'.");
"The argument type '{0}' isn't related to '{1}'.");

IterableContainsUnrelatedType()
: super(
Expand All @@ -145,8 +143,11 @@ class _Visitor extends UnrelatedTypesProcessors {
_Visitor(super.rule, super.typeSystem, super.typeProvider);

@override
InterfaceElement get interface => typeProvider.iterableElement;

@override
String get methodName => 'contains';
List<MethodDefinition> get methods => [
MethodDefinitionForElement(
typeProvider.iterableElement,
'contains',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
)
];
}
15 changes: 8 additions & 7 deletions lib/src/rules/list_remove_unrelated_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// 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 'package:analyzer/dart/element/element.dart';

import '../analyzer.dart';
import '../util/unrelated_types_visitor.dart';

Expand Down Expand Up @@ -121,7 +119,7 @@ class DerivedClass3 extends ClassBase implements Mixin {}

class ListRemoveUnrelatedType extends LintRule {
static const LintCode code = LintCode('list_remove_unrelated_type',
"The type of the argument of 'List<{0}>.remove' isn't a subtype of '{0}'.");
"The argument type '{0}' isn't related to '{1}'.");

ListRemoveUnrelatedType()
: super(
Expand All @@ -145,8 +143,11 @@ class _Visitor extends UnrelatedTypesProcessors {
_Visitor(super.rule, super.typeSystem, super.typeProvider);

@override
InterfaceElement get interface => typeProvider.listElement;

@override
String get methodName => 'remove';
List<MethodDefinition> get methods => [
MethodDefinitionForElement(
typeProvider.listElement,
'remove',
ExpectedArgumentKind.assignableToCollectionTypeArgument,
),
];
}
Loading