This repository has been archived by the owner on Jul 16, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add static code diagnostic prefer-iterable-of (#999)
Co-authored-by: Dmitry Krutskikh <dmitry.krutskikh@gmail.com>
- Loading branch information
1 parent
36baaa5
commit a254d6e
Showing
15 changed files
with
654 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
.../analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// ignore_for_file: public_member_api_docs | ||
|
||
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:collection/collection.dart'; | ||
|
||
import '../../../../../utils/dart_types_utils.dart'; | ||
import '../../../../../utils/node_utils.dart'; | ||
import '../../../lint_utils.dart'; | ||
import '../../../models/internal_resolved_unit_result.dart'; | ||
import '../../../models/issue.dart'; | ||
import '../../../models/replacement.dart'; | ||
import '../../../models/severity.dart'; | ||
import '../../models/common_rule.dart'; | ||
import '../../rule_utils.dart'; | ||
|
||
part 'visitor.dart'; | ||
|
||
class PreferIterableOfRule extends CommonRule { | ||
static const ruleId = 'prefer-iterable-of'; | ||
|
||
static const _warningMessage = 'Prefer using .of'; | ||
static const _replaceComment = "Replace with 'of'."; | ||
|
||
PreferIterableOfRule([Map<String, Object> config = const {}]) | ||
: super( | ||
id: ruleId, | ||
severity: readSeverity(config, Severity.warning), | ||
excludes: readExcludes(config), | ||
); | ||
|
||
@override | ||
Iterable<Issue> check(InternalResolvedUnitResult source) { | ||
final visitor = _Visitor(); | ||
|
||
source.unit.visitChildren(visitor); | ||
|
||
return visitor.expressions | ||
.map((expression) => createIssue( | ||
rule: this, | ||
location: nodeLocation(node: expression, source: source), | ||
message: _warningMessage, | ||
replacement: Replacement( | ||
comment: _replaceComment, | ||
replacement: expression.toString().replaceAll('.from', '.of'), | ||
), | ||
)) | ||
.toList(growable: false); | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/visitor.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// ignore_for_file: deprecated_member_use | ||
|
||
part of 'prefer_iterable_of_rule.dart'; | ||
|
||
class _Visitor extends RecursiveAstVisitor<void> { | ||
final _expressions = <InstanceCreationExpression>[]; | ||
|
||
Iterable<InstanceCreationExpression> get expressions => _expressions; | ||
|
||
@override | ||
void visitInstanceCreationExpression(InstanceCreationExpression node) { | ||
super.visitInstanceCreationExpression(node); | ||
|
||
if (isIterableOrSubclass(node.staticType) && | ||
node.constructorName.name?.name == 'from') { | ||
final arg = node.argumentList.arguments.first; | ||
|
||
final argumentType = _getType(arg.staticType); | ||
final castedType = _getType(node.staticType); | ||
|
||
if (argumentType != null && | ||
!argumentType.isDartCoreObject && | ||
!argumentType.isDynamic && | ||
_isUnnecessaryTypeCheck(castedType, argumentType)) { | ||
_expressions.add(node); | ||
} | ||
} | ||
} | ||
|
||
DartType? _getType(DartType? type) { | ||
if (type == null || type is! InterfaceType) { | ||
return null; | ||
} | ||
|
||
final typeArgument = type.typeArguments.firstOrNull; | ||
if (typeArgument == null) { | ||
return null; | ||
} | ||
|
||
return typeArgument; | ||
} | ||
|
||
bool _isUnnecessaryTypeCheck( | ||
DartType? objectType, | ||
DartType? castedType, | ||
) { | ||
if (objectType == null || castedType == null) { | ||
return false; | ||
} | ||
|
||
if (objectType == castedType) { | ||
return true; | ||
} | ||
|
||
if (_checkNullableCompatibility(objectType, castedType)) { | ||
return false; | ||
} | ||
|
||
final objectCastedType = | ||
_foundCastedTypeInObjectTypeHierarchy(objectType, castedType); | ||
if (objectCastedType == null) { | ||
return true; | ||
} | ||
|
||
if (!_checkGenerics(objectCastedType, castedType)) { | ||
return false; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
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, | ||
) { | ||
if (objectType.element == castedType.element) { | ||
return objectType; | ||
} | ||
|
||
if (objectType is InterfaceType) { | ||
return objectType.allSupertypes | ||
.firstWhereOrNull((value) => value.element == castedType.element); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
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 (!_isUnnecessaryTypeCheck( | ||
objectType.typeArguments[argumentIndex], | ||
castedType.typeArguments[argumentIndex], | ||
)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...nt_analyzer/rules/rules_list/prefer_iterable_of/examples/double_linked_queue_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'dart:collection'; | ||
|
||
void main() { | ||
const queue = DoubleLinkedQueue.of([1, 2, 3, 4, 5, 6, 7, 8, 9]); | ||
|
||
final copy = DoubleLinkedQueue<int>.from(queue); // LINT | ||
final numQueue = DoubleLinkedQueue<num>.from(queue); // LINT | ||
|
||
final intQueue = DoubleLinkedQueue<int>.from(numQueue); | ||
|
||
final unspecifedQueue = DoubleLinkedQueue.from(queue); // LINT | ||
|
||
final dynamicQueue = DoubleLinkedQueue<dynamic>.from([1, 2, 3]); | ||
final copy = DoubleLinkedQueue<int>.from(dynamicQueue); | ||
final dynamicCopy = DoubleLinkedQueue.from(dynamicQueue); | ||
|
||
final objectQueue = DoubleLinkedQueue<Object>.from([1, 2, 3]); | ||
final copy = DoubleLinkedQueue<int>.from(objectQueue); | ||
} |
19 changes: 19 additions & 0 deletions
19
...nalyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/hash_set_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'dart:collection'; | ||
|
||
void main() { | ||
const hashSet = HashSet.of([1, 2, 3, 4, 5, 6, 7, 8, 9]); | ||
|
||
final copy = HashSet<int>.from(hashSet); // LINT | ||
final numSet = HashSet<num>.from(hashSet); // LINT | ||
|
||
final intSet = HashSet<int>.from(numSet); | ||
|
||
final unspecifedSet = HashSet.from(hashSet); // LINT | ||
|
||
final dynamicSet = HashSet<dynamic>.from([1, 2, 3]); | ||
final copy = HashSet<int>.from(dynamicSet); | ||
final dynamicCopy = HashSet.from(dynamicSet); | ||
|
||
final objectSet = HashSet<Object>.from([1, 2, 3]); | ||
final copy = HashSet<int>.from(objectSet); | ||
} |
19 changes: 19 additions & 0 deletions
19
...s/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/linked_hash_set_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'dart:collection'; | ||
|
||
void main() { | ||
const hashSet = LinkedHashSet.of([1, 2, 3, 4, 5, 6, 7, 8, 9]); | ||
|
||
final copy = LinkedHashSet<int>.from(hashSet); // LINT | ||
final numSet = LinkedHashSet<num>.from(hashSet); // LINT | ||
|
||
final intSet = LinkedHashSet<int>.from(numSet); | ||
|
||
final unspecifedSet = LinkedHashSet.from(hashSet); // LINT | ||
|
||
final dynamicSet = LinkedHashSet<dynamic>.from([1, 2, 3]); | ||
final copy = LinkedHashSet<int>.from(dynamicSet); | ||
final dynamicCopy = LinkedHashSet.from(dynamicSet); | ||
|
||
final objectSet = LinkedHashSet<Object>.from([1, 2, 3]); | ||
final copy = LinkedHashSet<int>.from(objectSet); | ||
} |
17 changes: 17 additions & 0 deletions
17
...rc/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
void main() { | ||
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9]; | ||
|
||
final copy = List<int>.from(array); // LINT | ||
final numList = List<num>.from(array); // LINT | ||
|
||
final intList = List<int>.from(numList); | ||
|
||
final unspecifedList = List.from(array); // LINT | ||
|
||
final dynamicArray = <dynamic>[1, 2, 3]; | ||
final copy = List<int>.from(dynamicArray); | ||
final dynamicCopy = List.from(dynamicArray); | ||
|
||
final objectArray = <Object>[1, 2, 3]; | ||
final copy = List<int>.from(objectArray); | ||
} |
19 changes: 19 additions & 0 deletions
19
...lyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_queue_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'dart:collection'; | ||
|
||
void main() { | ||
const queue = ListQueue.of([1, 2, 3, 4, 5, 6, 7, 8, 9]); | ||
|
||
final copy = ListQueue<int>.from(queue); // LINT | ||
final numQueue = ListQueue<num>.from(queue); // LINT | ||
|
||
final intQueue = ListQueue<int>.from(numQueue); | ||
|
||
final unspecifedQueue = ListQueue.from(queue); // LINT | ||
|
||
final dynamicQueue = ListQueue<dynamic>.from([1, 2, 3]); | ||
final copy = ListQueue<int>.from(dynamicQueue); | ||
final dynamicCopy = ListQueue.from(dynamicQueue); | ||
|
||
final objectQueue = ListQueue<Object>.from([1, 2, 3]); | ||
final copy = ListQueue<int>.from(objectQueue); | ||
} |
19 changes: 19 additions & 0 deletions
19
...c/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/queue_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'dart:collection'; | ||
|
||
void main() { | ||
const queue = Queue.of([1, 2, 3, 4, 5, 6, 7, 8, 9]); | ||
|
||
final copy = Queue<int>.from(queue); // LINT | ||
final numQueue = Queue<num>.from(queue); // LINT | ||
|
||
final intQueue = Queue<int>.from(numQueue); | ||
|
||
final unspecifedQueue = Queue.from(queue); // LINT | ||
|
||
final dynamicQueue = Queue<dynamic>.from([1, 2, 3]); | ||
final copy = Queue<int>.from(dynamicQueue); | ||
final dynamicCopy = Queue.from(dynamicQueue); | ||
|
||
final objectQueue = Queue<Object>.from([1, 2, 3]); | ||
final copy = Queue<int>.from(objectQueue); | ||
} |
17 changes: 17 additions & 0 deletions
17
...src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/set_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
void main() { | ||
const set = {1, 2, 3, 4, 5, 6, 7, 8, 9}; | ||
|
||
final copy = Set<int>.from(set); // LINT | ||
final numSet = Set<num>.from(set); // LINT | ||
|
||
final intSet = Set<int>.from(numSet); | ||
|
||
final unspecifedSet = Set.from(set); // LINT | ||
|
||
final dynamicSet = <dynamic>{1, 2, 3}; | ||
final copy = Set<int>.from(dynamicSet); | ||
final dynamicCopy = Set.from(dynamicSet); | ||
|
||
final objectSet = <Object>{1, 2, 3}; | ||
final copy = Set<int>.from(objectSet); | ||
} |
19 changes: 19 additions & 0 deletions
19
...rs/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/splay_tree_set_example.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import 'dart:collection'; | ||
|
||
void main() { | ||
const hashSet = SplayTreeSet.of([1, 2, 3, 4, 5, 6, 7, 8, 9]); | ||
|
||
final copy = SplayTreeSet<int>.from(hashSet); // LINT | ||
final numSet = SplayTreeSet<num>.from(hashSet); // LINT | ||
|
||
final intSet = SplayTreeSet<int>.from(numSet); | ||
|
||
final unspecifedSet = SplayTreeSet.from(hashSet); // LINT | ||
|
||
final dynamicSet = SplayTreeSet<dynamic>.from([1, 2, 3]); | ||
final copy = SplayTreeSet<int>.from(dynamicSet); | ||
final dynamicCopy = SplayTreeSet.from(dynamicSet); | ||
|
||
final objectSet = SplayTreeSet<Object>.from([1, 2, 3]); | ||
final copy = SplayTreeSet<int>.from(objectSet); | ||
} |
Oops, something went wrong.