Skip to content
This repository has been archived by the owner on Jul 16, 2023. It is now read-only.

Commit

Permalink
feat: add static code diagnostic prefer-iterable-of (#999)
Browse files Browse the repository at this point in the history
Co-authored-by: Dmitry Krutskikh <dmitry.krutskikh@gmail.com>
  • Loading branch information
incendial and dkrutskikh committed Sep 19, 2022
1 parent 36baaa5 commit a254d6e
Show file tree
Hide file tree
Showing 15 changed files with 654 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

* feat: add static code diagnostic [`prefer-iterable-of`](https://dartcodemetrics.dev/docs/rules/common/prefer-iterable-of).

## 4.18.3

* fix: fix regression in is! checks for [`avoid-unnecessary-type-assertions`](https://dartcodemetrics.dev/docs/rules/common/avoid-unnecessary-type-assertions).
Expand Down
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Expand Up @@ -51,6 +51,7 @@ import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks_rule.
import 'rules_list/prefer_first/prefer_first_rule.dart';
import 'rules_list/prefer_immediate_return/prefer_immediate_return_rule.dart';
import 'rules_list/prefer_intl_name/prefer_intl_name_rule.dart';
import 'rules_list/prefer_iterable_of/prefer_iterable_of_rule.dart';
import 'rules_list/prefer_last/prefer_last_rule.dart';
import 'rules_list/prefer_match_file_name/prefer_match_file_name_rule.dart';
import 'rules_list/prefer_moving_to_variable/prefer_moving_to_variable_rule.dart';
Expand Down Expand Up @@ -121,6 +122,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
PreferFirstRule.ruleId: PreferFirstRule.new,
PreferImmediateReturnRule.ruleId: PreferImmediateReturnRule.new,
PreferIntlNameRule.ruleId: PreferIntlNameRule.new,
PreferIterableOfRule.ruleId: PreferIterableOfRule.new,
PreferLastRule.ruleId: PreferLastRule.new,
PreferMatchFileNameRule.ruleId: PreferMatchFileNameRule.new,
PreferMovingToVariableRule.ruleId: PreferMovingToVariableRule.new,
Expand Down
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
}

0 comments on commit a254d6e

Please sign in to comment.