refactor: migrate avoid_using_api#304
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the avoid_using_api lint rule to utilize the analyzer-based RuleContext and RuleVisitorRegistry instead of custom_lint APIs. It replaces AvoidUsingApiLinter with AvoidUsingApiVisitor (extending SimpleAstVisitor), introduces several AST node helper extensions in node_utils.dart, refactors path matching in path_utils.dart, and replaces old integration tests with a comprehensive unit test suite. The review feedback suggests making the rootPath parameter in _getActiveEntries nullable to align with shouldSkipFile and passing context.package?.root.path directly instead of coalescing it to an empty string.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
I found a bug related to the use of severity in this rule, and I'm working on a fix. |
… multiple lint codes
…o 249-migrate-avoid_using_api
| if (actualClassName != className) { | ||
| return; | ||
| } | ||
|
|
||
| if (node.constructorName.name?.name != expectedConstructorName) { | ||
| return; | ||
| } | ||
|
|
||
| if (!node.argumentList.containsNamed(namedParameter)) { | ||
| return; | ||
| } | ||
|
|
||
| if (matchesSource( | ||
| node.constructorName.type.element?.libraryUri, | ||
| source, | ||
| )) { |
There was a problem hiding this comment.
| if (actualClassName != className) { | |
| return; | |
| } | |
| if (node.constructorName.name?.name != expectedConstructorName) { | |
| return; | |
| } | |
| if (!node.argumentList.containsNamed(namedParameter)) { | |
| return; | |
| } | |
| if (matchesSource( | |
| node.constructorName.type.element?.libraryUri, | |
| source, | |
| )) { | |
| if (actualClassName == className && | |
| node.constructorName.name?.name == expectedConstructorName && | |
| node.argumentList.containsNamed(namedParameter) && | |
| matchesSource( | |
| node.constructorName.type.element?.libraryUri, | |
| source, | |
| )) { |
There was a problem hiding this comment.
There are probably more similar refactors possible in this file that I missed
There was a problem hiding this comment.
Thank you! I implemented similar improvements in other parts of this class as well.
…ingApiVisitor to improve readability and structure
| } | ||
| } | ||
|
|
||
| bool _isMemberOrClass(Element? element, String className, String source) { |
There was a problem hiding this comment.
seems like it can be extracted to util?
| return; | ||
| } | ||
|
|
||
| reporter.atNode(node, _getLintCode(entry)); |
There was a problem hiding this comment.
This is repeated 8 times, why don't we wrap reporter into a function when we return it from _resolveContext so it looks more like:
| reporter.atNode(node, _getLintCode(entry)); | |
| report(node); |
…ied traversal method and moving member checks to extensions
solid-danylosafonov
left a comment
There was a problem hiding this comment.
Also, it kinda starts looking like we could extract _checkX methods to be polymorphic extensions on node types so e.g. instead of _checkIdFromSource(node, entry), we'd have node.checkIdFromSource(entry). But I also think that checkX might not be the best name here
| final resolved = _resolveContext(); | ||
| if (resolved == null) return; | ||
| final (:activeEntries, :reporter) = resolved; |
There was a problem hiding this comment.
Probably can inline it now
| final resolved = _resolveContext(); | |
| if (resolved == null) return; | |
| final (:activeEntries, :reporter) = resolved; | |
| final currentUnit = context.currentUnit; | |
| if (currentUnit == null) return; | |
| final activeEntries = _cachedActiveEntries ??= _getActiveEntries( | |
| currentUnit.file.path, | |
| context.package?.root.path, | |
| ); | |
| final reporter = currentUnit.diagnosticReporter; |
| visitEntry(node, entry, (target) { | ||
| if (target is AstNode) { | ||
| reporter.atNode(target, _getLintCode(entry)); | ||
| } else if (target is Token) { | ||
| reporter.atToken(target, _getLintCode(entry)); | ||
| } | ||
| }); |
There was a problem hiding this comment.
I feel like we can inline _getLintCode here
| visitEntry(node, entry, (target) { | |
| if (target is AstNode) { | |
| reporter.atNode(target, _getLintCode(entry)); | |
| } else if (target is Token) { | |
| reporter.atToken(target, _getLintCode(entry)); | |
| } | |
| }); | |
| visitEntry(node, entry, (target) { | |
| final severity = | |
| entry.severity ?? parameters.severity ?? DiagnosticSeverity.INFO; | |
| final code = LintCode( | |
| AvoidUsingApiRule.lintName, | |
| entry.reason ?? AvoidUsingApiRule.defaultMessage, | |
| severity: severity, | |
| uniqueName: AvoidUsingApiRule.getUniqueName(severity), | |
| ); | |
| visitEntry( | |
| node, | |
| entry, | |
| (target) => switch (target) { | |
| AstNode() => reporter.atNode(target, code), | |
| Token() => reporter.atToken(target, code), | |
| _ => (), | |
| }, | |
| ); | |
| }); |
| void Function( | ||
| T, | ||
| AvoidUsingApiEntryParameters, | ||
| void Function(SyntacticEntity), |
There was a problem hiding this comment.
We actually can avoid passing it in alltogether
| void Function(SyntacticEntity), |
void _visit<T>(
T node,
void Function(T) visitSuper,
SyntacticEntity? Function(
T,
AvoidUsingApiEntryParameters,
)
visitEntry,
) {
visitSuper(node);
final currentUnit = context.currentUnit;
if (currentUnit == null) return;
final activeEntries = _cachedActiveEntries ??= _getActiveEntries(
currentUnit.file.path,
context.package?.root.path,
);
final reporter = currentUnit.diagnosticReporter;
for (final entry in activeEntries) {
final severity =
entry.severity ?? parameters.severity ?? DiagnosticSeverity.INFO;
final code = LintCode(
AvoidUsingApiRule.lintName,
entry.reason ?? AvoidUsingApiRule.defaultMessage,
severity: severity,
uniqueName: AvoidUsingApiRule.getUniqueName(severity),
);
final target = visitEntry(node, entry);
(() => switch (target) {
AstNode() => reporter.atNode(target, code),
Token() => reporter.atToken(target, code),
_ => (),
})();
}
}| final source = entry.source; | ||
| if (source == null) return; | ||
|
|
||
| final className = entry.className; | ||
| final identifier = entry.identifier; | ||
| final namedParameter = entry.namedParameter; |
There was a problem hiding this comment.
| final source = entry.source; | |
| if (source == null) return; | |
| final className = entry.className; | |
| final identifier = entry.identifier; | |
| final namedParameter = entry.namedParameter; | |
| final AvoidUsingApiEntryParameters( | |
| :className, | |
| :identifier, | |
| :namedParameter, | |
| :source, | |
| ) = entry; | |
| if (source == null) return null; |
| final identifier = entry.identifier; | ||
| final namedParameter = entry.namedParameter; | ||
|
|
||
| switch ((className, identifier, namedParameter)) { |
There was a problem hiding this comment.
Looks like we can short circuit here
| switch ((className, identifier, namedParameter)) { | |
| if (className == null) return; | |
| switch ((identifier, namedParameter)) { |
…meters access in node extensions
b38c6d9
into
solid-software:analysis_server_migration
Closes #249