refactor: migrate named_parameters_ordering#298
Conversation
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements the named_parameters_ordering lint rule and its corresponding quick fix to enforce consistent ordering of named parameters. The feedback highlights a critical bug where omitting parameter types in custom configurations leads to sorting errors, and another bug where the quick fix deletes trailing comments. Additionally, the reviewer suggests reusing the configuration parser in the quick fix to prevent duplicate logic and removing an unreachable case in the parameter classification switch statement.
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.
There was a problem hiding this comment.
Code Review
This pull request introduces a new quick fix (NamedParametersOrderingFix) and refactors the rule and visitor (NamedParametersOrderingRule, NamedParametersOrderingVisitor) to enforce and automatically sort named parameters in Dart. It also adds comprehensive unit tests. The review feedback highlights three critical issues: first, unlisted parameter types in the sorting configuration can cause indexOf to return -1, leading to incorrect sorting in the quick fix; second, a similar issue in the visitor's ordering check results in incorrect lint reports; and third, extracting parameter blocks with comments can incorrectly group trailing comments from previous lines, potentially corrupting the code replacement.
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.
…comments and fix configuration parsing logic
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the named_parameters_ordering lint rule and its corresponding quick fix (NamedParametersOrderingFix) to enforce named parameter ordering conventions in Dart. The review feedback highlights several edge cases in the quick fix implementation, including potential issues with trailing comma handling, incorrect line prefix calculations for inline parameters, trailing comment attribution, and the potential loss of inline comments in single-line parameter lists. Additionally, it suggests deduplicating the parsed order configuration to prevent unexpected sorting behavior.
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.
… and ensure unique parameter types in config parser
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces the named_parameters_ordering lint rule and its associated quick fix, NamedParametersOrderingFix, which automatically sorts named parameters according to a specified or default order. Feedback on the implementation highlights two key improvements for the quick fix: implementing a stable sort to preserve the original relative order of parameters with the same type, and using a loop to correctly skip all trailing comments on the same line as a previous parameter.
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.
…re comment chain in named parameters fix
| final previousParameterType = parametersInfo.lastOrNull; | ||
|
|
||
| final isWrong = _isOrderingWrong(parameterType, previousParameterType); | ||
|
|
||
| if (isWrong && previousParameterType != null) { |
There was a problem hiding this comment.
| final previousParameterType = parametersInfo.lastOrNull; | |
| final isWrong = _isOrderingWrong(parameterType, previousParameterType); | |
| if (isWrong && previousParameterType != null) { | |
| final previousParameterType = parametersInfo.lastOrNull; | |
| parametersInfo.add(parameterType); | |
| if (previousParameterType == null) continue; | |
| final isWrong = _isOrderingWrong(parameterType, previousParameterType); | |
| if (!isWrong) continue; | |
| ... what was nested in the if previously |
also instead of parametersInfo, we can add a small extension
extension IterablePairwise<T> on Iterable<T> {
Iterable<(T, T)> pairwise() sync* {
for (var i = 0; i + 1 < length; i++) {
yield (elementAt(i), elementAt(i + 1));
}
}
}that way we can skip null check
final badParameters = namedParametersList
.pairwise()
.where((pair) => _isOrderingWrong(pair.$2, pair.$1);
for (final parameter in badParameters) {
_rule.reportAtNode...
}
|
|
||
| final sortedNamedParams = [...namedParams]; | ||
| sortedNamedParams.sort((a, b) { | ||
| final typeA = ParameterType.fromParameter(a); | ||
| final typeB = ParameterType.fromParameter(b); | ||
| final indexA = parametersOrder.indexOf(typeA); | ||
| final indexB = parametersOrder.indexOf(typeB); | ||
| return indexA.compareTo(indexB); | ||
| }); |
There was a problem hiding this comment.
| final sortedNamedParams = [...namedParams]; | |
| sortedNamedParams.sort((a, b) { | |
| final typeA = ParameterType.fromParameter(a); | |
| final typeB = ParameterType.fromParameter(b); | |
| final indexA = parametersOrder.indexOf(typeA); | |
| final indexB = parametersOrder.indexOf(typeB); | |
| return indexA.compareTo(indexB); | |
| }); | |
| final sortedNamedParams = namedParams.sortedBy( | |
| (e) => parametersOrder.indexOf(ParameterType.fromParameter(e)), | |
| ); |
| bool isChanged = false; | ||
| for (int i = 0; i < namedParams.length; i++) { | ||
| if (namedParams[i] != sortedNamedParams[i]) { | ||
| isChanged = true; | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
| bool isChanged = false; | |
| for (int i = 0; i < namedParams.length; i++) { | |
| if (namedParams[i] != sortedNamedParams[i]) { | |
| isChanged = true; | |
| break; | |
| } | |
| } | |
| final isChanged = namedParams | |
| .zipWith(sortedNamedParams) | |
| .any((pair) => pair.$1 != pair.$2); |
extension IterableZip<T> on Iterable<T> {
Iterable<(T, T)> zipWith(Iterable<T> other) sync* {
for (var i = 0; i < length && i < other.length; i++) {
yield (elementAt(i), other.elementAt(i));
}
}
}| if (!isMultiline && !hasComments) { | ||
| // Single-line: no leading comments, simple text replacement | ||
| final sortedTexts = sortedNamedParams | ||
| .map((p) => utils.getRangeText(p.sourceRange)) | ||
| .toList(); | ||
|
|
||
| final replacementText = sortedTexts.join(', '); | ||
|
|
||
| final targetRange = SourceRange( | ||
| namedParams.first.offset, | ||
| namedParams.last.end - namedParams.first.offset, | ||
| ); | ||
|
|
||
| await builder.addDartFileEdit(file, (builder) { | ||
| builder.addSimpleReplacement(targetRange, replacementText); | ||
| }); | ||
| return; | ||
| } |
There was a problem hiding this comment.
sourceStart and sourceEnd are used later in code too
| if (!isMultiline && !hasComments) { | |
| // Single-line: no leading comments, simple text replacement | |
| final sortedTexts = sortedNamedParams | |
| .map((p) => utils.getRangeText(p.sourceRange)) | |
| .toList(); | |
| final replacementText = sortedTexts.join(', '); | |
| final targetRange = SourceRange( | |
| namedParams.first.offset, | |
| namedParams.last.end - namedParams.first.offset, | |
| ); | |
| await builder.addDartFileEdit(file, (builder) { | |
| builder.addSimpleReplacement(targetRange, replacementText); | |
| }); | |
| return; | |
| } | |
| final sourceStart = namedParams.first.offset; | |
| final sourceEnd = namedParams.last.end; | |
| if (!isMultiline && !hasComments) { | |
| return builder.addDartFileEdit(file, (builder) { | |
| builder.addSimpleReplacement( | |
| SourceRange(sourceStart, sourceEnd - sourceStart), | |
| sortedNamedParams | |
| .map((p) => utils.getRangeText(p.sourceRange)) | |
| .toList() | |
| .join(', '), | |
| ); | |
| }); | |
| } |
| final buffer = StringBuffer(); | ||
| for (int i = 0; i < sortedBlocks.length; i++) { | ||
| buffer.write(sortedBlocks[i].text); | ||
|
|
||
| final isLast = i == sortedBlocks.length - 1; | ||
| if (!isLast || hasTrailingComma) { | ||
| buffer.write(','); | ||
| } | ||
| final trailingComment = sortedBlocks[i].trailingComment; | ||
| if (trailingComment != null) { | ||
| buffer.write(' $trailingComment'); | ||
| } | ||
| if (!isLast) { | ||
| buffer.write('\n'); | ||
| } | ||
| } |
There was a problem hiding this comment.
| final buffer = StringBuffer(); | |
| for (int i = 0; i < sortedBlocks.length; i++) { | |
| buffer.write(sortedBlocks[i].text); | |
| final isLast = i == sortedBlocks.length - 1; | |
| if (!isLast || hasTrailingComma) { | |
| buffer.write(','); | |
| } | |
| final trailingComment = sortedBlocks[i].trailingComment; | |
| if (trailingComment != null) { | |
| buffer.write(' $trailingComment'); | |
| } | |
| if (!isLast) { | |
| buffer.write('\n'); | |
| } | |
| } | |
| final replacement = sortedBlocks.expandIndexed((i, e) { | |
| final isLast = i == sortedBlocks.length - 1; | |
| final trailingComment = e.trailingComment; | |
| return [ | |
| e.text, | |
| if (!isLast || hasTrailingComma) ',', | |
| if (trailingComment != null) ' $trailingComment', | |
| if (!isLast) '\n', | |
| ]; | |
| }).join(); |
| // comment on the original last parameter's line. | ||
| var rangeEnd = namedParams.last.end; | ||
| if (hasTrailingComma) { | ||
| rangeEnd = namedParams.last.endToken.next!.end; |
There was a problem hiding this comment.
I'd probably extract final tokenAfterEnd = namedParams.last.endToken.next; cuz it's used twice, and make this
var rangeEnd = hasTrailingComma ? tokenAfterEnd!.end : sourceEnd;| parameterList.rightParenthesis.offset; | ||
| if (rangeEnd < upperBound) { | ||
| final afterLast = utils.getRangeText( | ||
| SourceRange(rangeEnd, upperBound - rangeEnd), |
There was a problem hiding this comment.
I see that we are constantly doing SourceRange(start, end - start),
why don't we extract a simple utility SourceRange _createRange(int start, int end) => SourceRange(start, end - start);
Same stuff:
String _getRangeText(int start, int end) => utils.getRangeText(_createRange(start, end));
Closes #253