From 597a169c143f94d89a9f7ffcf450041fab8d6079 Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Mon, 15 Jun 2026 22:42:03 +0300 Subject: [PATCH 1/2] refactor: migrate avoid_unused_parameters --- lib/main.dart | 6 +- .../avoid_returning_widgets_rule.dart | 11 +- .../avoid_unused_parameters_rule.dart | 99 +++-- .../models/avoid_unused_parameters.dart | 20 - .../avoid_unused_parameters_parameters.dart | 27 ++ .../avoid_unused_parameters_visitor.dart | 69 ++-- lib/src/utils/parameter_utils.dart | 22 - lint_test/avoid_unused_parameters_test.dart | 243 ----------- .../avoid_unused_parameters_rule_test.dart | 384 ++++++++++++++++++ test/utils/fake_analysis_options_loader.dart | 15 + 10 files changed, 531 insertions(+), 365 deletions(-) delete mode 100644 lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters.dart create mode 100644 lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters_parameters.dart delete mode 100644 lint_test/avoid_unused_parameters_test.dart create mode 100644 test/lints/avoid_unused_parameters/avoid_unused_parameters_rule_test.dart create mode 100644 test/utils/fake_analysis_options_loader.dart diff --git a/lib/main.dart b/lib/main.dart index f2bab9aa..bbd0d7b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,7 +7,7 @@ import 'package:solid_lints/src/lints/avoid_final_with_getter/fixes/avoid_final_ import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart'; import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart'; import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; -import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart'; +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/proper_super_calls/proper_super_calls_rule.dart'; @@ -40,7 +40,9 @@ class SolidLintsPlugin extends Plugin { ProperSuperCallsRule(), AvoidReturningWidgetsRule( analysisOptionsLoader: analysisLoader, - parametersParser: AvoidReturningWidgetsParameters.fromJson, + ), + AvoidUnusedParametersRule( + analysisOptionsLoader: analysisLoader, ), ]; diff --git a/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart b/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart index c90f2d97..be2c6ac3 100644 --- a/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart +++ b/lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart @@ -66,11 +66,11 @@ class AvoidReturningWidgetsRule /// Creates a new instance of [AvoidReturningWidgetsRule] AvoidReturningWidgetsRule({ required super.analysisOptionsLoader, - required super.parametersParser, }) : super.withParameters( - name: _code.lowerCaseName, - description: _code.problemMessage, - ); + name: _code.lowerCaseName, + description: _code.problemMessage, + parametersParser: AvoidReturningWidgetsParameters.fromJson, + ); @override void registerNodeProcessors( @@ -79,7 +79,8 @@ class AvoidReturningWidgetsRule ) { super.registerNodeProcessors(registry, context); - final parameters = getParametersForContext(context) ?? + final parameters = + getParametersForContext(context) ?? AvoidReturningWidgetsParameters.empty(); final visitor = AvoidReturningWidgetsVisitor(this, parameters); diff --git a/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart b/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart index 383425e3..ab9d7e9c 100644 --- a/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart +++ b/lib/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart @@ -1,14 +1,21 @@ -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:solid_lints/src/lints/avoid_unused_parameters/models/avoid_unused_parameters.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/avoid_unused_parameters/models/avoid_unused_parameters_parameters.dart'; import 'package:solid_lints/src/lints/avoid_unused_parameters/visitors/avoid_unused_parameters_visitor.dart'; -import 'package:solid_lints/src/models/rule_config.dart'; import 'package:solid_lints/src/models/solid_lint_rule.dart'; -/// Warns about unused function, method, constructor or factory parameters. +/// Warns about unused function, method, constructor, or factory parameters. +/// +/// Named parameters are always allowed because they document the API surface. +/// Parameters whose names consist only of underscores are also ignored. +/// Overridden methods and methods used as tear-offs are skipped. +/// +/// {@template solid_lints.avoid_unused_parameters.example} +/// ### Example /// -/// ### Example: /// #### BAD: +/// /// ```dart /// typedef MaxFun = int Function(int a, int b); /// final MaxFun bad = (int a, int b) => 1; // LINT @@ -18,6 +25,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// final optional = (int a, [int b = 0]) { // LINT /// return a; /// }; +/// /// void fun(String x) {} // LINT /// void fun2(String x, String y) { // LINT /// print(y); @@ -35,6 +43,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// ``` /// /// #### GOOD: +/// /// ```dart /// typedef MaxFun = int Function(int a, int b); /// final MaxFun good = (int a, int b) => a + b; @@ -47,63 +56,67 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// } /// /// class TestClass { -/// static void staticMethod(int _) {} // OK -/// void method(String _) {} // OK +/// static void staticMethod(int _) {} +/// void method(String _) {} /// -/// TestClass([int _]); // OK -/// factory TestClass.named(int _) { // OK +/// TestClass([int _]); +/// factory TestClass.named(int _) { /// return TestClass(); /// } /// } /// ``` /// /// #### Allowed: +/// /// ```dart /// typedef Named = String Function({required String text}); /// final Named named = ({required text}) { -/// return ''; +/// return ''; /// }; -/// /// ``` -class AvoidUnusedParametersRule extends SolidLintRule { - /// This lint rule represents - /// the error whether we use bad formatted double literals. - static const String lintName = 'avoid_unused_parameters'; +/// {@endtemplate} +class AvoidUnusedParametersRule + extends SolidLintRule { + /// The name of this lint rule. + static const lintName = 'avoid_unused_parameters'; - AvoidUnusedParametersRule._(super.config); + /// Reported when a parameter is declared but never read in the body. + /// + /// {@macro solid_lints.avoid_unused_parameters.example} + static const LintCode _code = LintCode( + lintName, + 'Avoid unused parameters.', + correctionMessage: + 'Remove the parameter or replace its name with underscores.', + ); - /// Creates a new instance of [AvoidUnusedParametersRule] - /// based on the lint configuration. - factory AvoidUnusedParametersRule.createRule( - CustomLintConfigs configs, - ) { - final rule = RuleConfig( - configs: configs, - name: lintName, - paramsParser: AvoidUnusedParameters.fromJson, - problemMessage: (_) => 'Parameter is unused.', - ); + @override + LintCode get diagnosticCode => _code; - return AvoidUnusedParametersRule._(rule); - } + /// Creates a new instance of [AvoidUnusedParametersRule]. + AvoidUnusedParametersRule({ + required super.analysisOptionsLoader, + }) : super.withParameters( + name: lintName, + description: + 'Warns about unused function, method, constructor, or factory ' + 'parameters.', + parametersParser: AvoidUnusedParametersParameters.fromJson, + ); @override - void run( - CustomLintResolver resolver, - DiagnosticReporter reporter, - CustomLintContext context, + void registerNodeProcessors( + RuleVisitorRegistry registry, + RuleContext context, ) { - context.registry.addDeclaration((node) { - final isIgnored = config.parameters.exclude.shouldIgnore(node); + super.registerNodeProcessors(registry, context); - if (isIgnored) return; + final parameters = + getParametersForContext(context) ?? + AvoidUnusedParametersParameters.empty(); - final visitor = AvoidUnusedParametersVisitor(); - node.accept(visitor); + final visitor = AvoidUnusedParametersVisitor(this, parameters); - for (final element in visitor.unusedParameters) { - reporter.atNode(element, code); - } - }); + registry.addCompilationUnit(this, visitor); } } diff --git a/lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters.dart b/lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters.dart deleted file mode 100644 index f4dac97b..00000000 --- a/lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart'; - -/// A data model class that represents the "avoid returning widgets" input -/// parameters. -class AvoidUnusedParameters { - /// A list of methods that should be excluded from the lint. - final ExcludedIdentifiersListParameter exclude; - - /// Constructor for [AvoidUnusedParameters] model - AvoidUnusedParameters({ - required this.exclude, - }); - - /// Method for creating from json data - factory AvoidUnusedParameters.fromJson(Map json) { - return AvoidUnusedParameters( - exclude: ExcludedIdentifiersListParameter.defaultFromJson(json), - ); - } -} diff --git a/lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters_parameters.dart b/lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters_parameters.dart new file mode 100644 index 00000000..a6a20cbd --- /dev/null +++ b/lib/src/lints/avoid_unused_parameters/models/avoid_unused_parameters_parameters.dart @@ -0,0 +1,27 @@ +import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart'; + +/// A data model class that represents the `avoid_unused_parameters` input +/// parameters. +class AvoidUnusedParametersParameters { + /// A list of methods that should be excluded from the lint. + final ExcludedIdentifiersListParameter exclude; + + /// Constructor for [AvoidUnusedParametersParameters] model. + AvoidUnusedParametersParameters({ + required this.exclude, + }); + + /// Empty [AvoidUnusedParametersParameters] model, excludes nothing. + factory AvoidUnusedParametersParameters.empty() { + return AvoidUnusedParametersParameters( + exclude: ExcludedIdentifiersListParameter(exclude: []), + ); + } + + /// Method for creating from json data. + factory AvoidUnusedParametersParameters.fromJson(Map json) { + return AvoidUnusedParametersParameters( + exclude: ExcludedIdentifiersListParameter.defaultFromJson(json), + ); + } +} 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 a3bc2f6e..d99df70d 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 @@ -25,16 +25,18 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:collection/collection.dart'; +import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unused_parameters/models/avoid_unused_parameters_parameters.dart'; import 'package:solid_lints/src/utils/node_utils.dart'; import 'package:solid_lints/src/utils/parameter_utils.dart'; -/// AST Visitor which finds all is expressions and checks if they are -/// unrelated (result always false) +/// A visitor that reports unused formal parameters. class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { - final _unusedParameters = []; + final AvoidUnusedParametersRule _rule; + final AvoidUnusedParametersParameters _parameters; - /// List of unused parameters - Iterable get unusedParameters => _unusedParameters; + /// Creates a new instance of [AvoidUnusedParametersVisitor]. + AvoidUnusedParametersVisitor(this._rule, this._parameters); @override void visitConstructorDeclaration(ConstructorDeclaration node) { @@ -48,15 +50,18 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { parameters.parameters.isEmpty) { return; } + + if (_isExcluded(node)) return; + final unused = _getUnusedParameters( node.body, parameters.parameters, initializers: node.initializers, ).whereNot(nameConsistsOfUnderscoresOnly); - _unusedParameters.addAll( - unused, - ); + for (final parameter in unused) { + _rule.reportAtNode(parameter); + } } @override @@ -73,34 +78,38 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { return; } - final isTearOff = _usedAsTearOff(node); + if (_isExcluded(node) || isOverride(node.metadata) || _isATearOff(node)) { + return; + } - if (!isOverride(node.metadata) && !isTearOff) { - _unusedParameters.addAll( - _filterOutUnderscoresAndNamed( - node.body, - parameters.parameters, - ), - ); + for (final parameter in _filterOutUnderscoresAndNamed( + node.body, + parameters.parameters, + )) { + _rule.reportAtNode(parameter); } } @override void visitFunctionExpression(FunctionExpression node) { super.visitFunctionExpression(node); + + final declaration = node.thisOrAncestorOfType(); + if (declaration != null && _isExcluded(declaration)) return; + final params = node.parameters; - if (params == null) { - return; - } + if (params == null) return; - _unusedParameters.addAll( - _filterOutUnderscoresAndNamed( - node.body, - params.parameters, - ), - ); + for (final parameter in _filterOutUnderscoresAndNamed( + node.body, + params.parameters, + )) { + _rule.reportAtNode(parameter); + } } + bool _isExcluded(Declaration node) => _parameters.exclude.shouldIgnore(node); + Iterable _filterOutUnderscoresAndNamed( AstNode body, Iterable parameters, @@ -109,9 +118,10 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { body, parameters, ); - return unused.whereNot(nameConsistsOfUnderscoresOnly).where( - (param) => !param.isNamed, - ); + + return unused + .whereNot(nameConsistsOfUnderscoresOnly) + .where((param) => !param.isNamed); } Set _getUnusedParameters( @@ -145,7 +155,6 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { isFieldFormalParameter = parameter.toSource().contains('this.'); isSuperFormalParameter = parameter.toSource().contains('super.'); } - // if (name != null && !isPresentInAll && @@ -158,7 +167,7 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { return result; } - bool _usedAsTearOff(MethodDeclaration node) { + bool _isATearOff(MethodDeclaration node) { final name = node.name.lexeme; if (!Identifier.isPrivateName(name)) { return false; diff --git a/lib/src/utils/parameter_utils.dart b/lib/src/utils/parameter_utils.dart index 6e914ec6..a962673c 100644 --- a/lib/src/utils/parameter_utils.dart +++ b/lib/src/utils/parameter_utils.dart @@ -1,6 +1,5 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/error/error.dart' as error; -import 'package:custom_lint_builder/custom_lint_builder.dart'; /// Checks if parameter name consists only of underscores bool nameConsistsOfUnderscoresOnly(FormalParameter parameter) { @@ -21,24 +20,3 @@ error.DiagnosticSeverity? decodeErrorSeverity(String? severity) { _ => null, }; } - -/// Extension to create a copy of [LintCode] -extension LintCodeCopyWith on LintCode { - /// Returns a copy of [LintCode] with specified fields replaced - LintCode copyWith({ - String? name, - String? problemMessage, - String? correctionMessage, - String? uniqueName, - String? url, - error.DiagnosticSeverity? errorSeverity, - }) => - LintCode( - name: name ?? this.name, - problemMessage: problemMessage ?? this.problemMessage, - correctionMessage: correctionMessage ?? this.correctionMessage, - uniqueName: uniqueName ?? this.uniqueName, - url: url ?? this.url, - errorSeverity: errorSeverity ?? this.errorSeverity, - ); -} diff --git a/lint_test/avoid_unused_parameters_test.dart b/lint_test/avoid_unused_parameters_test.dart deleted file mode 100644 index 075269a6..00000000 --- a/lint_test/avoid_unused_parameters_test.dart +++ /dev/null @@ -1,243 +0,0 @@ -// ignore_for_file: prefer_const_declarations, prefer_match_file_name, avoid_global_state, named_parameters_ordering -// ignore_for_file: unnecessary_nullable_for_final_variable_declarations -// ignore_for_file: unused_local_variable -// ignore_for_file: unused_element -// ignore_for_file: newline_before_return -// ignore_for_file: no_empty_block -// ignore_for_file: member_ordering - -import 'package:flutter/material.dart'; - -/// Check the `avoid_unused_parameters` rule -/// -void testNamed() { - SomeAnotherClass( - func: ({required text}) {}, - ); -} - -typedef MaxFun = int Function(int a, int b); - -typedef Named = String Function({required String text}); - -typedef ReqNamed = void Function({required String text}); - -// expect_lint: avoid_unused_parameters -final MaxFun bad = (int a, int b) => 1; - -final MaxFun good = (int a, _) => a; - -final MaxFun ok = (int _, int __) => 1; - -final MaxFun goodMax = (int a, int b) { - return a + b; -}; - -final Named _named = ({required text}) { - return ''; -}; - -void ch({String text = ''}) {} - -final Named _named2 = ({required text}) { - return text; -}; - -final Named _named3 = ({required text}) => ''; - -final Named _named4 = ({required text}) => text; - -// expect_lint: avoid_unused_parameters -final optional = (int a, [int b = 0]) { - return a; -}; - -// expect_lint: avoid_unused_parameters -final named = (int a, {required int b, int c = 0}) { - return c; -}; - -// good -var k = (String g) { - return g; -}; - -// expect_lint: avoid_unused_parameters -var c = (String g) { - return '0'; -}; - -// expect_lint: avoid_unused_parameters -final MaxFun tetsFun = (int a, int b) { - return 4; -}; - -// expect_lint: avoid_unused_parameters -void fun(String s) { - return; -} - -// expect_lint: avoid_unused_parameters -void fun2(String s) { - return; -} - -class TestClass { - // expect_lint: avoid_unused_parameters - static void staticMethod(int a) {} - - // expect_lint: avoid_unused_parameters - void method(String s) { - return; - } - - void methodWithUnderscores(int _) {} -} - -class TestClass2 { - void method(String _) { - return; - } -} - -class SomeOtherClass { - // expect_lint: avoid_unused_parameters - final MaxFun maxFunLint = (int a, int b) => 1; - - // Good - final MaxFun good = (int a, int b) { - return a * b; - }; - - // expect_lint: avoid_unused_parameters - void method(String s) { - return; - } -} - -class SomeAnotherClass extends SomeOtherClass { - final ReqNamed func; - - SomeAnotherClass({ - required this.func, - }); - - @override - void method(String s) {} -} - -void someOtherFunction(String s) { - print(s); - return; -} - -class SomeOtherAnotherClass { - void method(String s) { - print(s); - return; - } - - // expect_lint: avoid_unused_parameters - void anonymousCallback(Function(int a) cb) {} -} - -// expect_lint: avoid_unused_parameters -void closure(int a) { - void internal(int a) { - print(a); - } -} - -// expect_lint: avoid_unused_parameters -final MaxFun maxFunInstance = (int a, int b) => 1; - -final MaxFun m = (_, __) => 1; - -class Foo { - final int a; - final int? b; - - Foo._(this.a, this.b); - - Foo.name(this.a, this.b); - - Foo.coolName({required this.a, required this.b}); - - // expect_lint: avoid_unused_parameters - Foo.another({required int c}) - : a = 1, - b = 0; - - // expect_lint: avoid_unused_parameters - factory Foo.aOnly(int a) { - return Foo._(1, null); - } -} - -class Bar extends Foo { - Bar.name(super.a, super.b) : super.name(); -} - -class TestWidget extends StatelessWidget { - const TestWidget({ - super.key, - // expect_lint: avoid_unused_parameters - int a = 1, - // expect_lint: avoid_unused_parameters - String k = '', - }); - - // expect_lint: avoid_unused_parameters - factory TestWidget.a([int b = 0]) { - return TestWidget(k: ''); - } - - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} - -class UsingConstructorParameterInInitializer { - final int _value; - - UsingConstructorParameterInInitializer(int value) : _value = value; - - void printValue() { - print(_value); - } -} - -// no lint -void excludeMethod(String s) { - return; -} - -// no lint -void simpleMethodName(String s) { - return; -} - -class Exclude { - // no lint - void excludeMethod(String s) { - return; - } - -// expect_lint: avoid_unused_parameters - void excludeMethod2(String s) { - return; - } -} - -class SimpleClassName { - // no lint - void simpleMethodName(String s) { - return; - } - -// expect_lint: avoid_unused_parameters - void simpleMethodName2(String s) { - return; - } -} diff --git a/test/lints/avoid_unused_parameters/avoid_unused_parameters_rule_test.dart b/test/lints/avoid_unused_parameters/avoid_unused_parameters_rule_test.dart new file mode 100644 index 00000000..440ee2e4 --- /dev/null +++ b/test/lints/avoid_unused_parameters/avoid_unused_parameters_rule_test.dart @@ -0,0 +1,384 @@ +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:solid_lints/src/lints/avoid_unused_parameters/avoid_unused_parameters_rule.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../utils/fake_analysis_options_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidUnusedParametersRuleTest); + }); +} + +@reflectiveTest +class AvoidUnusedParametersRuleTest extends AnalysisRuleTest { + static const _importFlutterMaterial = + "import 'package:flutter/material.dart';"; + + static const _mockFlutterMaterialContent = ''' +abstract class Widget { + final Object? key; + + const Widget({this.key}); +} + +class StatelessWidget implements Widget { + const StatelessWidget({super.key}); + + Widget build(BuildContext context); +} + +abstract interface class BuildContext {} + +class Placeholder extends StatelessWidget { + const Placeholder({super.key}); + + @override + Widget build(BuildContext context) => throw 'unimplemented'; +} +'''; + + @override + void setUp() { + final FakeAnalysisOptionsLoader fakeAnalysisOptionsLoader = + FakeAnalysisOptionsLoader( + ruleOptions: { + 'exclude': [ + {'class_name': 'Exclude', 'method_name': 'excludeMethod'}, + {'method_name': 'excludeMethod'}, + 'simpleMethodName', + 'SimpleClassName', + 'exclude', + ], + }, + ); + + rule = AvoidUnusedParametersRule( + analysisOptionsLoader: fakeAnalysisOptionsLoader, + ); + newPackage('flutter') + ..addFile('lib/material.dart', _mockFlutterMaterialContent); + super.setUp(); + } + + Future test_reports_on_unused_function_expression_parameters() async { + await assertDiagnostics( + r''' +typedef MaxFun = int Function(int a, int b); + +final MaxFun bad = (int a, int b) => 1; + +final MaxFun tetsFun = (int a, int b) { + return 4; +}; + +var c = (String g) { + return '0'; +}; + +final MaxFun maxFunInstance = (int a, int b) => 1; +''', + [ + lint(66, 5), + lint(73, 5), + lint(111, 5), + lint(118, 5), + lint(152, 8), + lint(213, 5), + lint(220, 5), + ], + ); + } + + Future test_reports_on_unused_optional_and_named_parameters() async { + await assertDiagnostics( + r''' +final optional = (int a, [int b = 0]) { + return a; +}; + +final named = (int a, {required int b, int c = 0}) { + return c; +}; +''', + [lint(26, 9), lint(71, 5)], + ); + } + + Future test_reports_on_unused_top_level_functions() async { + await assertDiagnostics( + r''' +void fun(String s) { + return; +} + +void fun2(String s) { + return; +} + +void closure(int a) { + void internal(int a) { + print(a); + } +} +''', + [lint(9, 8), lint(44, 8), lint(82, 5)], + ); + } + + Future test_reports_on_unused_parameters_in_methods() async { + await assertDiagnostics( + r''' +class TestClass { + static void staticMethod(int a) {} + + void method(String s) { + return; + } +} + +class SomeOtherClass { + final MaxFun maxFunLint = (int a, int b) => 1; + + // Good + final MaxFun good = (int a, int b) { + return a * b; + }; + + void method(String s) { + return; + } +} + +typedef MaxFun = int Function(int a, int b); + +class SomeOtherAnotherClass { + void method(String s) { + print(s); + return; + } + + void anonymousCallback(Function(int a) cb) {} +} +''', + [ + lint(45, 5), + lint(70, 8), + lint(153, 5), + lint(160, 5), + lint(261, 8), + lint(450, 18), + ], + ); + } + + Future test_reports_on_unused_parameters_in_constructors() async { + await assertDiagnostics( + r''' +class Foo { + final int a; + final int? b; + + Foo.another({required int c}) + : a = 1, + b = 0; + + factory Foo.aOnly(int a) { + return Foo._(1, null); + } + + Foo._(this.a, this.b); +} +''', + [lint(59, 14), lint(127, 5)], + ); + } + + Future test_reports_on_unused_widget_constructor_parameters() async { + await assertDiagnostics( + ''' +$_importFlutterMaterial + +class TestWidget extends StatelessWidget { + const TestWidget({ + super.key, + int a = 1, + String k = '', + }); + + factory TestWidget.a([int b = 0]) { + return const TestWidget(); + } + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} +''', + [lint(124, 9), lint(139, 13), lint(185, 9)], + ); + } + + Future test_does_not_report_on_used_parameters() async { + await assertNoDiagnostics(r''' +typedef MaxFun = int Function(int a, int b); + +final MaxFun good = (int a, int b) => a + b; + +final MaxFun goodMax = (int a, int b) { + return a + b; +}; + +String Function(String g) k = (String g) { + return g; +}; + +void someOtherFunction(String s) { + print(s); +} +'''); + } + + Future test_does_not_report_on_underscore_parameters() async { + await assertNoDiagnostics(r''' +typedef MaxFun = int Function(int a, int b); + +final MaxFun good = (int a, _) => a; + +final MaxFun ok = (int _, int __) => 1; + +final MaxFun m = (_, __) => 1; + +class TestClass { + void methodWithUnderscores(int _) {} +} + +class TestClass2 { + void method(String _) { + return; + } +} +'''); + } + + Future test_does_not_report_on_named_parameters() async { + await assertNoDiagnostics(r''' +typedef Named = String Function({required String text}); + +final Named _named = ({required text}) { + return ''; +}; + +void ch({String text = ''}) {} + +final Named _named2 = ({required text}) { + return text; +}; + +final Named _named3 = ({required text}) => ''; + +final Named _named4 = ({required text}) => text; + +void testNamed() { + SomeAnotherClass( + func: ({required text}) {}, + ); +} + +typedef ReqNamed = void Function({required String text}); + +class SomeAnotherClass { + final ReqNamed func; + + SomeAnotherClass({ + required this.func, + }); +} +'''); + } + + Future + test_does_not_report_on_override_and_field_formal_parameters() async { + await assertNoDiagnostics(r''' +class Foo { + final int a; + final int? b; + + Foo.name(this.a, this.b); + + Foo.coolName({required this.a, required this.b}); + + Foo._(this.a, this.b); +} + +class Bar extends Foo { + Bar.name(super.a, super.b) : super.name(); +} + +class SomeAnotherClass extends SomeOtherClass { + @override + void method(String s) {} +} + +class SomeOtherClass { + void method(String s) { + print(s); + } +} + +class UsingConstructorParameterInInitializer { + final int _value; + + UsingConstructorParameterInInitializer(int value) : _value = value; + + void printValue() { + print(_value); + } +} +'''); + } + + Future test_does_not_report_on_excluded_declarations() async { + await assertNoDiagnostics(r''' +void excludeMethod(String s) { + return; +} + +void simpleMethodName(String s) { + return; +} + +class Exclude { + void excludeMethod(String s) { + return; + } +} + +class SimpleClassName { + void simpleMethodName(String s) { + return; + } +} +'''); + } + + Future test_reports_on_non_matching_excluded_declarations() async { + await assertDiagnostics( + r''' +class Exclude { + void excludeMethod2(String s) { + return; + } +} + +class SimpleClassName { + void simpleMethodName2(String s) { + return; + } +} +''', + [lint(38, 8), lint(118, 8)], + ); + } +} diff --git a/test/utils/fake_analysis_options_loader.dart b/test/utils/fake_analysis_options_loader.dart new file mode 100644 index 00000000..c8683879 --- /dev/null +++ b/test/utils/fake_analysis_options_loader.dart @@ -0,0 +1,15 @@ +import 'package:analyzer/analysis_rule/rule_context.dart'; +import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart'; + +class FakeAnalysisOptionsLoader implements AnalysisOptionsLoader { + final Map ruleOptions; + + FakeAnalysisOptionsLoader({required this.ruleOptions}); + + @override + Map? getRuleOptions(RuleContext context, String ruleName) => + ruleOptions; + + @override + void loadRulesOptionsFromContext(RuleContext context) {} +} From 5bb8c401d7e7cefda240ec596eccfbd0df3427d6 Mon Sep 17 00:00:00 2001 From: Andrew Bekhiet Date: Mon, 15 Jun 2026 22:51:18 +0300 Subject: [PATCH 2/2] fix: check if current function expression is inside an excluded declaration --- .../visitors/avoid_unused_parameters_visitor.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 d99df70d..e10fb310 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 @@ -94,7 +94,10 @@ class AvoidUnusedParametersVisitor extends RecursiveAstVisitor { void visitFunctionExpression(FunctionExpression node) { super.visitFunctionExpression(node); - final declaration = node.thisOrAncestorOfType(); + final declaration = + node.thisOrAncestorOfType() ?? + node.thisOrAncestorOfType() ?? + node.thisOrAncestorOfType(); if (declaration != null && _isExcluded(declaration)) return; final params = node.parameters;