From ec3dd52052eb1acaef57c4481f286a3818b16368 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Thu, 1 Jan 2026 01:10:53 +0300 Subject: [PATCH 1/2] [leancode_lint] Add fix for `catch_parameter_names` This commit introduces a new quick-fix for the `catch_parameter_names` lint. The `RenameCatchParameter` fix automatically renames incorrectly named exception and stack trace parameters in `catch` clauses to conform to the names configured for the lint. The fix also renames all usages of the parameter within the catch block's scope, properly handling shadowing from nested scopes. --- .../lib/fixes/rename_catch_parameter.dart | 209 ++++++++++++++++++ .../lib/lints/catch_parameter_names.dart | 4 + 2 files changed, 213 insertions(+) create mode 100644 packages/leancode_lint/lib/fixes/rename_catch_parameter.dart diff --git a/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart b/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart new file mode 100644 index 00000000..c6f43702 --- /dev/null +++ b/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart @@ -0,0 +1,209 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/diagnostic/diagnostic.dart'; +import 'package:analyzer/source/source_range.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:leancode_lint/lints/catch_parameter_names.dart'; + +class RenameCatchParameter extends DartFix { + RenameCatchParameter(this.config); + + final CatchParameterNamesConfig config; + + @override + Future run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + Diagnostic analysisError, + List others, + ) async { + // Get the resolved unit directly + final result = await resolver.getResolvedUnitResult(); + final unit = result.unit; + + // Find the catch clause containing the error + final node = unit.nodeCovering(offset: analysisError.offset); + final catchClause = node?.thisOrAncestorOfType(); + + if (catchClause == null) { + return; + } + + // Determine which parameter needs renaming based on error location + final (targetParam, isException) = _identifyTargetParameter(catchClause, analysisError.offset); + + if (targetParam == null) { + return; + } + + final oldName = targetParam.name.lexeme; + final newName = isException ? config.exceptionName : config.stackTraceName; + + if (oldName == newName || oldName == '_') { + return; + } + + // Collect all occurrences to rename + final occurrences = _collectOccurrences(catchClause, targetParam, oldName); + + if (occurrences.isEmpty) { + return; + } + + // Apply the rename + reporter + .createChangeBuilder(message: 'Rename `$oldName` to `$newName`', priority: 80) + .addDartFileEdit((builder) { + // Apply replacements in reverse order to maintain offsets + for (final range in occurrences.reversed) { + builder.addSimpleReplacement(range, newName); + } + }); + } + + /// Identifies which parameter (exception or stack trace) is being flagged. + /// Returns (parameter, isException) or (null, false) if not found. + (CatchClauseParameter?, bool) _identifyTargetParameter(CatchClause catchClause, int errorOffset) { + final exceptionParam = catchClause.exceptionParameter; + final stackParam = catchClause.stackTraceParameter; + + if (exceptionParam != null && _containsOffset(exceptionParam, errorOffset)) { + return (exceptionParam, true); + } + + if (stackParam != null && _containsOffset(stackParam, errorOffset)) { + return (stackParam, false); + } + + return (null, false); + } + + bool _containsOffset(CatchClauseParameter param, int offset) { + return offset >= param.offset && offset < param.end; + } + + /// Collects all occurrences of the parameter that need to be renamed. + List _collectOccurrences( + CatchClause catchClause, + CatchClauseParameter targetParam, + String oldName, + ) { + final occurrences = []; + final targetElement = targetParam.declaredFragment?.element; + + // Add the declaration itself + occurrences.add(SourceRange(targetParam.name.offset, targetParam.name.length)); + + // Find all usages in the catch body + final visitor = _UsageFinder( + oldName: oldName, + targetElement: targetElement, + declarationOffset: targetParam.name.offset, + catchClause: catchClause, + ); + + catchClause.body.visitChildren(visitor); + occurrences.addAll(visitor.occurrences); + + return occurrences; + } +} + +/// Visitor that finds all usages of a catch parameter within its scope, +/// accounting for shadowing by nested declarations. +class _UsageFinder extends RecursiveAstVisitor { + _UsageFinder({ + required this.oldName, + required this.targetElement, + required this.declarationOffset, + required this.catchClause, + }); + + final String oldName; + final Element? targetElement; + final int declarationOffset; + final CatchClause catchClause; + final List occurrences = []; + + @override + void visitSimpleIdentifier(SimpleIdentifier node) { + // Skip the declaration itself + if (node.offset == declarationOffset) { + return; + } + + // Only rename if the name matches + if (node.name != oldName) { + return; + } + + // Use element comparison for accuracy when available + if (targetElement != null && node.element != null) { + if (node.element == targetElement) { + occurrences.add(SourceRange(node.offset, node.length)); + } + } else { + // Fallback to name-based matching (when element resolution unavailable) + // This is safe within the same catch clause scope + occurrences.add(SourceRange(node.offset, node.length)); + } + + super.visitSimpleIdentifier(node); + } + + @override + void visitCatchClause(CatchClause node) { + // Don't descend into nested catch clauses - they have their own scope + // and might shadow our parameter + if (node != catchClause) { + // Check if this nested catch shadows our parameter + final exceptionName = node.exceptionParameter?.name.lexeme; + final stackName = node.stackTraceParameter?.name.lexeme; + + if (exceptionName == oldName || stackName == oldName) { + // Parameter is shadowed in this nested catch - don't visit it + return; + } + } + + super.visitCatchClause(node); + } + + @override + void visitFunctionDeclaration(FunctionDeclaration node) { + // Check if any parameter shadows our variable + if (_functionShadowsParameter(node)) { + return; + } + super.visitFunctionDeclaration(node); + } + + @override + void visitFunctionExpression(FunctionExpression node) { + // Check if any parameter shadows our variable + if (_functionExpressionShadowsParameter(node)) { + return; + } + super.visitFunctionExpression(node); + } + + bool _functionShadowsParameter(FunctionDeclaration node) { + final params = node.functionExpression.parameters?.parameters; + if (params == null) { + return false; + } + + return params.any((p) => p.name?.lexeme == oldName); + } + + bool _functionExpressionShadowsParameter(FunctionExpression node) { + final params = node.parameters?.parameters; + if (params == null) { + return false; + } + + return params.any((p) => p.name?.lexeme == oldName); + } +} diff --git a/packages/leancode_lint/lib/lints/catch_parameter_names.dart b/packages/leancode_lint/lib/lints/catch_parameter_names.dart index c228bd9f..33eb8f06 100644 --- a/packages/leancode_lint/lib/lints/catch_parameter_names.dart +++ b/packages/leancode_lint/lib/lints/catch_parameter_names.dart @@ -2,6 +2,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/error/error.dart' hide LintCode; import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:leancode_lint/fixes/rename_catch_parameter.dart'; final class CatchParameterNamesConfig { const CatchParameterNamesConfig({ @@ -86,6 +87,9 @@ class CatchParameterNames extends DartLintRule { }); } + @override + List getFixes() => [RenameCatchParameter(config)]; + void _checkParameter( CatchClauseParameter? node, _CatchClauseParameter param, From a56568330650b72a05990f0583149ebf85bdb5c4 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Thu, 1 Jan 2026 01:29:03 +0300 Subject: [PATCH 2/2] [leancode_lint] Add quick fix for prefer_const_constructors_in_immutables This commit introduces a quick fix for the `prefer_const_constructors_in_immutables` lint rule. The fix automatically renames incorrectly named catch clause parameters (`exception` and `stackTrace`) and updates all their usages within the catch block. The `README.md` has also been updated to document this new quick fix. --- packages/leancode_lint/README.md | 4 +++ .../lib/fixes/rename_catch_parameter.dart | 30 +++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/leancode_lint/README.md b/packages/leancode_lint/README.md index 02425345..1350f2a5 100644 --- a/packages/leancode_lint/README.md +++ b/packages/leancode_lint/README.md @@ -200,6 +200,10 @@ custom_lint: stack_trace: stackTrace # Optional ``` +#### Quick Fix + +This rule provides an automatic fix that renames the parameters and updates all references within the catch block scope. + ### `hook_widget_does_not_use_hooks` **AVOID** extending `HookWidget` if no hooks are used. diff --git a/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart b/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart index c6f43702..6f7d600d 100644 --- a/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart +++ b/packages/leancode_lint/lib/fixes/rename_catch_parameter.dart @@ -13,12 +13,12 @@ class RenameCatchParameter extends DartFix { @override Future run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - Diagnostic analysisError, - List others, - ) async { + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + Diagnostic analysisError, + List others, + ) async { // Get the resolved unit directly final result = await resolver.getResolvedUnitResult(); final unit = result.unit; @@ -56,11 +56,11 @@ class RenameCatchParameter extends DartFix { reporter .createChangeBuilder(message: 'Rename `$oldName` to `$newName`', priority: 80) .addDartFileEdit((builder) { - // Apply replacements in reverse order to maintain offsets - for (final range in occurrences.reversed) { - builder.addSimpleReplacement(range, newName); - } - }); + // Apply replacements in reverse order to maintain offsets + for (final range in occurrences.reversed) { + builder.addSimpleReplacement(range, newName); + } + }); } /// Identifies which parameter (exception or stack trace) is being flagged. @@ -86,10 +86,10 @@ class RenameCatchParameter extends DartFix { /// Collects all occurrences of the parameter that need to be renamed. List _collectOccurrences( - CatchClause catchClause, - CatchClauseParameter targetParam, - String oldName, - ) { + CatchClause catchClause, + CatchClauseParameter targetParam, + String oldName, + ) { final occurrences = []; final targetElement = targetParam.declaredFragment?.element;