From 2a437b1c2c485f9f67cfb238466fb657e9c504f8 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Mon, 22 Jun 2026 16:59:22 +0300 Subject: [PATCH] refactor: migrate member_ordering rule --- lib/main.dart | 4 + .../lints/member_ordering/config_parser.dart | 1 - .../member_ordering/member_ordering_rule.dart | 172 ++--- .../member_ordering/models/member_info.dart | 17 + .../member_ordering/models/member_names.dart | 22 + .../member_ordering/models/member_order.dart | 33 + .../models/member_ordering_parameters.dart | 8 + .../member_ordering/models/member_type.dart | 2 +- .../visitors/member_ordering_visitor.dart | 262 ++++---- lib/src/models/rule_parameters_parser.dart | 2 + lib/src/models/solid_lint_rule.dart | 4 +- lib/src/models/solid_multi_lint_rule.dart | 37 ++ lint_test/member_ordering_test.dart | 232 ------- .../member_ordering_rule_test.dart | 620 ++++++++++++++++++ 14 files changed, 927 insertions(+), 489 deletions(-) create mode 100644 lib/src/lints/member_ordering/models/member_info.dart create mode 100644 lib/src/lints/member_ordering/models/member_names.dart create mode 100644 lib/src/lints/member_ordering/models/member_order.dart create mode 100644 lib/src/models/rule_parameters_parser.dart create mode 100644 lib/src/models/solid_multi_lint_rule.dart delete mode 100644 lint_test/member_ordering_test.dart create mode 100644 test/src/lints/member_ordering/member_ordering_rule_test.dart diff --git a/lib/main.dart b/lib/main.dart index 708b9fce..5bb7e654 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/fixes/av import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart'; +import 'package:solid_lints/src/lints/member_ordering/member_ordering_rule.dart'; import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart'; /// The entry point for the Solid Lints analyser server plugin. @@ -49,6 +50,9 @@ class SolidLintsPlugin extends Plugin { AvoidUnusedParametersRule( analysisOptionsLoader: analysisLoader, ), + MemberOrderingRule( + analysisOptionsLoader: analysisLoader, + ), ]; for (final lintRule in lintRules) { diff --git a/lib/src/lints/member_ordering/config_parser.dart b/lib/src/lints/member_ordering/config_parser.dart index 2d847d71..0bf0c5e2 100644 --- a/lib/src/lints/member_ordering/config_parser.dart +++ b/lib/src/lints/member_ordering/config_parser.dart @@ -54,7 +54,6 @@ class MemberOrderingConfigParser { 'final_fields', 'init_state_method', 'var_fields', - 'init_state_method', 'private_methods', 'overridden_public_methods', 'build_method', diff --git a/lib/src/lints/member_ordering/member_ordering_rule.dart b/lib/src/lints/member_ordering/member_ordering_rule.dart index 3f83f1c1..0c44f6bb 100644 --- a/lib/src/lints/member_ordering/member_ordering_rule.dart +++ b/lib/src/lints/member_ordering/member_ordering_rule.dart @@ -1,9 +1,9 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; +import 'package:analyzer/error/error.dart'; import 'package:solid_lints/src/lints/member_ordering/models/member_ordering_parameters.dart'; import 'package:solid_lints/src/lints/member_ordering/visitors/member_ordering_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; -import 'package:solid_lints/src/models/solid_lint_rule.dart'; +import 'package:solid_lints/src/models/solid_multi_lint_rule.dart'; /// A lint which allows to enforce a particular class member ordering /// conventions. @@ -45,14 +45,15 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// Assuming config: /// /// ```yaml -/// custom_lint: -/// rules: -/// - member_ordering: -/// alphabetize: true -/// order: -/// - fields -/// - getters_setters -/// - methods +/// plugins: +/// solid_lints: +/// diagnostics: +/// member_ordering: +/// alphabetize: true +/// order: +/// - fields +/// - getters_setters +/// - methods /// ``` /// /// #### BAD: @@ -82,113 +83,60 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// void method() {} /// } /// ``` -class MemberOrderingRule extends SolidLintRule { - /// This lint rule represents - /// the error whether we use bad formatted double literals. +class MemberOrderingRule extends SolidMultiLintRule { + /// The name of this lint rule. static const lintName = 'member_ordering'; - static const _warningMessage = 'should be before'; - static const _warningAlphabeticalMessage = 'should be alphabetically before'; - static const _warningTypeAlphabeticalMessage = - 'type name should be alphabetically before'; + /// Reported when a class member is declared out of order. + static const wrongOrderCode = LintCode( + lintName, + '{0} should be before {1}.', + uniqueName: 'wrong_order', + ); + + /// Reported when class members in the same group are not sorted + /// alphabetically. + static const alphabeticalOrderCode = LintCode( + lintName, + '{0} should be alphabetically before {1}.', + uniqueName: 'alphabetical_order', + ); + + /// Reported when class members in the same group are not sorted + /// alphabetically by type name. + static const alphabeticalByTypeOrderCode = LintCode( + lintName, + '{0} type name should be alphabetically before {1}.', + uniqueName: 'alphabetical_by_type_order', + ); + + /// Creates a new instance of [MemberOrderingRule]. + MemberOrderingRule({ + required super.analysisOptionsLoader, + }) : super( + name: lintName, + description: 'Enforces a particular class member ordering convention.', + parametersParser: MemberOrderingParameters.fromJson, + ); - MemberOrderingRule._(super.config); - - /// Creates a new instance of [MemberOrderingRule] - /// based on the lint configuration. - factory MemberOrderingRule.createRule(CustomLintConfigs configs) { - final config = RuleConfig( - configs: configs, - name: lintName, - paramsParser: MemberOrderingParameters.fromJson, - problemMessage: (_) => "Order of class member is wrong", - ); - - return MemberOrderingRule._(config); - } + @override + List get diagnosticCodes => [ + wrongOrderCode, + alphabeticalOrderCode, + alphabeticalByTypeOrderCode, + ]; @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addClassDeclaration((node) { - final visitor = MemberOrderingVisitor( - config.parameters.groupsOrder, - config.parameters.widgetsGroupsOrder, - ); - - final membersInfo = visitor.visitClassDeclaration(node); - final wrongOrderMembers = membersInfo.where( - (info) => info.memberOrder.isWrong, - ); - - for (final memberInfo in wrongOrderMembers) { - reporter.atNode( - memberInfo.classMember, - _createWrongOrderLintCode(memberInfo), - ); - } - - if (config.parameters.alphabetize) { - final alphabeticallyWrongOrderMembers = membersInfo.where( - (info) => info.memberOrder.isAlphabeticallyWrong, - ); - - for (final memberInfo in alphabeticallyWrongOrderMembers) { - reporter.atNode( - memberInfo.classMember, - _createAlphabeticallyWrongOrderLintCode(memberInfo), - ); - } - } - - if (!config.parameters.alphabetize && - config.parameters.alphabetizeByType) { - final alphabeticallyByTypeWrongOrderMembers = membersInfo.where( - (info) => info.memberOrder.isByTypeWrong, - ); - - for (final memberInfo in alphabeticallyByTypeWrongOrderMembers) { - reporter.atNode( - memberInfo.classMember, - _createAlphabeticallyByTypeWrongOrderLintCode(memberInfo), - ); - } - } - }); - } - - LintCode _createWrongOrderLintCode(MemberInfo info) { - final memberGroup = info.memberOrder.memberGroup; - final previousMemberGroup = info.memberOrder.previousMemberGroup; - - return LintCode( - name: lintName, - problemMessage: "$memberGroup $_warningMessage $previousMemberGroup.", - ); - } - - LintCode _createAlphabeticallyWrongOrderLintCode(MemberInfo info) { - final names = info.memberOrder.memberNames; - final current = names.currentName; - final previous = names.previousName; - - return LintCode( - name: lintName, - problemMessage: "$current $_warningAlphabeticalMessage $previous.", - ); - } + super.registerNodeProcessors(registry, context); - LintCode _createAlphabeticallyByTypeWrongOrderLintCode(MemberInfo info) { - final names = info.memberOrder.memberNames; - final current = names.currentName; - final previous = names.previousName; + final parameters = + getParametersForContext(context) ?? MemberOrderingParameters.empty(); + final visitor = MemberOrderingVisitor(this, parameters); - return LintCode( - name: lintName, - problemMessage: "$current $_warningTypeAlphabeticalMessage $previous", - ); + registry.addClassDeclaration(this, visitor); } } diff --git a/lib/src/lints/member_ordering/models/member_info.dart b/lib/src/lints/member_ordering/models/member_info.dart new file mode 100644 index 00000000..56a7fd4e --- /dev/null +++ b/lib/src/lints/member_ordering/models/member_info.dart @@ -0,0 +1,17 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:solid_lints/src/lints/member_ordering/models/member_order.dart'; + +/// Data class that holds AST class member and it's order info +class MemberInfo { + /// AST instance of an [ClassMember] + final ClassMember classMember; + + /// Class member order info + final MemberOrder memberOrder; + + /// Creates instance of an [MemberInfo] + const MemberInfo({ + required this.classMember, + required this.memberOrder, + }); +} diff --git a/lib/src/lints/member_ordering/models/member_names.dart b/lib/src/lints/member_ordering/models/member_names.dart new file mode 100644 index 00000000..b24b333a --- /dev/null +++ b/lib/src/lints/member_ordering/models/member_names.dart @@ -0,0 +1,22 @@ +/// Data class contains info about current and previous class member names +class MemberNames { + /// Name of current class member + final String currentName; + + /// Name of previous class member + final String? previousName; + + /// Type name of current class member + final String currentTypeName; + + /// Type name of previous class member + final String? previousTypeName; + + /// Creates instance of [MemberNames] + const MemberNames({ + required this.currentName, + required this.currentTypeName, + this.previousName, + this.previousTypeName, + }); +} diff --git a/lib/src/lints/member_ordering/models/member_order.dart b/lib/src/lints/member_ordering/models/member_order.dart new file mode 100644 index 00000000..8ec59a5b --- /dev/null +++ b/lib/src/lints/member_ordering/models/member_order.dart @@ -0,0 +1,33 @@ +import 'package:solid_lints/src/lints/member_ordering/models/member_group/member_group.dart'; +import 'package:solid_lints/src/lints/member_ordering/models/member_names.dart'; + +/// Data class holds information about class member order info +class MemberOrder { + /// Indicates if order is wrong + final bool isWrong; + + /// Indicates if order is wrong alphabetically + final bool isAlphabeticallyWrong; + + /// Indicates if order is wrong alphabetically by type + final bool isByTypeWrong; + + /// Info about current and previous class member name + final MemberNames memberNames; + + /// Info about current member member group + final MemberGroup memberGroup; + + /// Info about previous member member group + final MemberGroup? previousMemberGroup; + + /// Creates instance of [MemberOrder] + const MemberOrder({ + required this.isWrong, + required this.isAlphabeticallyWrong, + required this.isByTypeWrong, + required this.memberNames, + required this.memberGroup, + this.previousMemberGroup, + }); +} diff --git a/lib/src/lints/member_ordering/models/member_ordering_parameters.dart b/lib/src/lints/member_ordering/models/member_ordering_parameters.dart index 129ccf6a..647abec5 100644 --- a/lib/src/lints/member_ordering/models/member_ordering_parameters.dart +++ b/lib/src/lints/member_ordering/models/member_ordering_parameters.dart @@ -54,6 +54,14 @@ class MemberOrderingParameters { required this.alphabetizeByType, }); + /// Factory for creating empty/default parameters. + factory MemberOrderingParameters.empty() => MemberOrderingParameters( + groupsOrder: MemberOrderingConfigParser.parseOrder(null), + widgetsGroupsOrder: MemberOrderingConfigParser.parseWidgetsOrder(null), + alphabetize: false, + alphabetizeByType: false, + ); + /// Method for creating from json data factory MemberOrderingParameters.fromJson(Map json) => MemberOrderingParameters( diff --git a/lib/src/lints/member_ordering/models/member_type.dart b/lib/src/lints/member_ordering/models/member_type.dart index f7d01737..f08b7703 100644 --- a/lib/src/lints/member_ordering/models/member_type.dart +++ b/lib/src/lints/member_ordering/models/member_type.dart @@ -32,7 +32,7 @@ enum MemberType { method('methods', typeAlias: 'method'), /// Indicates constructor affiliation - constructor('constructors'), + constructor('constructors', typeAlias: 'constructor'), /// Indicates getters affiliation getter('getters'), diff --git a/lib/src/lints/member_ordering/visitors/member_ordering_visitor.dart b/lib/src/lints/member_ordering/visitors/member_ordering_visitor.dart index 6b2035d0..ddf5d095 100644 --- a/lib/src/lints/member_ordering/visitors/member_ordering_visitor.dart +++ b/lib/src/lints/member_ordering/visitors/member_ordering_visitor.dart @@ -24,6 +24,7 @@ import 'package:analyzer/dart/ast/ast.dart' hide Annotation; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:collection/collection.dart'; +import 'package:solid_lints/src/lints/member_ordering/member_ordering_rule.dart'; import 'package:solid_lints/src/lints/member_ordering/models/annotation.dart'; import 'package:solid_lints/src/lints/member_ordering/models/field_keyword.dart'; import 'package:solid_lints/src/lints/member_ordering/models/member_group/constructor_member_group.dart'; @@ -31,25 +32,27 @@ import 'package:solid_lints/src/lints/member_ordering/models/member_group/field_ import 'package:solid_lints/src/lints/member_ordering/models/member_group/get_set_member_group.dart'; import 'package:solid_lints/src/lints/member_ordering/models/member_group/member_group.dart'; import 'package:solid_lints/src/lints/member_ordering/models/member_group/method_member_group.dart'; +import 'package:solid_lints/src/lints/member_ordering/models/member_info.dart'; +import 'package:solid_lints/src/lints/member_ordering/models/member_names.dart'; +import 'package:solid_lints/src/lints/member_ordering/models/member_order.dart'; +import 'package:solid_lints/src/lints/member_ordering/models/member_ordering_parameters.dart'; import 'package:solid_lints/src/lints/member_ordering/models/member_type.dart'; import 'package:solid_lints/src/lints/member_ordering/models/modifier.dart'; import 'package:solid_lints/src/utils/types_utils.dart'; /// AST Visitor which finds all class members and checks if they are /// in order provided from rule config or default config -class MemberOrderingVisitor extends RecursiveAstVisitor> { - final List _groupsOrder; - final List _widgetsGroupsOrder; +class MemberOrderingVisitor extends SimpleAstVisitor { + final MemberOrderingRule _rule; + final MemberOrderingParameters _parameters; final _membersInfo = []; /// Creates instance of [MemberOrderingVisitor] - /// [_groupsOrder] config is used for regular classes - /// [_widgetsGroupsOrder] config is used for widget classes - MemberOrderingVisitor(this._groupsOrder, this._widgetsGroupsOrder); + MemberOrderingVisitor(this._rule, this._parameters); @override - List visitClassDeclaration(ClassDeclaration node) { + void visitClassDeclaration(ClassDeclaration node) { super.visitClassDeclaration(node); _membersInfo.clear(); @@ -58,17 +61,72 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { final isFlutterWidget = isWidgetOrSubclass(type) || isWidgetStateOrSubclass(type); - for (final member in node.members) { - if (member is FieldDeclaration) { - _visitFieldDeclaration(member, isFlutterWidget); - } else if (member is ConstructorDeclaration) { - _visitConstructorDeclaration(member, isFlutterWidget); - } else if (member is MethodDeclaration) { - _visitMethodDeclaration(member, isFlutterWidget); + final body = node.body; + if (body is BlockClassBody) { + for (final member in body.members) { + if (member is FieldDeclaration) { + _visitFieldDeclaration(member, isFlutterWidget); + } else if (member is ConstructorDeclaration) { + _visitConstructorDeclaration(member, isFlutterWidget); + } else if (member is MethodDeclaration) { + _visitMethodDeclaration(member, isFlutterWidget); + } } } - return _membersInfo; + final wrongOrderMembers = _membersInfo.where( + (info) => info.memberOrder.isWrong, + ); + + for (final memberInfo in wrongOrderMembers) { + final memberGroup = memberInfo.memberOrder.memberGroup; + final previousMemberGroup = memberInfo.memberOrder.previousMemberGroup; + + _rule.reportAtNode( + memberInfo.classMember, + diagnosticCode: MemberOrderingRule.wrongOrderCode, + arguments: [ + memberGroup.toString(), + previousMemberGroup?.toString() ?? '', + ], + ); + } + + if (_parameters.alphabetize) { + final alphabeticallyWrongOrderMembers = _membersInfo.where( + (info) => info.memberOrder.isAlphabeticallyWrong, + ); + + for (final memberInfo in alphabeticallyWrongOrderMembers) { + final names = memberInfo.memberOrder.memberNames; + _rule.reportAtNode( + memberInfo.classMember, + diagnosticCode: MemberOrderingRule.alphabeticalOrderCode, + arguments: [ + names.currentName, + names.previousName ?? '', + ], + ); + } + } + + if (!_parameters.alphabetize && _parameters.alphabetizeByType) { + final alphabeticallyByTypeWrongOrderMembers = _membersInfo.where( + (info) => info.memberOrder.isByTypeWrong, + ); + + for (final memberInfo in alphabeticallyByTypeWrongOrderMembers) { + final names = memberInfo.memberOrder.memberNames; + _rule.reportAtNode( + memberInfo.classMember, + diagnosticCode: MemberOrderingRule.alphabeticalByTypeOrderCode, + arguments: [ + names.currentName, + names.previousName ?? '', + ], + ); + } + } } void _visitFieldDeclaration( @@ -107,7 +165,7 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { memberOrder: _getOrder( closestGroup, declaration.name?.lexeme ?? '', - declaration.returnType.name, + declaration.typeName?.name ?? '', isFlutterWidget, ), ), @@ -119,40 +177,23 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { MethodDeclaration declaration, bool isFlutterWidget, ) { - if (declaration.isGetter || declaration.isSetter) { - final group = GetSetMemberGroup.parse(declaration); - final closestGroup = _getClosestGroup(group, isFlutterWidget); - - if (closestGroup != null) { - _membersInfo.add( - MemberInfo( - classMember: declaration, - memberOrder: _getOrder( - closestGroup, - declaration.name.lexeme, - declaration.returnType?.type?.getDisplayString() ?? '_', - isFlutterWidget, - ), - ), - ); - } - } else { - final group = MethodMemberGroup.parse(declaration); - final closestGroup = _getClosestGroup(group, isFlutterWidget); - - if (closestGroup != null) { - _membersInfo.add( - MemberInfo( - classMember: declaration, - memberOrder: _getOrder( - closestGroup, - declaration.name.lexeme, - declaration.returnType?.type?.getDisplayString() ?? '_', - isFlutterWidget, - ), + final group = (declaration.isGetter || declaration.isSetter) + ? GetSetMemberGroup.parse(declaration) + : MethodMemberGroup.parse(declaration); + final closestGroup = _getClosestGroup(group, isFlutterWidget); + + if (closestGroup != null) { + _membersInfo.add( + MemberInfo( + classMember: declaration, + memberOrder: _getOrder( + closestGroup, + declaration.name.lexeme, + declaration.returnType?.type?.getDisplayString() ?? '_', + isFlutterWidget, ), - ); - } + ), + ); } } @@ -160,17 +201,20 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { MemberGroup parsedGroup, bool isFlutterWidget, ) { - final closestGroups = (isFlutterWidget ? _widgetsGroupsOrder : _groupsOrder) - .where( - (group) => - _isConstructorGroup(group, parsedGroup) || - _isFieldGroup(group, parsedGroup) || - _isGetSetGroup(group, parsedGroup) || - _isMethodGroup(group, parsedGroup), - ) - .sorted( - (a, b) => b.getSortingCoefficient() - a.getSortingCoefficient(), - ); + final closestGroups = + (isFlutterWidget + ? _parameters.widgetsGroupsOrder + : _parameters.groupsOrder) + .where( + (group) => + _isConstructorGroup(group, parsedGroup) || + _isFieldGroup(group, parsedGroup) || + _isGetSetGroup(group, parsedGroup) || + _isMethodGroup(group, parsedGroup), + ) + .sorted( + (a, b) => b.getSortingCoefficient() - a.getSortingCoefficient(), + ); return closestGroups.firstOrNull; } @@ -187,8 +231,8 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { final previousMemberGroup = hasSameGroup && lastMemberOrder.previousMemberGroup != null - ? lastMemberOrder.previousMemberGroup - : lastMemberOrder.memberGroup; + ? lastMemberOrder.previousMemberGroup + : lastMemberOrder.memberGroup; final memberNames = MemberNames( currentName: memberName, @@ -199,16 +243,19 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { return MemberOrder( memberNames: memberNames, - isAlphabeticallyWrong: hasSameGroup && + isAlphabeticallyWrong: + hasSameGroup && memberNames.currentName.compareTo(memberNames.previousName!) < 0, - isByTypeWrong: hasSameGroup && - memberNames.currentTypeName - .toLowerCase() - .compareTo(memberNames.previousTypeName!.toLowerCase()) < + isByTypeWrong: + hasSameGroup && + memberNames.currentTypeName.toLowerCase().compareTo( + memberNames.previousTypeName!.toLowerCase(), + ) < 0, memberGroup: memberGroup, previousMemberGroup: previousMemberGroup, - isWrong: (hasSameGroup && lastMemberOrder.isWrong) || + isWrong: + (hasSameGroup && lastMemberOrder.isWrong) || _isCurrentGroupBefore( lastMemberOrder.memberGroup, memberGroup, @@ -218,8 +265,10 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { } return MemberOrder( - memberNames: - MemberNames(currentName: memberName, currentTypeName: typeName), + memberNames: MemberNames( + currentName: memberName, + currentTypeName: typeName, + ), isAlphabeticallyWrong: false, isByTypeWrong: false, memberGroup: memberGroup, @@ -232,7 +281,9 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { MemberGroup memberGroup, bool isFlutterWidget, ) { - final group = isFlutterWidget ? _widgetsGroupsOrder : _groupsOrder; + final group = isFlutterWidget + ? _parameters.widgetsGroupsOrder + : _parameters.groupsOrder; return group.indexOf(lastMemberGroup) > group.indexOf(memberGroup); } @@ -285,72 +336,3 @@ class MemberOrderingVisitor extends RecursiveAstVisitor> { (group.annotation == Annotation.unset || group.annotation == parsedGroup.annotation); } - -/// Data class that holds AST class member and it's order info -class MemberInfo { - /// AST instance of an [ClassMember] - final ClassMember classMember; - - /// Class member order info - final MemberOrder memberOrder; - - /// Creates instance of an [MemberInfo] - const MemberInfo({ - required this.classMember, - required this.memberOrder, - }); -} - -/// Data class holds information about class member order info -class MemberOrder { - /// Indicates if order is wrong - final bool isWrong; - - /// Indicates if order is wrong alphabetically - final bool isAlphabeticallyWrong; - - /// Indicates if order is wrong alphabetically by type - final bool isByTypeWrong; - - /// Info about current and previous class member name - final MemberNames memberNames; - - /// Info about current member member group - final MemberGroup memberGroup; - - /// Info about previous member member group - final MemberGroup? previousMemberGroup; - - /// Creates instance of [MemberOrder] - const MemberOrder({ - required this.isWrong, - required this.isAlphabeticallyWrong, - required this.isByTypeWrong, - required this.memberNames, - required this.memberGroup, - this.previousMemberGroup, - }); -} - -/// Data class contains info about current and previous class member names -class MemberNames { - /// Name of current class member - final String currentName; - - /// Name of previous class member - final String? previousName; - - /// Type name of current class member - final String currentTypeName; - - /// Type name of previous class member - final String? previousTypeName; - - /// Crates instance of [MemberNames] - const MemberNames({ - required this.currentName, - required this.currentTypeName, - this.previousName, - this.previousTypeName, - }); -} diff --git a/lib/src/models/rule_parameters_parser.dart b/lib/src/models/rule_parameters_parser.dart new file mode 100644 index 00000000..90d6b11e --- /dev/null +++ b/lib/src/models/rule_parameters_parser.dart @@ -0,0 +1,2 @@ +/// A function that parses the rule parameters from analysis options json. +typedef RuleParametersParser = T Function(Map); diff --git a/lib/src/models/solid_lint_rule.dart b/lib/src/models/solid_lint_rule.dart index 9f5bee56..b4bf288e 100644 --- a/lib/src/models/solid_lint_rule.dart +++ b/lib/src/models/solid_lint_rule.dart @@ -1,9 +1,7 @@ import 'package:analyzer/analysis_rule/analysis_rule.dart'; import 'package:analyzer/analysis_rule/rule_context.dart'; import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; - -/// A function that parses the rule parameters from analysis options json -typedef RuleParametersParser = T Function(Map); +import 'package:solid_lints/src/models/rule_parameters_parser.dart'; /// A base class for emitting information about /// issues with user's `.dart` files. diff --git a/lib/src/models/solid_multi_lint_rule.dart b/lib/src/models/solid_multi_lint_rule.dart new file mode 100644 index 00000000..244c9377 --- /dev/null +++ b/lib/src/models/solid_multi_lint_rule.dart @@ -0,0 +1,37 @@ +import 'package:analyzer/analysis_rule/analysis_rule.dart'; +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; +import 'package:solid_lints/src/models/rule_parameters_parser.dart'; + +/// A base class for lint rules that report multiple diagnostic codes +/// and require configuration parameters from analysis options. +/// +/// Mirrors SolidLintRule but extends MultiAnalysisRule instead of +/// AnalysisRule, allowing rules to define multiple diagnostic codes. +abstract class SolidMultiLintRule + extends MultiAnalysisRule { + final AnalysisOptionsLoader _analysisOptionsLoader; + + final RuleParametersParser _parametersParser; + + /// Constructor for [SolidMultiLintRule] model with parameters. + SolidMultiLintRule({ + required AnalysisOptionsLoader analysisOptionsLoader, + required RuleParametersParser parametersParser, + required super.name, + required super.description, + super.state, + }) : _analysisOptionsLoader = analysisOptionsLoader, + _parametersParser = parametersParser; + + /// Reads the rule parameters from analysis options and parses them to [T]. + T? getParametersForContext(RuleContext context) { + _analysisOptionsLoader.loadRulesOptionsFromContext(context); + + final unparsedParameters = + _analysisOptionsLoader.getRuleOptions(context, name); + if (unparsedParameters == null) return null; + + return _parametersParser(unparsedParameters); + } +} diff --git a/lint_test/member_ordering_test.dart b/lint_test/member_ordering_test.dart deleted file mode 100644 index 3faf21f0..00000000 --- a/lint_test/member_ordering_test.dart +++ /dev/null @@ -1,232 +0,0 @@ -// ignore_for_file: unused_field, prefer_match_file_name, proper_super_calls -// ignore_for_file: unused_element -// ignore_for_file: no_empty_block - -import 'package:flutter/widgets.dart'; - -/// Check the `member_ordering` rule - -class AlphabeticalClass { - final b = 1; - - // expect_lint: member_ordering - final a = 1; - final c = 1; - - void bStuff() {} - - // expect_lint: member_ordering - void aStuff() {} - - void cStuff() {} - - void visitStatement() {} - - // expect_lint: member_ordering - void visitStanford() {} -} - -class CorrectOrder { - final publicField = 1; - int _privateField = 2; - - CorrectOrder(); - - int get privateFieldGetter => _privateField; - - void set privateFieldSetter(int value) { - _privateField = value; - } - - void publicDoStuff() {} - - void _privateDoStuff() {} - - void close() {} -} - -class WrongOrder { - void close() {} - - // expect_lint: member_ordering - void _privateDoStuff() {} - - // expect_lint: member_ordering - void publicDoStuff() {} - - // expect_lint: member_ordering - void set privateFieldSetter(int value) { - _privateField = value; - } - - // expect_lint: member_ordering - int get privateFieldGetter => _privateField; - - // expect_lint: member_ordering - WrongOrder(); - - // expect_lint: member_ordering - int _privateField = 2; - - // expect_lint: member_ordering - final publicField = 1; -} - -class PartiallyWrongOrder { - final publicField = 1; - - PartiallyWrongOrder(); - - // expect_lint: member_ordering - int _privateField = 2; - - int get privateFieldGetter => _privateField; - - void set privateFieldSetter(int value) { - _privateField = value; - } - - void _privateDoStuff() {} - - // expect_lint: member_ordering - void publicDoStuff() {} - - void close() {} -} - -class CorrectWidget extends StatefulWidget { - @override - State createState() => _CorrectWidgetState(); - - const CorrectWidget({super.key}); -} - -class _CorrectWidgetState extends State { - static const constField = 1; - static final staticField = 1; - - static void staticDoStuff() {} - - final publicField = 1; - final _privateField = 1; - - void publicDoStuff() {} - - void _privateDoStuff() {} - - _CorrectWidgetState(); - - @override - Widget build(BuildContext context) => throw UnimplementedError(); - - @override - void initState() => super.initState(); - - @override - void didChangeDependencies() => super.didChangeDependencies(); - - @override - void didUpdateWidget(covariant CorrectWidget oldWidget) => - super.didUpdateWidget(oldWidget); - - @override - void dispose() => super.dispose(); -} - -class WrongWidget extends StatefulWidget { - const WrongWidget({super.key}); - - // expect_lint: member_ordering - @override - State createState() => _WrongWidgetState(); -} - -class _WrongWidgetState extends State { - @override - void dispose() => super.dispose(); - - // expect_lint: member_ordering - @override - void didUpdateWidget(covariant WrongWidget oldWidget) => - super.didUpdateWidget(oldWidget); - - // expect_lint: member_ordering - @override - void didChangeDependencies() => super.didChangeDependencies(); - - // expect_lint: member_ordering - @override - void initState() => super.initState(); - - // expect_lint: member_ordering - @override - Widget build(BuildContext context) => throw UnimplementedError(); - - // expect_lint: member_ordering - _WrongWidgetState(); - - // expect_lint: member_ordering - void _privateDoStuff() {} - - // expect_lint: member_ordering - void publicDoStuff() {} - - // expect_lint: member_ordering - final _privateField = 1; - - // expect_lint: member_ordering - final publicField = 1; - - // expect_lint: member_ordering - static void staticDoStuff() {} - - // expect_lint: member_ordering - static final staticField = 1; - - // expect_lint: member_ordering - static const constField = 1; -} - -class PartiallyCorrectWidget extends StatefulWidget { - @override - State createState() => _PartiallyCorrectWidgetState(); - - const PartiallyCorrectWidget({super.key}); -} - -class _PartiallyCorrectWidgetState extends State { - static final staticField = 1; - - // expect_lint: member_ordering - static const constField = 1; - - static void staticDoStuff() {} - - final _privateField = 1; - - // expect_lint: member_ordering - final publicField = 1; - - void publicDoStuff() {} - - void _privateDoStuff() {} - - _PartiallyCorrectWidgetState(); - - @override - Widget build(BuildContext context) => throw UnimplementedError(); - - @override - void didChangeDependencies() => super.didChangeDependencies(); - - // expect_lint: member_ordering - @override - void initState() => super.initState(); - - @override - void didUpdateWidget(covariant PartiallyCorrectWidget oldWidget) => - super.didUpdateWidget(oldWidget); - - @override - void dispose() => super.dispose(); -} diff --git a/test/src/lints/member_ordering/member_ordering_rule_test.dart b/test/src/lints/member_ordering/member_ordering_rule_test.dart new file mode 100644 index 00000000..6617d460 --- /dev/null +++ b/test/src/lints/member_ordering/member_ordering_rule_test.dart @@ -0,0 +1,620 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:analyzer_testing/utilities/utilities.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; +import 'package:solid_lints/src/lints/member_ordering/member_ordering_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../lints/auto_test_lint_offsets.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(MemberOrderingRuleTest); + }); +} + +@reflectiveTest +class MemberOrderingRuleTest extends AnalysisRuleTest with AutoTestLintOffsets { + static const _defaultAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + member_ordering: {} + '''; + + static const _mockAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + member_ordering: + alphabetize: true + order: + - public_fields + - private_fields + - constructors + - getters + - setters + - public_methods + - private_methods + - close_method + widgets_order: + - const_fields + - static_fields + - static_methods + - public_fields + - private_fields + - public_methods + - private_methods + - constructors + - build_method + - init_state_method + - did_change_dependencies_method + - did_update_widget_method + - dispose_method + '''; + + static const _alphabetizeAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + member_ordering: + alphabetize: true + alphabetize_by_type: true + '''; + + static const _customOrderAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + member_ordering: + order: + - public_methods + - public_fields + '''; + + static const _customWidgetsOrderAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + member_ordering: + widgets_order: + - build_method + - init_state_method + '''; + + static const _factoryOrderAnalysisOptionsContent = ''' +plugins: + solid_lints: + diagnostics: + member_ordering: + order: + - constructors + - factory_constructors + - public_fields + '''; + + @override + void setUp() { + final flutter = newPackage('flutter'); + flutter.addFile('lib/src/widgets/framework.dart', r''' +class BuildContext {} +class Key {} +abstract class Widget { + const Widget({Key? key}); +} +abstract class StatefulWidget extends Widget { + const StatefulWidget({super.key}); + State createState(); +} +abstract class State { + void initState() {} + void didChangeDependencies() {} + void didUpdateWidget(T oldWidget) {} + void dispose() {} + Widget build(BuildContext context) => throw 0; +} +'''); + + rule = MemberOrderingRule( + analysisOptionsLoader: AnalysisOptionsLoader( + resourceProvider: resourceProvider, + ), + ); + + super.setUp(); + + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_mockAnalysisOptionsContent), + ); + } + + @override + String get analysisRule => MemberOrderingRule.lintName; + + String _configWith(String pluginConfig) => + '''${analysisOptionsContent(rules: [rule.name])} +$pluginConfig'''; + + Future test_does_not_report_on_correct_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertNoDiagnostics(r''' +class CorrectOrder { + final publicField = 1; + + int get privateFieldGetter => 1; + + CorrectOrder(); + + void publicDoStuff() {} +} +'''); + } + + Future test_does_not_report_on_empty_class() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertNoDiagnostics(r''' +class EmptyClass {} +'''); + } + + Future test_does_not_report_on_single_member_class() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertNoDiagnostics(r''' +class SingleMember { + final a = 1; +} +'''); + } + + Future test_reports_on_wrong_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertAutoDiagnostics(''' +class WrongOrder { + void publicDoStuff() {} + ${expectLint('WrongOrder();')} +} +'''); + } + + Future + test_does_not_report_on_non_alphabetical_order_by_default() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertNoDiagnostics(r''' +class NonAlphabetical { + final b = 1; + final a = 1; +} +'''); + } + + Future test_does_not_report_on_correct_widget_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertNoDiagnostics(r''' +import 'package:flutter/src/widgets/framework.dart'; + +class CorrectWidgetState extends State { + CorrectWidgetState(); + + final finalField = 1; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } +} +'''); + } + + Future test_reports_on_wrong_widget_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_defaultAnalysisOptionsContent), + ); + await assertAutoDiagnostics(''' +import 'package:flutter/src/widgets/framework.dart'; + +class WrongWidgetState extends State { + @override + void initState() { + super.initState(); + } + + ${expectLint('WrongWidgetState();')} +} +'''); + } + + Future test_reports_on_non_alphabetical_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_alphabetizeAnalysisOptionsContent), + ); + + await assertAutoDiagnostics(''' +class AlphabeticalClass { + final b = 1; + ${expectLint('final a = 1;')} +} +'''); + } + + Future test_reports_on_non_alphabetical_by_type_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_alphabetizeAnalysisOptionsContent), + ); + + await assertAutoDiagnostics(''' +class AlphabeticalByTypeClass { + int b = 1; + ${expectLint('double a = 1.0;')} +} +'''); + } + + Future test_does_not_report_on_correct_custom_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_customOrderAnalysisOptionsContent), + ); + + await assertNoDiagnostics(r''' +class CorrectOrder { + void publicDoStuff() {} + final publicField = 1; +} +'''); + } + + Future test_reports_on_wrong_custom_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_customOrderAnalysisOptionsContent), + ); + + await assertAutoDiagnostics(''' +class WrongOrder { + final publicField = 1; + ${expectLint('void publicDoStuff() {}')} +} +'''); + } + + Future test_does_not_report_on_correct_custom_widgets_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_customWidgetsOrderAnalysisOptionsContent), + ); + + await assertNoDiagnostics(r''' +import 'package:flutter/src/widgets/framework.dart'; + +class CorrectWidgetState extends State { + @override + Widget build(BuildContext context) => throw 0; + + @override + void initState() { + super.initState(); + } +} +'''); + } + + Future test_reports_on_wrong_custom_widgets_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_customWidgetsOrderAnalysisOptionsContent), + ); + + await assertAutoDiagnostics(''' +import 'package:flutter/src/widgets/framework.dart'; + +class WrongWidgetState extends State { + @override + void initState() { + super.initState(); + } + + ${expectLint('@override Widget build(BuildContext context) => throw 0;')} +} +'''); + } + + Future test_reports_on_non_alphabetical_fields_and_methods_order() async { + await assertAutoDiagnostics(''' +class AlphabeticalClass { + final b = 1; + + ${expectLint('final a = 1;')} + final c = 1; + + void bStuff() {} + + ${expectLint('void aStuff() {}')} + + void cStuff() {} + + void visitStatement() {} + + ${expectLint('void visitStanford() {}')} +} +'''); + } + + Future test_reports_on_named_method_wrong_order() async { + await assertAutoDiagnostics(''' +class NamedMethodOrder { + void close() {} + + ${expectLint('void publicDoStuff() {}')} +} +'''); + } + + Future test_does_not_report_on_named_method_correct_order() async { + await assertNoDiagnostics(''' +class NamedMethodOrder { + void publicDoStuff() {} + + void close() {} +} +'''); + } + + Future test_does_not_report_on_correct_factory_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_factoryOrderAnalysisOptionsContent), + ); + await assertNoDiagnostics(r''' +class FactoryOrder { + FactoryOrder(); + + factory FactoryOrder.create() => FactoryOrder(); + + final field = 1; +} +'''); + } + + Future test_reports_on_wrong_factory_order() async { + newAnalysisOptionsYamlFile( + testPackageRootPath, + _configWith(_factoryOrderAnalysisOptionsContent), + ); + await assertAutoDiagnostics(''' +class FactoryOrder { + factory FactoryOrder.create() => FactoryOrder(); + + ${expectLint('FactoryOrder();')} + + final field = 1; +} +'''); + } + + Future test_does_not_report_on_correct_order_with_custom_config() async { + await assertNoDiagnostics(''' +class CorrectOrder { + final publicField = 1; + int _privateField = 2; + + CorrectOrder(); + + int get privateFieldGetter => _privateField; + + void set privateFieldSetter(int value) { + _privateField = value; + } + + void publicDoStuff() {} + + void _privateDoStuff() {} + + void close() {} +} +'''); + } + + Future test_reports_on_wrong_order_with_custom_config() async { + await assertAutoDiagnostics(''' +class WrongOrder { + void close() {} + + ${expectLint('void _privateDoStuff() {}')} + + ${expectLint('void publicDoStuff() {}')} + + ${expectLint('void set privateFieldSetter(int value) { _privateField = value; }')} + + ${expectLint('int get privateFieldGetter => _privateField;')} + + ${expectLint('WrongOrder();')} + + ${expectLint('int _privateField = 2;')} + + ${expectLint('final publicField = 1;')} +} +'''); + } + + Future test_reports_on_partially_wrong_order_with_custom_config() async { + await assertAutoDiagnostics(''' +class PartiallyWrongOrder { + final publicField = 1; + + PartiallyWrongOrder(); + + ${expectLint('int _privateField = 2;')} + + int get privateFieldGetter => _privateField; + + void set privateFieldSetter(int value) { + _privateField = value; + } + + void _privateDoStuff() {} + + ${expectLint('void publicDoStuff() {}')} + + void close() {} +} +'''); + } + + Future test_does_not_report_on_correct_widget_order_with_custom_config() async { + await assertNoDiagnostics(''' +import 'package:flutter/src/widgets/framework.dart'; + +class CorrectWidget extends StatefulWidget { + @override + State createState() => _CorrectWidgetState(); + + const CorrectWidget({super.key}); +} + +class _CorrectWidgetState extends State { + static const constField = 1; + static final staticField = 1; + + static void staticDoStuff() {} + + final publicField = 1; + final _privateField = 1; + + void publicDoStuff() {} + + void _privateDoStuff() {} + + _CorrectWidgetState(); + + @override + Widget build(BuildContext context) => throw 0; + + @override + void initState() => super.initState(); + + @override + void didChangeDependencies() => super.didChangeDependencies(); + + @override + void didUpdateWidget(covariant CorrectWidget oldWidget) => + super.didUpdateWidget(oldWidget); + + @override + void dispose() => super.dispose(); +} +'''); + } + + Future test_reports_on_wrong_widget_order_with_custom_config() async { + await assertAutoDiagnostics(''' +import 'package:flutter/src/widgets/framework.dart'; + +class WrongWidget extends StatefulWidget { + const WrongWidget({super.key}); + + ${expectLint('@override State createState() => _WrongWidgetState();')} +} + +class _WrongWidgetState extends State { + @override + void dispose() => super.dispose(); + + ${expectLint('@override void didUpdateWidget(covariant WrongWidget oldWidget) => super.didUpdateWidget(oldWidget);')} + + ${expectLint('@override void didChangeDependencies() => super.didChangeDependencies();')} + + ${expectLint('@override void initState() => super.initState();')} + + ${expectLint('@override Widget build(BuildContext context) => throw 0;')} + + ${expectLint('_WrongWidgetState();')} + + ${expectLint('void _privateDoStuff() {}')} + + ${expectLint('void publicDoStuff() {}')} + + ${expectLint('final _privateField = 1;')} + + ${expectLint('final publicField = 1;')} + + ${expectLint('static void staticDoStuff() {}')} + + ${expectLint('static final staticField = 1;')} + + ${expectLint('static const constField = 1;')} +} +'''); + } + + Future test_reports_on_partially_correct_widget_order_with_custom_config() async { + await assertAutoDiagnostics(''' +import 'package:flutter/src/widgets/framework.dart'; + +class PartiallyCorrectWidget extends StatefulWidget { + @override + State createState() => _PartiallyCorrectWidgetState(); + + const PartiallyCorrectWidget({super.key}); +} + +class _PartiallyCorrectWidgetState extends State { + static final staticField = 1; + + ${expectLint('static const constField = 1;')} + + static void staticDoStuff() {} + + final _privateField = 1; + + ${expectLint('final publicField = 1;')} + + void publicDoStuff() {} + + void _privateDoStuff() {} + + _PartiallyCorrectWidgetState(); + + @override + Widget build(BuildContext context) => throw 0; + + @override + void didChangeDependencies() => super.didChangeDependencies(); + + ${expectLint('@override void initState() => super.initState();')} + + @override + void didUpdateWidget(covariant PartiallyCorrectWidget oldWidget) => + super.didUpdateWidget(oldWidget); + + @override + void dispose() => super.dispose(); +} +'''); + } +}