From 80755739e605786125151ad79a4b1e0884a0a0b5 Mon Sep 17 00:00:00 2001 From: Dmitry Zhifarsky Date: Fri, 27 Jan 2023 12:42:05 +0400 Subject: [PATCH 1/4] feat: add new rule correct-game-instantiating --- CHANGELOG.md | 1 + .../rules/models/flame_rule.dart | 16 ++++ .../lint_analyzer/rules/models/rule_type.dart | 1 + .../lint_analyzer/rules/rules_factory.dart | 2 + .../correct_game_instantiating_rule.dart | 75 +++++++++++++++++++ .../correct_game_instantiating/visitor.dart | 69 +++++++++++++++++ lib/src/utils/flutter_types_utils.dart | 3 + .../correct_game_instantiating_rule_test.dart | 49 ++++++++++++ .../examples/example.dart | 64 ++++++++++++++++ test/src/helpers/rule_test_helper.dart | 8 +- 10 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 lib/src/analyzers/lint_analyzer/rules/models/flame_rule.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/visitor.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule_test.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/examples/example.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fd866b75e..9ea742cfd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * fix: export missing parts of public API. +* feat: add static code diagnostic [`correct-game-instantiating`](https://dcm.dev/docs/individuals/rules/flame/correct-game-instantiating). ## 5.5.0 diff --git a/lib/src/analyzers/lint_analyzer/rules/models/flame_rule.dart b/lib/src/analyzers/lint_analyzer/rules/models/flame_rule.dart new file mode 100644 index 0000000000..dad35f6f98 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/models/flame_rule.dart @@ -0,0 +1,16 @@ +import 'rule.dart'; +import 'rule_type.dart'; + +/// Represents a base class for Flutter-specific rules. +abstract class FlameRule extends Rule { + static const link = 'https://pub.dev/packages/flame'; + + const FlameRule({ + required super.id, + required super.severity, + required super.excludes, + required super.includes, + }) : super( + type: RuleType.flame, + ); +} diff --git a/lib/src/analyzers/lint_analyzer/rules/models/rule_type.dart b/lib/src/analyzers/lint_analyzer/rules/models/rule_type.dart index a6d3e8ca6f..3fe3840ac4 100644 --- a/lib/src/analyzers/lint_analyzer/rules/models/rule_type.dart +++ b/lib/src/analyzers/lint_analyzer/rules/models/rule_type.dart @@ -8,4 +8,5 @@ class RuleType { static const flutter = RuleType._('flutter'); static const intl = RuleType._('intl'); static const angular = RuleType._('angular'); + static const flame = RuleType._('flame'); } diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index 54b05badd8..1bbf8d5972 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -35,6 +35,7 @@ import 'rules_list/binary_expression_operand_order/binary_expression_operand_ord import 'rules_list/check_for_equals_in_render_object_setters/check_for_equals_in_render_object_setters_rule.dart'; import 'rules_list/component_annotation_arguments_ordering/component_annotation_arguments_ordering_rule.dart'; import 'rules_list/consistent_update_render_object/consistent_update_render_object_rule.dart'; +import 'rules_list/correct_game_instantiating/correct_game_instantiating_rule.dart'; import 'rules_list/double_literal_format/double_literal_format_rule.dart'; import 'rules_list/format_comment/format_comment_rule.dart'; import 'rules_list/list_all_equatable_fields/list_all_equatable_fields_rule.dart'; @@ -119,6 +120,7 @@ final _implementedRules = )>{ ComponentAnnotationArgumentsOrderingRule.ruleId: ComponentAnnotationArgumentsOrderingRule.new, ConsistentUpdateRenderObjectRule.ruleId: ConsistentUpdateRenderObjectRule.new, + CorrectGameInstantiatingRule.ruleId: CorrectGameInstantiatingRule.new, DoubleLiteralFormatRule.ruleId: DoubleLiteralFormatRule.new, FormatCommentRule.ruleId: FormatCommentRule.new, ListAllEquatableFieldsRule.ruleId: ListAllEquatableFieldsRule.new, diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule.dart new file mode 100644 index 0000000000..3989d42472 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule.dart @@ -0,0 +1,75 @@ +// ignore_for_file: public_member_api_docs + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:collection/collection.dart'; + +import '../../../../../utils/flutter_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/flame_rule.dart'; +import '../../rule_utils.dart'; + +part 'visitor.dart'; + +class CorrectGameInstantiatingRule extends FlameRule { + static const String ruleId = 'correct-game-instantiating'; + + static const _warningMessage = + 'Incorrect game instantiation. The game will reset on each rebuild.'; + static const _correctionMessage = "Replace with 'controlled'."; + + CorrectGameInstantiatingRule([Map config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + includes: readIncludes(config), + ); + + @override + Iterable check(InternalResolvedUnitResult source) { + final visitor = _Visitor(); + + source.unit.visitChildren(visitor); + + return visitor.info + .map((info) => createIssue( + rule: this, + location: nodeLocation(node: info.node, source: source), + message: _warningMessage, + replacement: _createReplacement(info), + )) + .toList(growable: false); + } + + Replacement? _createReplacement(_InstantiationInfo info) { + if (info.isStateless) { + final arguments = info.node.argumentList.arguments.map((arg) { + if (arg is NamedExpression && arg.name.label.name == 'game') { + final expression = arg.expression; + if (expression is InstanceCreationExpression) { + final name = + expression.staticType?.getDisplayString(withNullability: false); + if (name != null) { + return 'gameFactory: $name.new,'; + } + } + } + + return arg.toSource(); + }); + + return Replacement( + replacement: 'GameWidget.controlled$arguments;', + comment: _correctionMessage, + ); + } + + return null; + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/visitor.dart new file mode 100644 index 0000000000..a992e4c1f9 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/visitor.dart @@ -0,0 +1,69 @@ +part of 'correct_game_instantiating_rule.dart'; + +class _Visitor extends RecursiveAstVisitor { + final _info = <_InstantiationInfo>[]; + + Iterable<_InstantiationInfo> get info => _info; + + @override + void visitClassDeclaration(ClassDeclaration node) { + super.visitClassDeclaration(node); + + final type = node.extendsClause?.superclass.type; + if (type == null) { + return; + } + + final isWidget = isWidgetOrSubclass(type); + final isWidgetState = isWidgetStateOrSubclass(type); + if (!isWidget && !isWidgetState) { + return; + } + + final declaration = node.members.firstWhereOrNull((declaration) => + declaration is MethodDeclaration && declaration.name.lexeme == 'build'); + + if (declaration is MethodDeclaration) { + final visitor = _GameWidgetInstantiatingVisitor(); + declaration.visitChildren(visitor); + + if (visitor.wrongInstantiation != null) { + _info.add(_InstantiationInfo( + visitor.wrongInstantiation!, + isStateless: !isWidgetState, + )); + } + } + } +} + +class _GameWidgetInstantiatingVisitor extends RecursiveAstVisitor { + InstanceCreationExpression? wrongInstantiation; + + _GameWidgetInstantiatingVisitor(); + + @override + void visitInstanceCreationExpression(InstanceCreationExpression node) { + super.visitInstanceCreationExpression(node); + + if (isGameWidget(node.staticType) && + node.constructorName.name?.name != 'controlled') { + final gameArgument = node.argumentList.arguments.firstWhereOrNull( + (argument) => + argument is NamedExpression && argument.name.label.name == 'game', + ); + + if (gameArgument is NamedExpression && + gameArgument.expression is InstanceCreationExpression) { + wrongInstantiation = node; + } + } + } +} + +class _InstantiationInfo { + final bool isStateless; + final InstanceCreationExpression node; + + const _InstantiationInfo(this.node, {required this.isStateless}); +} diff --git a/lib/src/utils/flutter_types_utils.dart b/lib/src/utils/flutter_types_utils.dart index d5ea11e2a9..801ddab720 100644 --- a/lib/src/utils/flutter_types_utils.dart +++ b/lib/src/utils/flutter_types_utils.dart @@ -50,6 +50,9 @@ bool isPaddingWidget(DartType? type) => bool isBuildContext(DartType? type) => type?.getDisplayString(withNullability: false) == 'BuildContext'; +bool isGameWidget(DartType? type) => + type?.getDisplayString(withNullability: false) == 'GameWidget'; + bool _isWidget(DartType? type) => type?.getDisplayString(withNullability: false) == 'Widget'; diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule_test.dart new file mode 100644 index 0000000000..49514d5eb8 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule_test.dart @@ -0,0 +1,49 @@ +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/correct_game_instantiating_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _examplePath = 'correct_game_instantiating/examples/example.dart'; + +void main() { + group('CorrectGameInstantiatingRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = CorrectGameInstantiatingRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'correct-game-instantiating', + severity: Severity.warning, + ); + }); + + test('reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = CorrectGameInstantiatingRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [4, 25], + startColumns: [12, 12], + locationTexts: [ + 'GameWidget(game: MyFlameGame())', + 'GameWidget(game: MyFlameGame())', + ], + messages: [ + 'Incorrect game instantiation. The game will reset on each rebuild.', + 'Incorrect game instantiation. The game will reset on each rebuild.', + ], + replacements: [ + 'GameWidget.controlled(gameFactory: MyFlameGame.new,);', + null, + ], + replacementComments: [ + "Replace with 'controlled'.", + null, + ], + ); + }); + }); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/examples/example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/examples/example.dart new file mode 100644 index 0000000000..d9309c9327 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/correct_game_instantiating/examples/example.dart @@ -0,0 +1,64 @@ +class MyGamePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return GameWidget(game: MyFlameGame()); // LINT + } +} + +class MyGamePage extends StatefulWidget { + @override + State createState() => _MyGamePageState(); +} + +class _MyGamePageState extends State { + late final _game = MyFlameGame(); + + @override + Widget build(BuildContext context) { + return GameWidget(game: _game); + } +} + +class _MyGamePageState extends State { + @override + Widget build(BuildContext context) { + return GameWidget(game: MyFlameGame()); // LINT + } +} + +class MyGamePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return GameWidget.controlled(gameFactory: MyFlameGame.new); + } +} + +class GameWidget extends Widget { + final Widget game; + + GameWidget({required this.game}); + + GameWidget.controlled({required GameFactory gameFactory}) { + this.game = gameFactory(); + } +} + +class MyFlameGame extends Widget {} + +typedef GameFactory = T Function(); + +class StatefulWidget extends Widget {} + +class StatelessWidget extends Widget {} + +class Widget { + const Widget(); +} + +class BuildContext {} + +abstract class State { + void initState(); + + void setState(VoidCallback callback) => callback(); +} diff --git a/test/src/helpers/rule_test_helper.dart b/test/src/helpers/rule_test_helper.dart index abf57e96f1..607827de07 100644 --- a/test/src/helpers/rule_test_helper.dart +++ b/test/src/helpers/rule_test_helper.dart @@ -46,8 +46,8 @@ class RuleTestHelper { Iterable? startColumns, Iterable? locationTexts, Iterable? messages, - Iterable? replacements, - Iterable? replacementComments, + Iterable? replacements, + Iterable? replacementComments, }) { if (startLines != null) { expect( @@ -83,7 +83,7 @@ class RuleTestHelper { if (replacements != null) { expect( - issues.map((issue) => issue.suggestion!.replacement), + issues.map((issue) => issue.suggestion?.replacement), equals(replacements), reason: 'incorrect replacement', ); @@ -91,7 +91,7 @@ class RuleTestHelper { if (replacementComments != null) { expect( - issues.map((issue) => issue.suggestion!.comment), + issues.map((issue) => issue.suggestion?.comment), equals(replacementComments), reason: 'incorrect replacement comment', ); From 7b7d520177455004e70c08e1c2429a3245194fcf Mon Sep 17 00:00:00 2001 From: Dmitry Zhifarsky Date: Fri, 27 Jan 2023 17:31:28 +0400 Subject: [PATCH 2/4] feat: add static code diagnostic avoid-initializing-in-on-mount --- CHANGELOG.md | 1 + .../lint_analyzer/rules/rules_factory.dart | 2 + .../avoid_global_state_rule.dart | 5 +- .../avoid_initializing_in_on_mount_rule.dart | 49 +++++++++++++++++++ .../visitor.dart | 48 ++++++++++++++++++ lib/src/utils/flame_type_utils.dart | 10 ++++ ...id_initializing_in_on_mount_rule_test.dart | 37 ++++++++++++++ .../examples/example.dart | 15 ++++++ 8 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/visitor.dart create mode 100644 lib/src/utils/flame_type_utils.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule_test.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/examples/example.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea742cfd4..54ca30bf1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * fix: export missing parts of public API. * feat: add static code diagnostic [`correct-game-instantiating`](https://dcm.dev/docs/individuals/rules/flame/correct-game-instantiating). +* feat: add static code diagnostic [`avoid-initializing-in-on-mount`](https://dcm.dev/docs/individuals/rules/flame/avoid-initializing-in-on-mount). ## 5.5.0 diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index 1bbf8d5972..45c61a406d 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -11,6 +11,7 @@ import 'rules_list/avoid_dynamic/avoid_dynamic_rule.dart'; import 'rules_list/avoid_expanded_as_spacer/avoid_expanded_as_spacer_rule.dart'; import 'rules_list/avoid_global_state/avoid_global_state_rule.dart'; import 'rules_list/avoid_ignoring_return_values/avoid_ignoring_return_values_rule.dart'; +import 'rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart'; import 'rules_list/avoid_late_keyword/avoid_late_keyword_rule.dart'; import 'rules_list/avoid_missing_enum_constant_in_map/avoid_missing_enum_constant_in_map_rule.dart'; import 'rules_list/avoid_nested_conditional_expressions/avoid_nested_conditional_expressions_rule.dart'; @@ -88,6 +89,7 @@ final _implementedRules = )>{ AvoidDynamicRule.ruleId: AvoidDynamicRule.new, AvoidGlobalStateRule.ruleId: AvoidGlobalStateRule.new, AvoidIgnoringReturnValuesRule.ruleId: AvoidIgnoringReturnValuesRule.new, + AvoidInitializingInOnMountRule.ruleId: AvoidInitializingInOnMountRule.new, AvoidLateKeywordRule.ruleId: AvoidLateKeywordRule.new, AvoidMissingEnumConstantInMapRule.ruleId: AvoidMissingEnumConstantInMapRule.new, diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_global_state/avoid_global_state_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_global_state/avoid_global_state_rule.dart index 4c951203d0..4ef02d3ba7 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_global_state/avoid_global_state_rule.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_global_state/avoid_global_state_rule.dart @@ -37,10 +37,7 @@ class AvoidGlobalStateRule extends CommonRule { return visitor.declarations .map((declaration) => createIssue( rule: this, - location: nodeLocation( - node: declaration, - source: source, - ), + location: nodeLocation(node: declaration, source: source), message: _warning, )) .toList(growable: false); diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart new file mode 100644 index 0000000000..88c6edd46c --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart @@ -0,0 +1,49 @@ +// 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/element.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:collection/collection.dart'; + +import '../../../../../utils/flame_type_utils.dart'; +import '../../../../../utils/node_utils.dart'; +import '../../../lint_utils.dart'; +import '../../../models/internal_resolved_unit_result.dart'; +import '../../../models/issue.dart'; +import '../../../models/severity.dart'; +import '../../models/flame_rule.dart'; +import '../../rule_utils.dart'; + +part 'visitor.dart'; + +class AvoidInitializingInOnMountRule extends FlameRule { + static const String ruleId = 'avoid-initializing-in-on-mount'; + + static const _warningMessage = + 'Avoid initializing final late variables in onMount.'; + + AvoidInitializingInOnMountRule([Map config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + includes: readIncludes(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, + )) + .toList(growable: false); + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/visitor.dart new file mode 100644 index 0000000000..727299538b --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/visitor.dart @@ -0,0 +1,48 @@ +part of 'avoid_initializing_in_on_mount_rule.dart'; + +class _Visitor extends SimpleAstVisitor { + final _expressions = []; + + Iterable get expressions => _expressions; + + @override + void visitClassDeclaration(ClassDeclaration node) { + super.visitClassDeclaration(node); + + final type = node.extendsClause?.superclass.type; + if (type == null || !isComponentOrSubclass(type)) { + return; + } + + final onMountMethod = node.members.firstWhereOrNull((member) => + member is MethodDeclaration && + member.name.lexeme == 'onMount' && + isOverride(member.metadata)); + + if (onMountMethod is MethodDeclaration) { + final visitor = _AssignmentExpression(); + onMountMethod.visitChildren(visitor); + + _expressions.addAll(visitor.wrongAssignments); + } + } +} + +class _AssignmentExpression extends RecursiveAstVisitor { + final wrongAssignments = {}; + + @override + void visitAssignmentExpression(AssignmentExpression node) { + super.visitAssignmentExpression(node); + + final target = node.leftHandSide; + if (target is SimpleIdentifierImpl) { + final element = target.scopeLookupResult?.getter; + if (element is PropertyAccessorElement && + element.variable.isFinal && + element.variable.isLate) { + wrongAssignments.add(node); + } + } + } +} diff --git a/lib/src/utils/flame_type_utils.dart b/lib/src/utils/flame_type_utils.dart new file mode 100644 index 0000000000..78ddae8e17 --- /dev/null +++ b/lib/src/utils/flame_type_utils.dart @@ -0,0 +1,10 @@ +import 'package:analyzer/dart/element/type.dart'; + +bool isComponentOrSubclass(DartType? type) => + _isComponent(type) || _isSubclassOfComponent(type); + +bool _isComponent(DartType? type) => + type?.getDisplayString(withNullability: false) == 'Component'; + +bool _isSubclassOfComponent(DartType? type) => + type is InterfaceType && type.allSupertypes.any(_isComponent); diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule_test.dart new file mode 100644 index 0000000000..f66c9b9831 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule_test.dart @@ -0,0 +1,37 @@ +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _examplePath = 'avoid_initializing_in_on_mount/examples/example.dart'; + +void main() { + group('AvoidInitializingInOnMountRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidInitializingInOnMountRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'avoid-initializing-in-on-mount', + severity: Severity.warning, + ); + }); + + test('reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidInitializingInOnMountRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [8], + startColumns: [5], + locationTexts: ['x = 1'], + messages: [ + 'Avoid initializing final late variables in onMount.', + ], + ); + }); + }); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/examples/example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/examples/example.dart new file mode 100644 index 0000000000..60987e1141 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_initializing_in_on_mount/examples/example.dart @@ -0,0 +1,15 @@ +class MyComponent extends Component { + late final int x; + + int y; + + @override + void onMount() { + x = 1; // LINT + y = 2; + } +} + +class Component { + void onMount() {} +} From ae4620b7fd7b72c4c0d356c03878374abb941815 Mon Sep 17 00:00:00 2001 From: Dmitry Zhifarsky Date: Fri, 27 Jan 2023 20:38:04 +0400 Subject: [PATCH 3/4] feat: add avoid-creating-vector-in-update --- CHANGELOG.md | 1 + .../lint_analyzer/rules/rules_factory.dart | 2 + .../avoid_creating_vector_in_update_rule.dart | 46 +++++++++++++++ .../visitor.dart | 52 +++++++++++++++++ lib/src/utils/flame_type_utils.dart | 3 + ...d_creating_vector_in_update_rule_test.dart | 43 ++++++++++++++ .../examples/example.dart | 58 +++++++++++++++++++ 7 files changed, 205 insertions(+) create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/visitor.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule_test.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/examples/example.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ca30bf1c..57e5714e09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * fix: export missing parts of public API. * feat: add static code diagnostic [`correct-game-instantiating`](https://dcm.dev/docs/individuals/rules/flame/correct-game-instantiating). * feat: add static code diagnostic [`avoid-initializing-in-on-mount`](https://dcm.dev/docs/individuals/rules/flame/avoid-initializing-in-on-mount). +* feat: add static code diagnostic [`avoid-creating-vector-in-update`](https://dcm.dev/docs/individuals/rules/flame/avoid-creating-vector-in-update). ## 5.5.0 diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index 45c61a406d..8569d057d7 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -5,6 +5,7 @@ import 'rules_list/avoid_banned_imports/avoid_banned_imports_rule.dart'; import 'rules_list/avoid_border_all/avoid_border_all_rule.dart'; import 'rules_list/avoid_cascade_after_if_null/avoid_cascade_after_if_null_rule.dart'; import 'rules_list/avoid_collection_methods_with_unrelated_types/avoid_collection_methods_with_unrelated_types_rule.dart'; +import 'rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule.dart'; import 'rules_list/avoid_double_slash_imports/avoid_double_slash_imports_rule.dart'; import 'rules_list/avoid_duplicate_exports/avoid_duplicate_exports_rule.dart'; import 'rules_list/avoid_dynamic/avoid_dynamic_rule.dart'; @@ -84,6 +85,7 @@ final _implementedRules = )>{ AvoidCascadeAfterIfNullRule.ruleId: AvoidCascadeAfterIfNullRule.new, AvoidCollectionMethodsWithUnrelatedTypesRule.ruleId: AvoidCollectionMethodsWithUnrelatedTypesRule.new, + AvoidCreatingVectorInUpdateRule.ruleId: AvoidCreatingVectorInUpdateRule.new, AvoidDoubleSlashImportsRule.ruleId: AvoidDoubleSlashImportsRule.new, AvoidDuplicateExportsRule.ruleId: AvoidDuplicateExportsRule.new, AvoidDynamicRule.ruleId: AvoidDynamicRule.new, diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule.dart new file mode 100644 index 0000000000..9a63dfac39 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule.dart @@ -0,0 +1,46 @@ +// ignore_for_file: public_member_api_docs + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +// ignore: implementation_imports +import 'package:collection/collection.dart'; + +import '../../../../../utils/flame_type_utils.dart'; +import '../../../../../utils/node_utils.dart'; +import '../../../lint_utils.dart'; +import '../../../models/internal_resolved_unit_result.dart'; +import '../../../models/issue.dart'; +import '../../../models/severity.dart'; +import '../../models/flame_rule.dart'; +import '../../rule_utils.dart'; + +part 'visitor.dart'; + +class AvoidCreatingVectorInUpdateRule extends FlameRule { + static const String ruleId = 'avoid-creating-vector-in-update'; + + static const _warningMessage = "Avoid creating Vector2 in 'update' method."; + + AvoidCreatingVectorInUpdateRule([Map config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + includes: readIncludes(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, + )) + .toList(growable: false); + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/visitor.dart new file mode 100644 index 0000000000..6833a152bb --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/visitor.dart @@ -0,0 +1,52 @@ +part of 'avoid_creating_vector_in_update_rule.dart'; + +class _Visitor extends SimpleAstVisitor { + final _expressions = []; + + Iterable get expressions => _expressions; + + @override + void visitClassDeclaration(ClassDeclaration node) { + super.visitClassDeclaration(node); + + final type = node.extendsClause?.superclass.type; + if (type == null || !isComponentOrSubclass(type)) { + return; + } + + final updateMethod = node.members.firstWhereOrNull((member) => + member is MethodDeclaration && + member.name.lexeme == 'update' && + isOverride(member.metadata)); + + if (updateMethod is MethodDeclaration) { + final visitor = _VectorVisitor(); + updateMethod.visitChildren(visitor); + + _expressions.addAll(visitor.wrongExpressions); + } + } +} + +class _VectorVisitor extends RecursiveAstVisitor { + final wrongExpressions = {}; + + @override + void visitInstanceCreationExpression(InstanceCreationExpression node) { + super.visitInstanceCreationExpression(node); + + if (isVector(node.staticType)) { + wrongExpressions.add(node); + } + } + + @override + void visitBinaryExpression(BinaryExpression node) { + super.visitBinaryExpression(node); + + if (isVector(node.leftOperand.staticType) && + isVector(node.rightOperand.staticType)) { + wrongExpressions.add(node); + } + } +} diff --git a/lib/src/utils/flame_type_utils.dart b/lib/src/utils/flame_type_utils.dart index 78ddae8e17..9ee02b3251 100644 --- a/lib/src/utils/flame_type_utils.dart +++ b/lib/src/utils/flame_type_utils.dart @@ -3,6 +3,9 @@ import 'package:analyzer/dart/element/type.dart'; bool isComponentOrSubclass(DartType? type) => _isComponent(type) || _isSubclassOfComponent(type); +bool isVector(DartType? type) => + type?.getDisplayString(withNullability: false) == 'Vector2'; + bool _isComponent(DartType? type) => type?.getDisplayString(withNullability: false) == 'Component'; diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule_test.dart new file mode 100644 index 0000000000..e14cece7f3 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule_test.dart @@ -0,0 +1,43 @@ +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/avoid_creating_vector_in_update_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _examplePath = 'avoid_creating_vector_in_update/examples/example.dart'; + +void main() { + group('AvoidCreatingVectorInUpdateRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidCreatingVectorInUpdateRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'avoid-creating-vector-in-update', + severity: Severity.warning, + ); + }); + + test('reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidCreatingVectorInUpdateRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [4, 23, 24], + startColumns: [23, 23, 23], + locationTexts: [ + 'Vector2(10, 10)', + 'vector1 + vector2', + 'vector1 - vector2', + ], + messages: [ + "Avoid creating Vector2 in 'update' method.", + "Avoid creating Vector2 in 'update' method.", + "Avoid creating Vector2 in 'update' method.", + ], + ); + }); + }); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/examples/example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/examples/example.dart new file mode 100644 index 0000000000..be03108d30 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_creating_vector_in_update/examples/example.dart @@ -0,0 +1,58 @@ +class MyComponent extends Component { + @override + void update(double dt) { + final newVector = Vector2(10, 10); // LINT + } +} + +class MyComponent extends Component { + final _temporaryVector = Vector2.zero(); + + @override + void update(double dt) { + _temporaryVector.setValues(10, 10); + } +} + +class MyComponent extends Component { + final vector1 = Vector2(10, 10); + final vector2 = Vector2(20, 20); + + @override + void update(double dt) { + final addVector = vector1 + vector2; // LINT + final subVector = vector1 - vector2; // LINT + } +} + +class Component { + void onMount() {} +} + +class Vector2 { + final double x; + final double y; + + const Vector2(this.x, this.y); + + const Vector2.zero() + : x = 0, + y = 0; + + void setValues(double x, double y) { + this.x = x; + this.y = y; + } + + @override + Vector2 operator /(double scale) => this; + + @override + Vector2 operator +(double scale) => this; + + @override + Vector2 operator -(double scale) => this; + + @override + Vector2 operator *(double scale) => this; +} From d173e98d4dc13fc22fbe49b12f98be7b978dc12d Mon Sep 17 00:00:00 2001 From: Dmitry Zhifarsky Date: Fri, 27 Jan 2023 21:46:28 +0400 Subject: [PATCH 4/4] feat: add static code diagnostic avoid-redundant-async-on-load --- CHANGELOG.md | 1 + .../lint_analyzer/rules/rules_factory.dart | 2 + .../avoid_redundant_async_rule.dart | 5 +- .../avoid_redundant_async_on_load_rule.dart | 46 ++++++++++++ .../visitor.dart | 70 +++++++++++++++++++ .../avoid_redundant_async_rule_test.dart | 2 +- ...oid_redundant_async_on_load_rule_test.dart | 49 +++++++++++++ .../examples/example.dart | 61 ++++++++++++++++ 8 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/visitor.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule_test.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/examples/example.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e5714e09..5417559484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * feat: add static code diagnostic [`correct-game-instantiating`](https://dcm.dev/docs/individuals/rules/flame/correct-game-instantiating). * feat: add static code diagnostic [`avoid-initializing-in-on-mount`](https://dcm.dev/docs/individuals/rules/flame/avoid-initializing-in-on-mount). * feat: add static code diagnostic [`avoid-creating-vector-in-update`](https://dcm.dev/docs/individuals/rules/flame/avoid-creating-vector-in-update). +* feat: add static code diagnostic [`avoid-redundant-async-on-load`](https://dcm.dev/docs/individuals/rules/flame/avoid-redundant-async-on-load). ## 5.5.0 diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index 8569d057d7..214862b889 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -21,6 +21,7 @@ import 'rules_list/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart'; import 'rules_list/avoid_passing_async_when_sync_expected/avoid_passing_async_when_sync_expected_rule.dart'; import 'rules_list/avoid_preserve_whitespace_false/avoid_preserve_whitespace_false_rule.dart'; import 'rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart'; +import 'rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart'; import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; import 'rules_list/avoid_shrink_wrap_in_lists/avoid_shrink_wrap_in_lists_rule.dart'; import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart'; @@ -103,6 +104,7 @@ final _implementedRules = )>{ AvoidPassingAsyncWhenSyncExpectedRule.new, AvoidPreserveWhitespaceFalseRule.ruleId: AvoidPreserveWhitespaceFalseRule.new, AvoidRedundantAsyncRule.ruleId: AvoidRedundantAsyncRule.new, + AvoidRedundantAsyncOnLoadRule.ruleId: AvoidRedundantAsyncOnLoadRule.new, AvoidReturningWidgetsRule.ruleId: AvoidReturningWidgetsRule.new, AvoidShrinkWrapInListsRule.ruleId: AvoidShrinkWrapInListsRule.new, AvoidThrowInCatchBlockRule.ruleId: AvoidThrowInCatchBlockRule.new, diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart index 8a9a6fee1f..c2eb4a8452 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart @@ -38,10 +38,7 @@ class AvoidRedundantAsyncRule extends CommonRule { return visitor.nodes.map( (node) => createIssue( rule: this, - location: nodeLocation( - node: node, - source: source, - ), + location: nodeLocation(node: node, source: source), message: _warningMessage, ), ); diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart new file mode 100644 index 0000000000..327f6a0456 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart @@ -0,0 +1,46 @@ +// 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/type.dart'; +import 'package:collection/collection.dart'; + +import '../../../../../utils/flame_type_utils.dart'; +import '../../../../../utils/node_utils.dart'; +import '../../../lint_utils.dart'; +import '../../../models/internal_resolved_unit_result.dart'; +import '../../../models/issue.dart'; +import '../../../models/severity.dart'; +import '../../models/flame_rule.dart'; +import '../../rule_utils.dart'; + +part 'visitor.dart'; + +class AvoidRedundantAsyncOnLoadRule extends FlameRule { + static const String ruleId = 'avoid-redundant-async-on-load'; + + static const _warningMessage = "Avoid unnecessary async 'onLoad'."; + + AvoidRedundantAsyncOnLoadRule([Map config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + includes: readIncludes(config), + ); + + @override + Iterable check(InternalResolvedUnitResult source) { + final visitor = _Visitor(); + + source.unit.visitChildren(visitor); + + return visitor.declarations + .map((declaration) => createIssue( + rule: this, + location: nodeLocation(node: declaration, source: source), + message: _warningMessage, + )) + .toList(growable: false); + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/visitor.dart new file mode 100644 index 0000000000..15ed1a3776 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/visitor.dart @@ -0,0 +1,70 @@ +part of 'avoid_redundant_async_on_load_rule.dart'; + +class _Visitor extends SimpleAstVisitor { + final _declarations = []; + + Iterable get declarations => _declarations; + + @override + void visitClassDeclaration(ClassDeclaration node) { + super.visitClassDeclaration(node); + + final type = node.extendsClause?.superclass.type; + if (type == null || !isComponentOrSubclass(type)) { + return; + } + + final onLoadMethod = node.members.firstWhereOrNull((member) => + member is MethodDeclaration && + member.name.lexeme == 'onLoad' && + isOverride(member.metadata)); + + if (onLoadMethod is MethodDeclaration) { + if (_hasFutureType(onLoadMethod.returnType?.type) && + _hasRedundantAsync(onLoadMethod.body)) { + _declarations.add(onLoadMethod); + } + } + } + + bool _hasFutureType(DartType? type) => + type != null && (type.isDartAsyncFuture || type.isDartAsyncFutureOr); + + bool _hasRedundantAsync(FunctionBody body) { + if (body is ExpressionFunctionBody) { + final type = body.expression.staticType; + + if (type != null && + (type.isDartAsyncFuture || type.isDartAsyncFutureOr)) { + return false; + } + } + + final asyncVisitor = _AsyncVisitor(body); + body.parent?.visitChildren(asyncVisitor); + + return !asyncVisitor.hasValidAsync; + } +} + +class _AsyncVisitor extends RecursiveAstVisitor { + final FunctionBody body; + + bool hasValidAsync = false; + + _AsyncVisitor(this.body); + + @override + void visitReturnStatement(ReturnStatement node) { + super.visitReturnStatement(node); + + hasValidAsync = true; + } + + @override + void visitAwaitExpression(AwaitExpression node) { + super.visitAwaitExpression(node); + + hasValidAsync = true; + } +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule_test.dart index 0ccf557ae1..5700757dab 100644 --- a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule_test.dart +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async/avoid_redundant_async_rule_test.dart @@ -19,7 +19,7 @@ void main() { ); }); - test('reports about found issues with the default config', () async { + test('reports about found issues', () async { final unit = await RuleTestHelper.resolveFromFile(_examplePath); final issues = AvoidRedundantAsyncRule().check(unit); diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule_test.dart new file mode 100644 index 0000000000..e9c66017fd --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule_test.dart @@ -0,0 +1,49 @@ +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _examplePath = 'avoid_redundant_async_on_load/examples/example.dart'; + +void main() { + group('AvoidRedundantAsyncOnLoadRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidRedundantAsyncOnLoadRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'avoid-redundant-async-on-load', + severity: Severity.warning, + ); + }); + + test('reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidRedundantAsyncOnLoadRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [6, 14, 22], + startColumns: [3, 3, 3], + locationTexts: [ + 'Future onLoad() {\n' + " print('hello');\n" + ' }', + 'FutureOr onLoad() {\n' + " print('hello');\n" + ' }', + 'FutureOr onLoad() async {\n' + " print('hello');\n" + ' }', + ], + messages: [ + "Avoid unnecessary async 'onLoad'.", + "Avoid unnecessary async 'onLoad'.", + "Avoid unnecessary async 'onLoad'.", + ], + ); + }); + }); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/examples/example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/examples/example.dart new file mode 100644 index 0000000000..f2ee125235 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_redundant_async_on_load/examples/example.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +class MyComponent extends Component { + // LINT + @override + Future onLoad() { + print('hello'); + } +} + +class MyComponent extends Component { + // LINT + @override + FutureOr onLoad() { + print('hello'); + } +} + +class MyComponent extends Component { + // LINT + @override + FutureOr onLoad() async { + print('hello'); + } +} + +class MyComponent extends Component { + @override + Future onLoad() async { + await someAsyncMethod(); + } + + Future someAsyncMethod() => Future.value(null); +} + +class MyComponent extends Component { + @override + Future onLoad() async { + return someAsyncMethod(); + } + + Future someAsyncMethod() => Future.value(null); +} + +class MyComponent extends Component { + @override + Future onLoad() => someAsyncMethod(); + + Future someAsyncMethod() => Future.value(null); +} + +class MyComponent extends Component { + @override + void onLoad() { + print('hello'); + } +} + +class Component { + FutureOr onLoad() {} +}