From b67c8bc9eeab87f2416bfea742edc8361e550c52 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 09:52:39 +0300 Subject: [PATCH 01/27] chore: update SDK and dependencies --- pubspec.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index c220efac..eaaba9fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,25 +15,25 @@ platforms: windows: environment: - sdk: ">=3.9.0 <4.0.0" + sdk: ">=3.13.0 <4.0.0" dependencies: # Needed until required types for fixes are exported by analyzer_server_plugin # More details: https://github.com/dart-lang/sdk/issues/61821 - analyzer_plugin: ^0.14.2 - analyzer: ^10.0.1 + analyzer_plugin: ^0.14.14 + analyzer: ^14.1.0 collection: ^1.19.1 - analysis_server_plugin: ^0.3.3 + analysis_server_plugin: ^0.3.20 equatable: ^2.1.0 glob: ^2.1.3 path: ^1.9.1 yaml: ^3.1.3 # These packages are required for pana analysis to run correctly - test: ^1.25.14 + test: ^1.31.2 dev_dependencies: args: ^2.7.0 - analyzer_testing: ^0.1.9 + analyzer_testing: ^0.3.4 test_reflective_loader: ^0.4.0 plugin: From 09000b38877d4835304aed632f56a5ff567f3e71 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 09:53:19 +0300 Subject: [PATCH 02/27] refactor: simplify parameter checking logic and update skip condition for incomplete function definitions --- .../avoid_unused_parameters_visitor.dart | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart b/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart index 31cb12ad..87c7fce3 100644 --- a/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart +++ b/lib/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart @@ -74,7 +74,7 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { final parameters = node.parameters; if ((parent is ClassDeclaration && parent.abstractKeyword != null) || - node.isAbstract || + !node.isComplete || node.externalKeyword != null || (parameters == null || parameters.parameters.isEmpty)) { return; @@ -150,24 +150,11 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { parameter.declaredFragment?.element.baseElement.nonSynthetic, ); - /// Variables declared and initialized as 'Foo(this.param)' - bool isFieldFormalParameter = parameter is FieldFormalParameter; + final isInitializingFormal = + parameter is FieldFormalParameter || + parameter is SuperFormalParameter; - /// Variables declared and initialized as 'Foo(super.param)' - bool isSuperFormalParameter = parameter is SuperFormalParameter; - - if (parameter is DefaultFormalParameter) { - /// Variables as 'Foo({super.param})' or 'Foo({this.param})' - /// is being reported as [DefaultFormalParameter] instead - /// of [SuperFormalParameter] it seems to be an issue in DartSDK - isFieldFormalParameter = parameter.toSource().contains('this.'); - isSuperFormalParameter = parameter.toSource().contains('super.'); - } - - if (name != null && - !isPresentInAll && - !isFieldFormalParameter && - !isSuperFormalParameter) { + if (name != null && !isPresentInAll && !isInitializingFormal) { result.add(parameter); } } From b4a0047c38ecebe41c91ef272f3b6f41f07a8318 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 09:57:22 +0300 Subject: [PATCH 03/27] refactor: update member access utils to resolve expression elements from argument expressions instead of arguments directly --- .../feature_envy/utils/member_access_utils.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/src/lints/feature_envy/utils/member_access_utils.dart b/lib/src/lints/feature_envy/utils/member_access_utils.dart index 707a7153..624d9541 100644 --- a/lib/src/lints/feature_envy/utils/member_access_utils.dart +++ b/lib/src/lints/feature_envy/utils/member_access_utils.dart @@ -15,7 +15,12 @@ abstract final class MemberAccessUtils { final baseElement = switch (target.unwrapTarget) { ExtensionOverride(:final argumentList) => - argumentList.arguments.firstOrNull?.staticType?.element, + argumentList + .arguments + .firstOrNull + ?.argumentExpression + .staticType + ?.element, final expr => expr?.staticType?.element, }; @@ -37,7 +42,12 @@ abstract final class MemberAccessUtils { (target != null || !isPatternField) && switch (target?.unwrapTarget) { ExtensionOverride(:final argumentList) => - argumentList.arguments.firstOrNull?.unwrapTarget.isThisOrSuper ?? + argumentList + .arguments + .firstOrNull + ?.argumentExpression + .unwrapTarget + .isThisOrSuper ?? false, final expr => expr.isThisOrSuperOrNull, }; From e3d75530a4cd22414d064ade41b5562b2d5b85e9 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:02:43 +0300 Subject: [PATCH 04/27] refactor: simplify ParameterType classification using pattern matching and defaultClause check --- .../models/parameter_type.dart | 39 +++++-------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/lib/src/lints/named_parameters_ordering/models/parameter_type.dart b/lib/src/lints/named_parameters_ordering/models/parameter_type.dart index 8f560db3..6bf77d62 100644 --- a/lib/src/lints/named_parameters_ordering/models/parameter_type.dart +++ b/lib/src/lints/named_parameters_ordering/models/parameter_type.dart @@ -35,36 +35,15 @@ enum ParameterType { } /// Classifies a [FormalParameter] into a [ParameterType]. - /// - /// Recursively unwraps [DefaultFormalParameter] wrappers to determine - /// the underlying parameter kind. - static ParameterType fromParameter( - FormalParameter parameter, { - bool hasDefaultValue = false, - }) { - if (parameter is DefaultFormalParameter && - parameter.parameter is! DefaultFormalParameter) { - return fromParameter( - parameter.parameter, - hasDefaultValue: parameter.defaultValue != null, - ); - } - - switch (parameter) { - case SuperFormalParameter(:final isRequired): - return isRequired - ? ParameterType.requiredInherited - : ParameterType.inherited; - - case DefaultFormalParameter(): - case _ when hasDefaultValue: - return ParameterType.defaultValue; - - case FieldFormalParameter(:final isRequired) || - FunctionTypedFormalParameter(:final isRequired) || - SimpleFormalParameter(:final isRequired): - return isRequired ? ParameterType.required : ParameterType.nullable; - } + static ParameterType fromParameter(FormalParameter parameter) { + return switch (parameter) { + SuperFormalParameter(:final isRequired) => + isRequired ? ParameterType.requiredInherited : ParameterType.inherited, + FormalParameter(defaultClause: _?) => ParameterType.defaultValue, + FormalParameter(:final isRequired) when isRequired => + ParameterType.required, + _ => ParameterType.nullable, + }; } /// String representation of the parameter type From 6a82db20fe27c07b10b35f8e6618cc05b94d30a1 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:04:59 +0300 Subject: [PATCH 05/27] refactor: update magic number visitor to use RecordLiteralField instead of NamedExpression check --- .../no_magic_number/visitors/no_magic_number_rule_visitor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart b/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart index 012aead9..a9db8ac0 100644 --- a/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart +++ b/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart @@ -71,7 +71,7 @@ class NoMagicNumberRuleVisitor extends SimpleAstVisitor { return p is TypedLiteral || p is MapLiteralEntry || p is RecordLiteral || - (p is NamedExpression && p.parent is RecordLiteral); + p is RecordLiteralField; } bool _isWidgetParameter(Literal literal) { From 3a672090f09659dc6a7b6f2c6748a3cfc3f04cf8 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:15:41 +0300 Subject: [PATCH 06/27] refactor: optimize BuildContext search using ancestors traversal and update node utility helpers --- .../utils/use_nearest_context_utils.dart | 26 +++++------------- lib/src/utils/node_utils.dart | 27 ++++++++++++++----- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/src/lints/use_nearest_context/utils/use_nearest_context_utils.dart b/lib/src/lints/use_nearest_context/utils/use_nearest_context_utils.dart index d5b4652f..114462ec 100644 --- a/lib/src/lints/use_nearest_context/utils/use_nearest_context_utils.dart +++ b/lib/src/lints/use_nearest_context/utils/use_nearest_context_utils.dart @@ -1,24 +1,10 @@ import 'package:analyzer/dart/ast/ast.dart'; +import 'package:collection/collection.dart'; +import 'package:solid_lints/src/utils/node_utils.dart'; import 'package:solid_lints/src/utils/types_utils.dart'; /// Finds the closest BuildContext parameter in the AST parent chain of [node]. -SimpleFormalParameter? findClosestBuildContext(AstNode node) { - AstNode? current = node.parent; - - while (current != null) { - if (current is FunctionExpression) { - final functionParams = current.parameters?.parameters ?? []; - for (final param in functionParams) { - final actualParam = param is DefaultFormalParameter - ? param.parameter - : param; - if (actualParam is SimpleFormalParameter && - isBuildContext(actualParam.declaredFragment?.element.type)) { - return actualParam; - } - } - } - current = current.parent; - } - return null; -} +FormalParameter? findClosestBuildContext(AstNode node) => node.ancestors + .whereType() + .expand((fn) => fn.parameters?.parameters ?? const []) + .firstWhereOrNull((p) => isBuildContext(p.declaredFragment?.element.type)); diff --git a/lib/src/utils/node_utils.dart b/lib/src/utils/node_utils.dart index 880a57e3..5f931725 100644 --- a/lib/src/utils/node_utils.dart +++ b/lib/src/utils/node_utils.dart @@ -48,7 +48,7 @@ extension SimpleIdentifierExtension on SimpleIdentifier { /// Returns `true` if this identifier refers to a variable declared inside /// the body of the function that owns [as] (i.e. a local variable in the /// same scope). - bool isDeclaredInSameFunction({required SimpleFormalParameter as}) { + bool isDeclaredInSameFunction({required FormalParameter as}) { final element = this.element; if (element is! LocalVariableElement) return false; @@ -83,10 +83,21 @@ extension SimpleIdentifierExtension on SimpleIdentifier { /// Extension on [AstNode] to provide generic context/traversal checks. extension AstNodeExtension on AstNode { + /// Returns an iterable of all parent nodes of this node up to the root. + Iterable get ancestors sync* { + for (var current = parent; current != null; current = current.parent) { + yield current; + } + } + /// Returns `true` if the node is within the default value of a formal /// parameter. bool get isDefaultValue => - thisOrAncestorOfType() != null; + thisOrAncestorMatching( + (ancestor) => + ancestor is FormalParameter && ancestor.defaultClause != null, + ) != + null; /// Returns `true` if the node is within a constructor initializer. bool get isInConstructorInitializer => @@ -162,7 +173,7 @@ extension ArgumentListExtension on ArgumentList { /// Returns `true` if this argument list contains a named parameter argument /// with the given [name]. bool containsNamed(String name) => arguments.any( - (arg) => arg is NamedExpression && arg.name.label.name == name, + (arg) => arg is NamedArgument && arg.name.lexeme == name, ); } @@ -297,10 +308,12 @@ extension ExpressionNullableExtension on Expression? { while (true) { if (current case AsExpression(:final expression)) { current = expression.unParenthesized; - } else if (current case PostfixExpression( - :final operand, - :final operator, - ) when operator.type == TokenType.BANG) { + } else if (current + case PostfixExpression( + :final operand, + :final operator, + ) + when operator.type == TokenType.BANG) { current = operand.unParenthesized; } else { return current; From 7d967f46fd49f4cf95d5fdc71f2e0b03581e495f Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:33:52 +0300 Subject: [PATCH 07/27] chore: remove deprecated and unnecessary lint rules from analysis options --- lib/analysis_options.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index b9203a45..a20f30f7 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -138,10 +138,8 @@ linter: - avoid_implementing_value_types - avoid_init_to_null - avoid_multiple_declarations_per_line - - avoid_null_checks_in_equality_operators - avoid_positional_boolean_parameters - avoid_print - - avoid_private_typedef_functions - avoid_redundant_argument_values - avoid_relative_lib_imports - avoid_renaming_method_parameters @@ -273,7 +271,6 @@ linter: - type_annotate_public_apis - type_init_formals - unawaited_futures - - unnecessary_await_in_return - unnecessary_brace_in_string_interps - unnecessary_breaks - unnecessary_const @@ -298,7 +295,6 @@ linter: - use_decorated_box - use_full_hex_values_for_flutter_colors - use_function_type_syntax_for_parameters - - use_if_null_to_convert_nulls_to_bools - use_is_even_rather_than_modulo - use_named_constants - use_raw_strings From 9e587cf0b677a08fb051099cc08ef651aefffee8 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:36:12 +0300 Subject: [PATCH 08/27] refactor: use field formal parameters in AnalysisOptionsLoader constructor --- .../common/parameter_parser/analysis_options_loader.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/common/parameter_parser/analysis_options_loader.dart b/lib/src/common/parameter_parser/analysis_options_loader.dart index 88be58d6..f190e961 100644 --- a/lib/src/common/parameter_parser/analysis_options_loader.dart +++ b/lib/src/common/parameter_parser/analysis_options_loader.dart @@ -33,10 +33,9 @@ class AnalysisOptionsLoader { } AnalysisOptionsLoader._({ - required ResourceProvider resourceProvider, - required AnalysisOptionsParser parser, - }) : _resourceProvider = resourceProvider, - _parser = parser; + required this._resourceProvider, + required this._parser, + }); /// Gets the options for a specific rule by its name. Map? getRuleOptions(RuleContext context, String ruleName) => From 2bc45ddcb02c50337142f667fb6af5004688f78c Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:40:24 +0300 Subject: [PATCH 09/27] refactor: update avoid_returning_widgets visitor to ignore incomplete method declarations instead of abstract ones --- .../visitors/avoid_returning_widgets_visitor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart index 7b9184a4..e8a1d97c 100644 --- a/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart +++ b/lib/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart @@ -41,7 +41,7 @@ class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor { } if (node is MethodDeclaration && - (node.isAbstract || + (!node.isComplete || node.body is EmptyFunctionBody || (node.isGetter && _isStateWidgetCastingGetter(node)))) { return; From eb146edb31b660e2d7563db7d953b26be5d44665 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:40:38 +0300 Subject: [PATCH 10/27] refactor: use initializer list shorthand in DeclarationOrderingVisitor constructor --- .../visitors/declaration_ordering_visitor.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/lints/member_ordering/visitors/declaration_ordering_visitor.dart b/lib/src/lints/member_ordering/visitors/declaration_ordering_visitor.dart index 709cb2ed..2066849f 100644 --- a/lib/src/lints/member_ordering/visitors/declaration_ordering_visitor.dart +++ b/lib/src/lints/member_ordering/visitors/declaration_ordering_visitor.dart @@ -44,10 +44,9 @@ class DeclarationOrderingVisitor { /// Creates instance of [DeclarationOrderingVisitor]. DeclarationOrderingVisitor({ - required MemberOrderingParameters parameters, - required bool isFlutterWidget, - }) : _parameters = parameters, - _isFlutterWidget = isFlutterWidget; + required this._parameters, + required this._isFlutterWidget, + }); /// Visits a [ClassMember]. void visit(ClassMember member) => switch (member) { From 968e4d8f63eca3f212be2673e047b860e8876c82 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:40:53 +0300 Subject: [PATCH 11/27] refactor: simplify MemberOrderingReporter constructor using field formal parameters --- .../member_ordering/visitors/member_ordering_reporter.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/lints/member_ordering/visitors/member_ordering_reporter.dart b/lib/src/lints/member_ordering/visitors/member_ordering_reporter.dart index 27be61e4..04e6237e 100644 --- a/lib/src/lints/member_ordering/visitors/member_ordering_reporter.dart +++ b/lib/src/lints/member_ordering/visitors/member_ordering_reporter.dart @@ -33,10 +33,9 @@ class MemberOrderingReporter { /// Creates instance of [MemberOrderingReporter]. const MemberOrderingReporter({ - required List membersInfo, - required MemberOrderingRule rule, - }) : _membersInfo = membersInfo, - _rule = rule; + required this._membersInfo, + required this._rule, + }); /// Generates diagnostic reports based on the configuration parameters. void report({ From 6d6475efe7ed7d762ffd9afeb4905d88092ef6a7 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:41:29 +0300 Subject: [PATCH 12/27] refactor: simplify NoEmptyBlockVisitor constructor using field formal parameters --- .../visitors/no_empty_block_visitor.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/src/lints/no_empty_block/visitors/no_empty_block_visitor.dart b/lib/src/lints/no_empty_block/visitors/no_empty_block_visitor.dart index f58ddd00..6100174d 100644 --- a/lib/src/lints/no_empty_block/visitors/no_empty_block_visitor.dart +++ b/lib/src/lints/no_empty_block/visitors/no_empty_block_visitor.dart @@ -38,12 +38,10 @@ class NoEmptyBlockVisitor extends RecursiveAstVisitor { /// Constructor for [NoEmptyBlockVisitor] NoEmptyBlockVisitor({ - required AnalysisRule rule, - required bool allowWithComments, - required ExcludedIdentifiersListParameter exclude, - }) : _rule = rule, - _allowWithComments = allowWithComments, - _exclude = exclude; + required this._rule, + required this._allowWithComments, + required this._exclude, + }); @override void visitBlock(Block node) { From c217099032ff8a0e740bd7ab09d1b04277fd3316 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:41:53 +0300 Subject: [PATCH 13/27] refactor: simplify PreferConditionalExpressionsVisitor constructor using field formal parameters --- .../visitors/prefer_conditional_expressions_visitor.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart b/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart index d9441907..8114ea3e 100644 --- a/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart +++ b/lib/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart @@ -35,10 +35,9 @@ class PreferConditionalExpressionsVisitor extends RecursiveAstVisitor { /// Creates instance of [PreferConditionalExpressionsVisitor] PreferConditionalExpressionsVisitor({ - required PreferConditionalExpressionsRule rule, - required bool ignoreNested, - }) : _rule = rule, - _ignoreNested = ignoreNested; + required this._rule, + required this._ignoreNested, + }); @override void visitIfStatement(IfStatement node) { From 37a931ade4d6f5945830d06022617645b49556e9 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:43:21 +0300 Subject: [PATCH 14/27] fix: correct field access for ExtensionTypeDeclaration in file name visitor --- .../visitors/prefer_match_file_name_visitor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart b/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart index 8f1f7228..2fa9b428 100644 --- a/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart +++ b/lib/src/lints/prefer_match_file_name/visitors/prefer_match_file_name_visitor.dart @@ -42,7 +42,7 @@ class PreferMatchFileNameVisitor extends SimpleAstVisitor { ExtensionDeclaration() => d.name, MixinDeclaration() => d.name, EnumDeclaration() => d.namePart.typeName, - ExtensionTypeDeclaration() => d.primaryConstructor.typeName, + ExtensionTypeDeclaration() => d.namePart.typeName, _ => null, }; From d1aaf5d96bdbc8ee0996e68aba7df9072b840eb8 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:44:00 +0300 Subject: [PATCH 15/27] fix: resolve incorrect type parameter lookup in ExtensionTypeDeclaration visitor and reformat code style --- ..._descriptive_names_for_type_parameters_visitor.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart b/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart index 428c6f1b..51655562 100644 --- a/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart +++ b/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart @@ -15,9 +15,11 @@ class UseDescriptiveNamesForTypeParametersVisitor UseDescriptiveNamesForTypeParametersVisitor(this._rule, this._parameters); void _visit(TypeParameterList? types) { - if (types case TypeParameterList( - typeParameters: final ps, - ) when ps.length >= _minParameters) { + if (types + case TypeParameterList( + typeParameters: final ps, + ) + when ps.length >= _minParameters) { ps.where(_hasInvalidShortName).forEach(_report); } } @@ -69,5 +71,5 @@ class UseDescriptiveNamesForTypeParametersVisitor @override void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) => - _visit(node.primaryConstructor.typeParameters); + _visit(node.namePart.typeParameters); } From 2a67b5681bafa1dd8bf81a3c9afbac1fbb76c5e0 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:44:37 +0300 Subject: [PATCH 16/27] refactor: rename constructor message parameter to _message for direct field assignment in SolidDiagnosticMessage --- lib/src/models/solid_diagnostic_message.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/models/solid_diagnostic_message.dart b/lib/src/models/solid_diagnostic_message.dart index 85d2ce09..72db10cb 100644 --- a/lib/src/models/solid_diagnostic_message.dart +++ b/lib/src/models/solid_diagnostic_message.dart @@ -21,9 +21,9 @@ class SolidDiagnosticMessage implements DiagnosticMessage { SolidDiagnosticMessage({ required this.filePath, required this.length, - required String message, + required this._message, required this.offset, - }) : _message = message; + }); @override String messageText({required bool includeUrl}) { From 20ff8d2ef11f75cc2fef71ee74762f4b0a08bee6 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:48:23 +0300 Subject: [PATCH 17/27] refactor: simplify SolidLintRule constructor by using field formal parameters --- lib/src/models/solid_lint_rule.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/models/solid_lint_rule.dart b/lib/src/models/solid_lint_rule.dart index d6a5ab7b..c0145c98 100644 --- a/lib/src/models/solid_lint_rule.dart +++ b/lib/src/models/solid_lint_rule.dart @@ -23,11 +23,11 @@ abstract class SolidLintRule extends AnalysisRule { /// Constructor for [SolidLintRule] model with parameters. SolidLintRule.withParameters({ required this.analysisOptionsLoader, - required RuleParametersParser parametersParser, + required this._parametersParser, required super.name, required super.description, super.state, - }) : _parametersParser = parametersParser; + }); /// Reads the rule parameters from analysis options and parses them to [T] T? getParametersForContext(RuleContext context) { From 0fc45147f574bd09e46a6752fba96b8697c3e6a8 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:49:17 +0300 Subject: [PATCH 18/27] refactor: simplify RuleParametersParser assignment in SolidMultiLintRule constructor --- lib/src/models/solid_multi_lint_rule.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/models/solid_multi_lint_rule.dart b/lib/src/models/solid_multi_lint_rule.dart index 00750db1..cbb64a00 100644 --- a/lib/src/models/solid_multi_lint_rule.dart +++ b/lib/src/models/solid_multi_lint_rule.dart @@ -19,11 +19,11 @@ abstract class SolidMultiLintRule extends MultiAnalysisRule { /// Constructor for [SolidMultiLintRule] model with parameters. SolidMultiLintRule({ required this.analysisOptionsLoader, - required RuleParametersParser parametersParser, + required this._parametersParser, required super.name, required super.description, super.state, - }) : _parametersParser = parametersParser; + }); /// Reads the rule parameters from analysis options and parses them to [T]. T? getParametersForContext(RuleContext context) { From 8da3feacc073a506a88a845a5e7c5f899a7b237e Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 10:52:19 +0300 Subject: [PATCH 19/27] refactor: update test helper to use getFile for dummy unit retrieval --- .../common/parameter_parser/analysis_options_loader_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/src/common/parameter_parser/analysis_options_loader_test.dart b/test/src/common/parameter_parser/analysis_options_loader_test.dart index aa404ae3..0904c10c 100644 --- a/test/src/common/parameter_parser/analysis_options_loader_test.dart +++ b/test/src/common/parameter_parser/analysis_options_loader_test.dart @@ -797,9 +797,7 @@ analyzer: _TestWorkspacePackage(rootFolder), definingUnit: definingUnit ?? - _TestRuleContextUnit( - rootFolder.getChildAssumingFile('lib/dummy.dart'), - ), + _TestRuleContextUnit(rootFolder.getFile('lib/dummy.dart')), currentUnit: currentUnit, ); } From 880c92b648992a1f5f5f37d5de9397ba6a153752 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 11:12:09 +0300 Subject: [PATCH 20/27] refactor: simplify visitor constructors and update AST node handling for named arguments and file system access --- .../visitors/ast_structural_hash_visitor.dart | 23 ++++++++++--------- .../avoid_duplicate_code_visitor.dart | 23 +++++++------------ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart b/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart index 49ea8816..0747752a 100644 --- a/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart +++ b/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart @@ -23,10 +23,9 @@ class AstStructuralHashVisitor extends UnifyingAstVisitor { /// Creates a new [AstStructuralHashVisitor]. AstStructuralHashVisitor({ - required bool ignoreLiterals, - required bool ignoreIdentifiers, - }) : _ignoreLiterals = ignoreLiterals, - _ignoreIdentifiers = ignoreIdentifiers; + required this._ignoreLiterals, + required this._ignoreIdentifiers, + }); /// Computes the structural hash for the given [node]. /// @@ -94,10 +93,12 @@ class AstStructuralHashVisitor extends UnifyingAstVisitor { @override void visitPrefixExpression(PrefixExpression node) { - if (node case PrefixExpression( - operator: Token(type: TokenType.MINUS || TokenType.PLUS), - operand: IntegerLiteral() || DoubleLiteral(), - ) when _ignoreLiterals) { + if (node + case PrefixExpression( + operator: Token(type: TokenType.MINUS || TokenType.PLUS), + operand: IntegerLiteral() || DoubleLiteral(), + ) + when _ignoreLiterals) { node.operand.accept(this); } else { _append(node.operator.lexeme); @@ -124,9 +125,9 @@ class AstStructuralHashVisitor extends UnifyingAstVisitor { } @override - void visitNamedExpression(NamedExpression node) { - _append(node.name.label.name); - super.visitNamedExpression(node); + void visitNamedArgument(NamedArgument node) { + _append(node.name.lexeme); + super.visitNamedArgument(node); } // --- Literals --- diff --git a/lib/src/lints/avoid_duplicate_code/visitors/avoid_duplicate_code_visitor.dart b/lib/src/lints/avoid_duplicate_code/visitors/avoid_duplicate_code_visitor.dart index 3ca1ca67..233d5aa9 100644 --- a/lib/src/lints/avoid_duplicate_code/visitors/avoid_duplicate_code_visitor.dart +++ b/lib/src/lints/avoid_duplicate_code/visitors/avoid_duplicate_code_visitor.dart @@ -3,7 +3,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/file_system/file_system.dart'; -import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:collection/collection.dart'; import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; import 'package:solid_lints/src/lints/avoid_duplicate_code/avoid_duplicate_code_rule.dart'; @@ -40,19 +39,13 @@ class AvoidDuplicateCodeVisitor extends RecursiveAstVisitor { AvoidDuplicateCodeVisitor( this._rule, this._parameters, { - required String filePath, - required int modificationStamp, - required IgnoreMatcher ignoreMatcher, - ContextRoot? contextRoot, - ResourceProvider? resourceProvider, - AnalysisOptionsLoader? analysisOptionsLoader, - }) : _filePath = filePath, - _modificationStamp = modificationStamp, - _contextRoot = contextRoot, - _resourceProvider = - resourceProvider ?? PhysicalResourceProvider.INSTANCE, - _analysisOptionsLoader = analysisOptionsLoader, - _ignoreMatcher = ignoreMatcher; + required this._filePath, + required this._modificationStamp, + required this._ignoreMatcher, + required this._resourceProvider, + this._contextRoot, + this._analysisOptionsLoader, + }); @override void visitCompilationUnit(CompilationUnit node) { @@ -317,7 +310,7 @@ class AvoidDuplicateCodeVisitor extends RecursiveAstVisitor { return _packageRootCache.putIfAbsent(dirPath, () { var dir = _resourceProvider.getFolder(dirPath); while (true) { - final pubspec = dir.getChildAssumingFile('pubspec.yaml'); + final pubspec = dir.getFile('pubspec.yaml'); if (pubspec.exists) { return dir.path; } From 07994bcf63a8c8417e72d1bfda6f60a51dfa3e3f Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 11:40:51 +0300 Subject: [PATCH 21/27] style: apply consistent formatting and minor cleanups across the codebase --- .../package_config_resolver.dart | 1 + .../visitors/getter_variable_visitor.dart | 20 ++-- .../unnecessary_where_type_visitor.dart | 16 ++-- lib/src/models/rule_with_fixes.dart | 7 +- lib/src/utils/typecast_utils.dart | 11 ++- .../global_hash_registry_test.dart | 91 +++++++++---------- .../prefer_match_file_name_rule_test.dart | 3 +- 7 files changed, 76 insertions(+), 73 deletions(-) diff --git a/lib/src/common/parameter_parser/package_config_resolver.dart b/lib/src/common/parameter_parser/package_config_resolver.dart index 1cc1207e..e4acd413 100644 --- a/lib/src/common/parameter_parser/package_config_resolver.dart +++ b/lib/src/common/parameter_parser/package_config_resolver.dart @@ -1,4 +1,5 @@ import 'dart:convert'; + import 'package:analyzer/file_system/file_system.dart'; import 'package:solid_lints/src/common/parameter_parser/cached_package_config.dart'; diff --git a/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart b/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart index c9afd760..27f40dc8 100644 --- a/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart +++ b/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart @@ -17,15 +17,17 @@ class GetterVariableVisitor extends RecursiveAstVisitor { @override void visitVariableDeclaration(VariableDeclaration node) { - if (node case VariableDeclaration( - declaredFragment: VariableFragment( - element: VariableElement( - isPrivate: true, - isFinal: true, - :final id, - ), - ), - ) when id == _getterId) { + if (node + case VariableDeclaration( + declaredFragment: VariableFragment( + element: VariableElement( + isPrivate: true, + isFinal: true, + :final id, + ), + ), + ) + when id == _getterId) { _variable = node; } diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart index 6b39c3df..a2b56938 100644 --- a/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart @@ -45,13 +45,15 @@ class UnnecessaryWhereTypeVisitor extends SimpleAstVisitor { } bool _isUnnecessaryWhereType(MethodInvocation node) { - if (node case MethodInvocation( - methodName: Identifier( - name: AvoidUnnecessaryTypeAssertionsRule.whereTypeMethodName, - ), - target: Expression(staticType: final InterfaceType targetType), - typeArguments: TypeArgumentList(:final arguments), - ) when arguments.isNotEmpty) { + if (node + case MethodInvocation( + methodName: Identifier( + name: AvoidUnnecessaryTypeAssertionsRule.whereTypeMethodName, + ), + target: Expression(staticType: final InterfaceType targetType), + typeArguments: TypeArgumentList(:final arguments), + ) + when arguments.isNotEmpty) { final targetIterableType = switch (targetType) { InterfaceType(isDartCoreIterable: true) => targetType, InterfaceType(:final allSupertypes) => allSupertypes.firstWhereOrNull( diff --git a/lib/src/models/rule_with_fixes.dart b/lib/src/models/rule_with_fixes.dart index 2be5ca98..b93d517e 100644 --- a/lib/src/models/rule_with_fixes.dart +++ b/lib/src/models/rule_with_fixes.dart @@ -3,10 +3,9 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/error/error.dart'; /// A function that creates a [CorrectionProducer] for a given context. -typedef ProducerGenerator = - CorrectionProducer Function({ - required CorrectionProducerContext context, - }); +typedef ProducerGenerator = CorrectionProducer Function({ + required CorrectionProducerContext context, +}); /// A collection of diagnostic codes and their associated fix generators. typedef FixesForCodes = Iterable>; diff --git a/lib/src/utils/typecast_utils.dart b/lib/src/utils/typecast_utils.dart index e90fe2e6..b8c36e9b 100644 --- a/lib/src/utils/typecast_utils.dart +++ b/lib/src/utils/typecast_utils.dart @@ -65,10 +65,13 @@ class TypeCast { return false; } - if (this case TypeCast( - source: final objectType, - target: final castedType, - ) when objectType is ParameterizedType && castedType is ParameterizedType) { + if (this + case TypeCast( + source: final objectType, + target: final castedType, + ) + when objectType is ParameterizedType && + castedType is ParameterizedType) { if (objectType.typeArguments.length != castedType.typeArguments.length) { return false; } diff --git a/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart b/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart index 93c8de1e..01672690 100644 --- a/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart +++ b/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart @@ -386,55 +386,52 @@ void main() { expect(params1, isNot(equals(paramsDifferentExclude))); }); - test( - 'does not match or clear files from sibling directories with prefixing names', - () { - final currentRoot = io.Directory.current.path; - final siblingRoot = '${currentRoot}_sibling'; - final siblingFilePath = p.normalize(p.join(siblingRoot, 'file.dart')); - final projectFilePath = p.normalize(p.join(currentRoot, 'file.dart')); - - registry.updateFile(projectFilePath, [ - const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), - ], modificationStamp: 1); - - registry.updateFile(siblingFilePath, [ - const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), - ], modificationStamp: 1); - - expect(registry.fileCount, equals(2)); - - // 1. findCrossFileMatches should not find duplicate in siblingFilePath - // if limited to currentRoot. - final matches = registry.findCrossFileMatches(projectFilePath, [ - const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), - ], packageRoot: currentRoot); - expect(matches, isEmpty); - - // 2. clearEntriesForRoot should not clear siblingFilePath when - // clearing currentRoot. - final newParams = AvoidDuplicateCodeParameters( - minTokens: 40, - ignoreLiterals: false, - ignoreIdentifiers: false, - checkBlocks: true, - exclude: AvoidDuplicateCodeParameters.empty().exclude, - ); + test('does not match or clear files from sibling directories with prefixing names', () { + final currentRoot = io.Directory.current.path; + final siblingRoot = '${currentRoot}_sibling'; + final siblingFilePath = p.normalize(p.join(siblingRoot, 'file.dart')); + final projectFilePath = p.normalize(p.join(currentRoot, 'file.dart')); - registry.updateFile( - projectFilePath, - [const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5)], - modificationStamp: 1, - parameters: newParams, - packageRoot: currentRoot, - ); + registry.updateFile(projectFilePath, [ + const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), + ], modificationStamp: 1); - expect( - registry.getFileEntries(siblingFilePath, packageRoot: siblingRoot), - isNotNull, - ); - }, - ); + registry.updateFile(siblingFilePath, [ + const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), + ], modificationStamp: 1); + + expect(registry.fileCount, equals(2)); + + // 1. findCrossFileMatches should not find duplicate in siblingFilePath + // if limited to currentRoot. + final matches = registry.findCrossFileMatches(projectFilePath, [ + const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), + ], packageRoot: currentRoot); + expect(matches, isEmpty); + + // 2. clearEntriesForRoot should not clear siblingFilePath when + // clearing currentRoot. + final newParams = AvoidDuplicateCodeParameters( + minTokens: 40, + ignoreLiterals: false, + ignoreIdentifiers: false, + checkBlocks: true, + exclude: AvoidDuplicateCodeParameters.empty().exclude, + ); + + registry.updateFile( + projectFilePath, + [const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5)], + modificationStamp: 1, + parameters: newParams, + packageRoot: currentRoot, + ); + + expect( + registry.getFileEntries(siblingFilePath, packageRoot: siblingRoot), + isNotNull, + ); + }); test( 'debounces save operations independently for different package roots', diff --git a/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart b/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart index 581b0b9f..16572bb7 100644 --- a/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart +++ b/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart @@ -202,8 +202,7 @@ final someVariable = 42; '''); } - void - test_does_not_report_on_multiple_public_declarations_if_first_matches() async { + void test_does_not_report_on_multiple_public_declarations_if_first_matches() async { await assertNoDiagnostics(r''' class Test {} class AnotherPublicClass {} From 61c4789b4b126718e7e7e6015358778ae30cc798 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 11:54:11 +0300 Subject: [PATCH 22/27] refactor: remove redundant export of main.dart from library entry point --- lib/solid_lints.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/solid_lints.dart b/lib/solid_lints.dart index 55434eb7..d9bc949a 100644 --- a/lib/solid_lints.dart +++ b/lib/solid_lints.dart @@ -3,5 +3,3 @@ /// This package is an analyzer plugin and is intended to be used via /// `analysis_options.yaml`. library; - -export 'main.dart'; From 8ca1c5f0f820780cd3dd2be2c726fafd40ff6faa Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 12:07:46 +0300 Subject: [PATCH 23/27] refactor: move test package to dev_dependencies as pana resolves it correctly --- pubspec.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index eaaba9fd..ec9d7ca7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,12 +28,11 @@ dependencies: glob: ^2.1.3 path: ^1.9.1 yaml: ^3.1.3 - # These packages are required for pana analysis to run correctly - test: ^1.31.2 dev_dependencies: args: ^2.7.0 analyzer_testing: ^0.3.4 + test: ^1.31.2 test_reflective_loader: ^0.4.0 plugin: From aa2c2aad5c3791828c83a24fb1b33365514d2348 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 12:13:32 +0300 Subject: [PATCH 24/27] chore: update minimum Dart SDK constraint to 3.12.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index ec9d7ca7..ba99cf11 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ platforms: windows: environment: - sdk: ">=3.13.0 <4.0.0" + sdk: ">=3.12.0 <4.0.0" dependencies: # Needed until required types for fixes are exported by analyzer_server_plugin From 266005aee1e248ea12613b635484ebae292d1310 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 12:32:51 +0300 Subject: [PATCH 25/27] style: reformat Dart code to improve readability and apply consistent indentation --- .../visitors/ast_structural_hash_visitor.dart | 10 +- .../visitors/getter_variable_visitor.dart | 20 ++-- .../unnecessary_where_type_visitor.dart | 16 ++-- ...ive_names_for_type_parameters_visitor.dart | 8 +- lib/src/models/rule_with_fixes.dart | 7 +- lib/src/utils/node_utils.dart | 10 +- lib/src/utils/typecast_utils.dart | 11 +-- .../global_hash_registry_test.dart | 91 ++++++++++--------- .../prefer_match_file_name_rule_test.dart | 3 +- 9 files changed, 84 insertions(+), 92 deletions(-) diff --git a/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart b/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart index 0747752a..70a6ca04 100644 --- a/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart +++ b/lib/src/lints/avoid_duplicate_code/visitors/ast_structural_hash_visitor.dart @@ -93,12 +93,10 @@ class AstStructuralHashVisitor extends UnifyingAstVisitor { @override void visitPrefixExpression(PrefixExpression node) { - if (node - case PrefixExpression( - operator: Token(type: TokenType.MINUS || TokenType.PLUS), - operand: IntegerLiteral() || DoubleLiteral(), - ) - when _ignoreLiterals) { + if (node case PrefixExpression( + operator: Token(type: TokenType.MINUS || TokenType.PLUS), + operand: IntegerLiteral() || DoubleLiteral(), + ) when _ignoreLiterals) { node.operand.accept(this); } else { _append(node.operator.lexeme); diff --git a/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart b/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart index 27f40dc8..c9afd760 100644 --- a/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart +++ b/lib/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart @@ -17,17 +17,15 @@ class GetterVariableVisitor extends RecursiveAstVisitor { @override void visitVariableDeclaration(VariableDeclaration node) { - if (node - case VariableDeclaration( - declaredFragment: VariableFragment( - element: VariableElement( - isPrivate: true, - isFinal: true, - :final id, - ), - ), - ) - when id == _getterId) { + if (node case VariableDeclaration( + declaredFragment: VariableFragment( + element: VariableElement( + isPrivate: true, + isFinal: true, + :final id, + ), + ), + ) when id == _getterId) { _variable = node; } diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart index a2b56938..6b39c3df 100644 --- a/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart +++ b/lib/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart @@ -45,15 +45,13 @@ class UnnecessaryWhereTypeVisitor extends SimpleAstVisitor { } bool _isUnnecessaryWhereType(MethodInvocation node) { - if (node - case MethodInvocation( - methodName: Identifier( - name: AvoidUnnecessaryTypeAssertionsRule.whereTypeMethodName, - ), - target: Expression(staticType: final InterfaceType targetType), - typeArguments: TypeArgumentList(:final arguments), - ) - when arguments.isNotEmpty) { + if (node case MethodInvocation( + methodName: Identifier( + name: AvoidUnnecessaryTypeAssertionsRule.whereTypeMethodName, + ), + target: Expression(staticType: final InterfaceType targetType), + typeArguments: TypeArgumentList(:final arguments), + ) when arguments.isNotEmpty) { final targetIterableType = switch (targetType) { InterfaceType(isDartCoreIterable: true) => targetType, InterfaceType(:final allSupertypes) => allSupertypes.firstWhereOrNull( diff --git a/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart b/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart index 51655562..36919c64 100644 --- a/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart +++ b/lib/src/lints/use_descriptive_names_for_type_parameters/visitors/use_descriptive_names_for_type_parameters_visitor.dart @@ -15,11 +15,9 @@ class UseDescriptiveNamesForTypeParametersVisitor UseDescriptiveNamesForTypeParametersVisitor(this._rule, this._parameters); void _visit(TypeParameterList? types) { - if (types - case TypeParameterList( - typeParameters: final ps, - ) - when ps.length >= _minParameters) { + if (types case TypeParameterList( + typeParameters: final ps, + ) when ps.length >= _minParameters) { ps.where(_hasInvalidShortName).forEach(_report); } } diff --git a/lib/src/models/rule_with_fixes.dart b/lib/src/models/rule_with_fixes.dart index b93d517e..2be5ca98 100644 --- a/lib/src/models/rule_with_fixes.dart +++ b/lib/src/models/rule_with_fixes.dart @@ -3,9 +3,10 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/error/error.dart'; /// A function that creates a [CorrectionProducer] for a given context. -typedef ProducerGenerator = CorrectionProducer Function({ - required CorrectionProducerContext context, -}); +typedef ProducerGenerator = + CorrectionProducer Function({ + required CorrectionProducerContext context, + }); /// A collection of diagnostic codes and their associated fix generators. typedef FixesForCodes = Iterable>; diff --git a/lib/src/utils/node_utils.dart b/lib/src/utils/node_utils.dart index 5f931725..1714b856 100644 --- a/lib/src/utils/node_utils.dart +++ b/lib/src/utils/node_utils.dart @@ -308,12 +308,10 @@ extension ExpressionNullableExtension on Expression? { while (true) { if (current case AsExpression(:final expression)) { current = expression.unParenthesized; - } else if (current - case PostfixExpression( - :final operand, - :final operator, - ) - when operator.type == TokenType.BANG) { + } else if (current case PostfixExpression( + :final operand, + :final operator, + ) when operator.type == TokenType.BANG) { current = operand.unParenthesized; } else { return current; diff --git a/lib/src/utils/typecast_utils.dart b/lib/src/utils/typecast_utils.dart index b8c36e9b..e90fe2e6 100644 --- a/lib/src/utils/typecast_utils.dart +++ b/lib/src/utils/typecast_utils.dart @@ -65,13 +65,10 @@ class TypeCast { return false; } - if (this - case TypeCast( - source: final objectType, - target: final castedType, - ) - when objectType is ParameterizedType && - castedType is ParameterizedType) { + if (this case TypeCast( + source: final objectType, + target: final castedType, + ) when objectType is ParameterizedType && castedType is ParameterizedType) { if (objectType.typeArguments.length != castedType.typeArguments.length) { return false; } diff --git a/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart b/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart index 01672690..93c8de1e 100644 --- a/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart +++ b/test/src/lints/avoid_duplicate_code/global_hash_registry_test.dart @@ -386,52 +386,55 @@ void main() { expect(params1, isNot(equals(paramsDifferentExclude))); }); - test('does not match or clear files from sibling directories with prefixing names', () { - final currentRoot = io.Directory.current.path; - final siblingRoot = '${currentRoot}_sibling'; - final siblingFilePath = p.normalize(p.join(siblingRoot, 'file.dart')); - final projectFilePath = p.normalize(p.join(currentRoot, 'file.dart')); - - registry.updateFile(projectFilePath, [ - const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), - ], modificationStamp: 1); - - registry.updateFile(siblingFilePath, [ - const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), - ], modificationStamp: 1); - - expect(registry.fileCount, equals(2)); - - // 1. findCrossFileMatches should not find duplicate in siblingFilePath - // if limited to currentRoot. - final matches = registry.findCrossFileMatches(projectFilePath, [ - const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), - ], packageRoot: currentRoot); - expect(matches, isEmpty); - - // 2. clearEntriesForRoot should not clear siblingFilePath when - // clearing currentRoot. - final newParams = AvoidDuplicateCodeParameters( - minTokens: 40, - ignoreLiterals: false, - ignoreIdentifiers: false, - checkBlocks: true, - exclude: AvoidDuplicateCodeParameters.empty().exclude, - ); + test( + 'does not match or clear files from sibling directories with prefixing names', + () { + final currentRoot = io.Directory.current.path; + final siblingRoot = '${currentRoot}_sibling'; + final siblingFilePath = p.normalize(p.join(siblingRoot, 'file.dart')); + final projectFilePath = p.normalize(p.join(currentRoot, 'file.dart')); + + registry.updateFile(projectFilePath, [ + const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), + ], modificationStamp: 1); + + registry.updateFile(siblingFilePath, [ + const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), + ], modificationStamp: 1); + + expect(registry.fileCount, equals(2)); + + // 1. findCrossFileMatches should not find duplicate in siblingFilePath + // if limited to currentRoot. + final matches = registry.findCrossFileMatches(projectFilePath, [ + const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5), + ], packageRoot: currentRoot); + expect(matches, isEmpty); + + // 2. clearEntriesForRoot should not clear siblingFilePath when + // clearing currentRoot. + final newParams = AvoidDuplicateCodeParameters( + minTokens: 40, + ignoreLiterals: false, + ignoreIdentifiers: false, + checkBlocks: true, + exclude: AvoidDuplicateCodeParameters.empty().exclude, + ); - registry.updateFile( - projectFilePath, - [const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5)], - modificationStamp: 1, - parameters: newParams, - packageRoot: currentRoot, - ); + registry.updateFile( + projectFilePath, + [const HashEntry(hash: 123, lineNumber: 10, tokenCount: 5)], + modificationStamp: 1, + parameters: newParams, + packageRoot: currentRoot, + ); - expect( - registry.getFileEntries(siblingFilePath, packageRoot: siblingRoot), - isNotNull, - ); - }); + expect( + registry.getFileEntries(siblingFilePath, packageRoot: siblingRoot), + isNotNull, + ); + }, + ); test( 'debounces save operations independently for different package roots', diff --git a/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart b/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart index 16572bb7..581b0b9f 100644 --- a/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart +++ b/test/src/lints/prefer_match_file_name/prefer_match_file_name_rule_test.dart @@ -202,7 +202,8 @@ final someVariable = 42; '''); } - void test_does_not_report_on_multiple_public_declarations_if_first_matches() async { + void + test_does_not_report_on_multiple_public_declarations_if_first_matches() async { await assertNoDiagnostics(r''' class Test {} class AnotherPublicClass {} From 500083dc8bd785fefe98a051219512f02331bcfb Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 12:51:32 +0300 Subject: [PATCH 26/27] refactor: migrate fix imports to analysis_server_plugin and remove analyzer_plugin dependency --- CHANGELOG.md | 2 ++ .../fixes/avoid_final_with_getter_fix.dart | 4 ++-- .../fixes/avoid_unnecessary_type_assertions_fix.dart | 4 ++-- .../fixes/double_literal_format_fix.dart | 4 ++-- .../fixes/named_parameters_ordering_fix.dart | 4 ++-- .../fixes/prefer_conditional_expressions_fix.dart | 4 ++-- lib/src/lints/prefer_first/fixes/prefer_first_fix.dart | 4 ++-- lib/src/lints/prefer_last/fixes/prefer_last_fix.dart | 4 ++-- .../fixes/rename_nearest_context_parameter_fix.dart | 4 ++-- .../fixes/replace_with_nearest_context_parameter_fix.dart | 4 ++-- pubspec.yaml | 3 --- 11 files changed, 20 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 017f9e96..2711f574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 1.0.0-dev.2 +- Upgraded minimum Dart SDK constraint to `>=3.12.0`. +- Upgraded `analyzer` to `^14.1.0` and `analysis_server_plugin` to `^0.3.20`. - Resolved false positives in `avoid_returning_widgets` rule. - Resolved false positives on constructors in `number_of_parameters` rule. - Improved `member_ordering` configuration. diff --git a/lib/src/lints/avoid_final_with_getter/fixes/avoid_final_with_getter_fix.dart b/lib/src/lints/avoid_final_with_getter/fixes/avoid_final_with_getter_fix.dart index 49d25888..b97a97a2 100644 --- a/lib/src/lints/avoid_final_with_getter/fixes/avoid_final_with_getter_fix.dart +++ b/lib/src/lints/avoid_final_with_getter/fixes/avoid_final_with_getter_fix.dart @@ -1,9 +1,9 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart'; import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart'; import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/variable_references_visitor.dart'; diff --git a/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart b/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart index 814520c1..f5b1fe3c 100644 --- a/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart +++ b/lib/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart @@ -1,9 +1,9 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/source/source_range.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart'; /// A Quick fix for `avoid_unnecessary_type_assertions` rule diff --git a/lib/src/lints/double_literal_format/fixes/double_literal_format_fix.dart b/lib/src/lints/double_literal_format/fixes/double_literal_format_fix.dart index 9b7e0426..5137afeb 100644 --- a/lib/src/lints/double_literal_format/fixes/double_literal_format_fix.dart +++ b/lib/src/lints/double_literal_format/fixes/double_literal_format_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart'; import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_utils.dart'; diff --git a/lib/src/lints/named_parameters_ordering/fixes/named_parameters_ordering_fix.dart b/lib/src/lints/named_parameters_ordering/fixes/named_parameters_ordering_fix.dart index 4349e8d0..70cb0053 100644 --- a/lib/src/lints/named_parameters_ordering/fixes/named_parameters_ordering_fix.dart +++ b/lib/src/lints/named_parameters_ordering/fixes/named_parameters_ordering_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:collection/collection.dart'; import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; import 'package:solid_lints/src/lints/named_parameters_ordering/models/named_parameters_ordering_parameters.dart'; diff --git a/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart b/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart index 76503be7..eec2fc69 100644 --- a/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart +++ b/lib/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/prefer_conditional_expressions/models/statement_info.dart'; import 'package:solid_lints/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart'; diff --git a/lib/src/lints/prefer_first/fixes/prefer_first_fix.dart b/lib/src/lints/prefer_first/fixes/prefer_first_fix.dart index 0dad920b..81ba2ebd 100644 --- a/lib/src/lints/prefer_first/fixes/prefer_first_fix.dart +++ b/lib/src/lints/prefer_first/fixes/prefer_first_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/prefer_first/prefer_first_rule.dart'; /// A Quick fix for `prefer_first` rule diff --git a/lib/src/lints/prefer_last/fixes/prefer_last_fix.dart b/lib/src/lints/prefer_last/fixes/prefer_last_fix.dart index 105d6cc0..3272391c 100644 --- a/lib/src/lints/prefer_last/fixes/prefer_last_fix.dart +++ b/lib/src/lints/prefer_last/fixes/prefer_last_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/prefer_last/prefer_last_rule.dart'; /// A Quick fix for `prefer_last` rule diff --git a/lib/src/lints/use_nearest_context/fixes/rename_nearest_context_parameter_fix.dart b/lib/src/lints/use_nearest_context/fixes/rename_nearest_context_parameter_fix.dart index 8f10e84b..d4ed9b7a 100644 --- a/lib/src/lints/use_nearest_context/fixes/rename_nearest_context_parameter_fix.dart +++ b/lib/src/lints/use_nearest_context/fixes/rename_nearest_context_parameter_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/use_nearest_context/use_nearest_context_rule.dart'; import 'package:solid_lints/src/lints/use_nearest_context/utils/use_nearest_context_utils.dart'; diff --git a/lib/src/lints/use_nearest_context/fixes/replace_with_nearest_context_parameter_fix.dart b/lib/src/lints/use_nearest_context/fixes/replace_with_nearest_context_parameter_fix.dart index 8baaa491..8030d5ee 100644 --- a/lib/src/lints/use_nearest_context/fixes/replace_with_nearest_context_parameter_fix.dart +++ b/lib/src/lints/use_nearest_context/fixes/replace_with_nearest_context_parameter_fix.dart @@ -1,8 +1,8 @@ +import 'package:analysis_server_plugin/edit/change_builder/change_builder.dart'; import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; +import 'package:analysis_server_plugin/edit/fix/fix.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; -import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; import 'package:solid_lints/src/lints/use_nearest_context/use_nearest_context_rule.dart'; import 'package:solid_lints/src/lints/use_nearest_context/utils/use_nearest_context_utils.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index ba99cf11..0f7c585d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,9 +18,6 @@ environment: sdk: ">=3.12.0 <4.0.0" dependencies: - # Needed until required types for fixes are exported by analyzer_server_plugin - # More details: https://github.com/dart-lang/sdk/issues/61821 - analyzer_plugin: ^0.14.14 analyzer: ^14.1.0 collection: ^1.19.1 analysis_server_plugin: ^0.3.20 From 3df96b83accbfb389dbee4ede0deba07edea8070 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Tue, 25 Aug 2026 13:16:06 +0300 Subject: [PATCH 27/27] fix: update record field type check and add test cases for magic numbers in record literals --- .../visitors/no_magic_number_rule_visitor.dart | 2 +- .../no_magic_number/no_magic_number_rule_test.dart | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart b/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart index a9db8ac0..931766c6 100644 --- a/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart +++ b/lib/src/lints/no_magic_number/visitors/no_magic_number_rule_visitor.dart @@ -71,7 +71,7 @@ class NoMagicNumberRuleVisitor extends SimpleAstVisitor { return p is TypedLiteral || p is MapLiteralEntry || p is RecordLiteral || - p is RecordLiteralField; + p is RecordLiteralNamedField; } bool _isWidgetParameter(Literal literal) { diff --git a/test/src/lints/no_magic_number/no_magic_number_rule_test.dart b/test/src/lints/no_magic_number/no_magic_number_rule_test.dart index bd4ff8c7..62ff52a0 100644 --- a/test/src/lints/no_magic_number/no_magic_number_rule_test.dart +++ b/test/src/lints/no_magic_number/no_magic_number_rule_test.dart @@ -302,7 +302,17 @@ void fn() {} await assertNoDiagnostics(r''' void fn() { var point = (10, 20); + var negativePoint = (-10, -20); var named = (x: 100, y: 200); + var negativeNamed = (x: -100, y: -200); +} +'''); + } + + Future test_reports_magic_number_in_record_expressions() async { + await assertAutoDiagnostics(''' +void fn() { + var point = (${expectLint('10')} + ${expectLint('42')}, x: ${expectLint('100')} * ${expectLint('5')}); } '''); }