diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b3abdee5..8b57a1ea76 100644 --- a/CHANGELOG.md +++ b/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). diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index d284718e51..d4e0840fb4 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -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'; @@ -121,6 +122,7 @@ final _implementedRules = )>{ 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, diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule.dart new file mode 100644 index 0000000000..1b2f7858f3 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule.dart @@ -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 config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + ); + + @override + Iterable 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); + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/visitor.dart new file mode 100644 index 0000000000..26148f1043 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/visitor.dart @@ -0,0 +1,119 @@ +// ignore_for_file: deprecated_member_use + +part of 'prefer_iterable_of_rule.dart'; + +class _Visitor extends RecursiveAstVisitor { + final _expressions = []; + + Iterable 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; + } +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/double_linked_queue_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/double_linked_queue_example.dart new file mode 100644 index 0000000000..57ac8cfb57 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/double_linked_queue_example.dart @@ -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.from(queue); // LINT + final numQueue = DoubleLinkedQueue.from(queue); // LINT + + final intQueue = DoubleLinkedQueue.from(numQueue); + + final unspecifedQueue = DoubleLinkedQueue.from(queue); // LINT + + final dynamicQueue = DoubleLinkedQueue.from([1, 2, 3]); + final copy = DoubleLinkedQueue.from(dynamicQueue); + final dynamicCopy = DoubleLinkedQueue.from(dynamicQueue); + + final objectQueue = DoubleLinkedQueue.from([1, 2, 3]); + final copy = DoubleLinkedQueue.from(objectQueue); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/hash_set_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/hash_set_example.dart new file mode 100644 index 0000000000..14ef6d065d --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/hash_set_example.dart @@ -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.from(hashSet); // LINT + final numSet = HashSet.from(hashSet); // LINT + + final intSet = HashSet.from(numSet); + + final unspecifedSet = HashSet.from(hashSet); // LINT + + final dynamicSet = HashSet.from([1, 2, 3]); + final copy = HashSet.from(dynamicSet); + final dynamicCopy = HashSet.from(dynamicSet); + + final objectSet = HashSet.from([1, 2, 3]); + final copy = HashSet.from(objectSet); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/linked_hash_set_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/linked_hash_set_example.dart new file mode 100644 index 0000000000..c45a527925 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/linked_hash_set_example.dart @@ -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.from(hashSet); // LINT + final numSet = LinkedHashSet.from(hashSet); // LINT + + final intSet = LinkedHashSet.from(numSet); + + final unspecifedSet = LinkedHashSet.from(hashSet); // LINT + + final dynamicSet = LinkedHashSet.from([1, 2, 3]); + final copy = LinkedHashSet.from(dynamicSet); + final dynamicCopy = LinkedHashSet.from(dynamicSet); + + final objectSet = LinkedHashSet.from([1, 2, 3]); + final copy = LinkedHashSet.from(objectSet); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_example.dart new file mode 100644 index 0000000000..9c6c1a47a9 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_example.dart @@ -0,0 +1,17 @@ +void main() { + const array = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + final copy = List.from(array); // LINT + final numList = List.from(array); // LINT + + final intList = List.from(numList); + + final unspecifedList = List.from(array); // LINT + + final dynamicArray = [1, 2, 3]; + final copy = List.from(dynamicArray); + final dynamicCopy = List.from(dynamicArray); + + final objectArray = [1, 2, 3]; + final copy = List.from(objectArray); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_queue_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_queue_example.dart new file mode 100644 index 0000000000..e5b6b30c72 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/list_queue_example.dart @@ -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.from(queue); // LINT + final numQueue = ListQueue.from(queue); // LINT + + final intQueue = ListQueue.from(numQueue); + + final unspecifedQueue = ListQueue.from(queue); // LINT + + final dynamicQueue = ListQueue.from([1, 2, 3]); + final copy = ListQueue.from(dynamicQueue); + final dynamicCopy = ListQueue.from(dynamicQueue); + + final objectQueue = ListQueue.from([1, 2, 3]); + final copy = ListQueue.from(objectQueue); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/queue_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/queue_example.dart new file mode 100644 index 0000000000..555d151a63 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/queue_example.dart @@ -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.from(queue); // LINT + final numQueue = Queue.from(queue); // LINT + + final intQueue = Queue.from(numQueue); + + final unspecifedQueue = Queue.from(queue); // LINT + + final dynamicQueue = Queue.from([1, 2, 3]); + final copy = Queue.from(dynamicQueue); + final dynamicCopy = Queue.from(dynamicQueue); + + final objectQueue = Queue.from([1, 2, 3]); + final copy = Queue.from(objectQueue); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/set_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/set_example.dart new file mode 100644 index 0000000000..52a0bf105d --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/set_example.dart @@ -0,0 +1,17 @@ +void main() { + const set = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + + final copy = Set.from(set); // LINT + final numSet = Set.from(set); // LINT + + final intSet = Set.from(numSet); + + final unspecifedSet = Set.from(set); // LINT + + final dynamicSet = {1, 2, 3}; + final copy = Set.from(dynamicSet); + final dynamicCopy = Set.from(dynamicSet); + + final objectSet = {1, 2, 3}; + final copy = Set.from(objectSet); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/splay_tree_set_example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/splay_tree_set_example.dart new file mode 100644 index 0000000000..86e913c613 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/examples/splay_tree_set_example.dart @@ -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.from(hashSet); // LINT + final numSet = SplayTreeSet.from(hashSet); // LINT + + final intSet = SplayTreeSet.from(numSet); + + final unspecifedSet = SplayTreeSet.from(hashSet); // LINT + + final dynamicSet = SplayTreeSet.from([1, 2, 3]); + final copy = SplayTreeSet.from(dynamicSet); + final dynamicCopy = SplayTreeSet.from(dynamicSet); + + final objectSet = SplayTreeSet.from([1, 2, 3]); + final copy = SplayTreeSet.from(objectSet); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule_test.dart new file mode 100644 index 0000000000..9360f6aeb5 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule_test.dart @@ -0,0 +1,284 @@ +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/prefer_iterable_of/prefer_iterable_of_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _listExamplePath = 'prefer_iterable_of/examples/list_example.dart'; +const _setExamplePath = 'prefer_iterable_of/examples/set_example.dart'; +const _doubleLinkedQueueExamplePath = + 'prefer_iterable_of/examples/double_linked_queue_example.dart'; +const _hashSetExamplePath = 'prefer_iterable_of/examples/hash_set_example.dart'; +const _linkedHashSetExamplePath = + 'prefer_iterable_of/examples/linked_hash_set_example.dart'; +const _listQueueExamplePath = + 'prefer_iterable_of/examples/list_queue_example.dart'; +const _queueExamplePath = 'prefer_iterable_of/examples/queue_example.dart'; +const _splayTreeSetExamplePath = + 'prefer_iterable_of/examples/splay_tree_set_example.dart'; + +void main() { + group('PreferIterableOfRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_listExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'prefer-iterable-of', + severity: Severity.warning, + ); + }); + + test('reports about found issues for lists', () async { + final unit = await RuleTestHelper.resolveFromFile(_listExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [4, 5, 9], + startColumns: [16, 19, 26], + locationTexts: [ + 'List.from(array)', + 'List.from(array)', + 'List.from(array)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'List.of(array)', + 'List.of(array)', + 'List.of(array)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for sets', () async { + final unit = await RuleTestHelper.resolveFromFile(_setExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [4, 5, 9], + startColumns: [16, 18, 25], + locationTexts: [ + 'Set.from(set)', + 'Set.from(set)', + 'Set.from(set)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'Set.of(set)', + 'Set.of(set)', + 'Set.of(set)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for double linked queues', () async { + final unit = + await RuleTestHelper.resolveFromFile(_doubleLinkedQueueExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 7, 11], + startColumns: [16, 20, 27], + locationTexts: [ + 'DoubleLinkedQueue.from(queue)', + 'DoubleLinkedQueue.from(queue)', + 'DoubleLinkedQueue.from(queue)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'DoubleLinkedQueue.of(queue)', + 'DoubleLinkedQueue.of(queue)', + 'DoubleLinkedQueue.of(queue)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for hash sets', () async { + final unit = await RuleTestHelper.resolveFromFile(_hashSetExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 7, 11], + startColumns: [16, 18, 25], + locationTexts: [ + 'HashSet.from(hashSet)', + 'HashSet.from(hashSet)', + 'HashSet.from(hashSet)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'HashSet.of(hashSet)', + 'HashSet.of(hashSet)', + 'HashSet.of(hashSet)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for linked hash sets', () async { + final unit = + await RuleTestHelper.resolveFromFile(_linkedHashSetExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 7, 11], + startColumns: [16, 18, 25], + locationTexts: [ + 'LinkedHashSet.from(hashSet)', + 'LinkedHashSet.from(hashSet)', + 'LinkedHashSet.from(hashSet)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'LinkedHashSet.of(hashSet)', + 'LinkedHashSet.of(hashSet)', + 'LinkedHashSet.of(hashSet)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for list queues', () async { + final unit = await RuleTestHelper.resolveFromFile(_listQueueExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 7, 11], + startColumns: [16, 20, 27], + locationTexts: [ + 'ListQueue.from(queue)', + 'ListQueue.from(queue)', + 'ListQueue.from(queue)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'ListQueue.of(queue)', + 'ListQueue.of(queue)', + 'ListQueue.of(queue)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for queues', () async { + final unit = await RuleTestHelper.resolveFromFile(_queueExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 7, 11], + startColumns: [16, 20, 27], + locationTexts: [ + 'Queue.from(queue)', + 'Queue.from(queue)', + 'Queue.from(queue)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'Queue.of(queue)', + 'Queue.of(queue)', + 'Queue.of(queue)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + + test('reports about found issues for splay tree sets', () async { + final unit = + await RuleTestHelper.resolveFromFile(_splayTreeSetExamplePath); + final issues = PreferIterableOfRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 7, 11], + startColumns: [16, 18, 25], + locationTexts: [ + 'SplayTreeSet.from(hashSet)', + 'SplayTreeSet.from(hashSet)', + 'SplayTreeSet.from(hashSet)', + ], + messages: [ + 'Prefer using .of', + 'Prefer using .of', + 'Prefer using .of', + ], + replacements: [ + 'SplayTreeSet.of(hashSet)', + 'SplayTreeSet.of(hashSet)', + 'SplayTreeSet.of(hashSet)', + ], + replacementComments: [ + "Replace with 'of'.", + "Replace with 'of'.", + "Replace with 'of'.", + ], + ); + }); + }); +} diff --git a/website/docs/rules/common/prefer-iterable-of.mdx b/website/docs/rules/common/prefer-iterable-of.mdx new file mode 100644 index 0000000000..2fb761434a --- /dev/null +++ b/website/docs/rules/common/prefer-iterable-of.mdx @@ -0,0 +1,34 @@ +import RuleDetails from '@site/src/components/RuleDetails'; + + + +Warns when `List.from()` factory is used instead of `List.of()`. + +The difference between `List.of()` and `List.from()` is that `.of()` takes an argument of the same type as what it returns and enforces it at compilation time, and that `.from()` allows potentially unsafe downcasting and enforces convertibility at runtime. + +Additional resources: + +- +- + +### Example {#example} + +**❌ Bad:** + +```dart +... +var intList = [1, 2, 3]; + +var copy = List.from(intList); // LINT +var numList = List.from(intList); // LINT + +var unspecifedList = List.from(intList); // LINT +``` + +**✅ Good:** + +```dart +var numList = [1, 2, 3]; + +var intList = List.from(numList); +``` diff --git a/website/docs/rules/index.mdx b/website/docs/rules/index.mdx index ab2bfb970f..9bd6bd9fbf 100644 --- a/website/docs/rules/index.mdx +++ b/website/docs/rules/index.mdx @@ -398,6 +398,17 @@ Rules are grouped by category to help you understand their purpose. Each rule ha return statement. + + Warns when List.from() factory is used instead of{' '} + List.of(). + +