Skip to content

Commit d013119

Browse files
asashourCommit Queue
authored and
Commit Queue
committed
[analysis_server] RangeFactory enhancements
Fixes #50959 Change-Id: I582706c0aea2597fd04dacb496e2ef16f055102a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278741 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
1 parent 0c87126 commit d013119

File tree

6 files changed

+286
-54
lines changed

6 files changed

+286
-54
lines changed

pkg/analysis_server/lib/src/services/correction/dart/make_required_named_parameters_first.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
66
import 'package:analysis_server/src/services/correction/fix.dart';
7+
import 'package:analysis_server/src/utilities/extensions/range_factory.dart';
78
import 'package:analyzer/dart/ast/ast.dart';
89
import 'package:analyzer/source/source_range.dart';
910
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -57,19 +58,18 @@ class MakeRequiredNamedParametersFirst extends CorrectionProducer {
5758
var firstParameter = parameters[firstOptionalParameter!];
5859
var firstComments = firstParameter.beginToken.precedingComments;
5960
var offset = firstComments?.offset ?? firstParameter.offset;
61+
var lineInfo = resolvedResult.lineInfo;
6062
builder.addInsertion(offset, (builder) {
6163
for (var index in requiredParameterIndices) {
62-
var parameter = parameters[index];
63-
var firstToken =
64-
parameter.beginToken.precedingComments ?? parameter.beginToken;
65-
var lastToken = parameter.endToken;
66-
var text = utils.getRangeText(range.startEnd(firstToken, lastToken));
64+
var nodeRange = range.nodeWithComments(lineInfo, parameters[index]);
65+
var text = utils.getRangeText(nodeRange);
6766
builder.write('$text, ');
6867
}
6968
});
7069
SourceRange? lastRange;
7170
for (var index in requiredParameterIndices) {
72-
var sourceRange = range.nodeInList(parameters, parameters[index]);
71+
var sourceRange = range.nodeInListWithComments(
72+
lineInfo, parameters, parameters[index]);
7373
if (sourceRange.offset >= (lastRange?.end ?? 0)) {
7474
builder.addDeletion(sourceRange);
7575
}

pkg/analysis_server/lib/src/services/correction/dart/make_super_invocation_last.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
66
import 'package:analysis_server/src/services/correction/fix.dart';
7+
import 'package:analysis_server/src/utilities/extensions/range_factory.dart';
78
import 'package:analyzer/dart/ast/ast.dart';
89
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
910
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
@@ -21,12 +22,21 @@ class MakeSuperInvocationLast extends CorrectionProducer {
2122
if (parent is! ConstructorDeclaration) return;
2223

2324
var initializers = parent.initializers;
24-
var firstToken = node.beginToken.precedingComments ?? node.beginToken;
25-
var lastToken = node.endToken;
26-
var text = utils.getRangeText(range.startEnd(firstToken, lastToken));
25+
var lineInfo = resolvedResult.lineInfo;
26+
27+
var deletionRange =
28+
range.nodeInListWithComments(lineInfo, initializers, node);
29+
30+
var nodeRange = range.nodeWithComments(lineInfo, node);
31+
var text = utils.getRangeText(nodeRange);
32+
var insertionOffset = range
33+
.trailingComment(lineInfo, initializers.last.endToken,
34+
returnComma: false)
35+
.token
36+
.end;
2737
await builder.addDartFileEdit(file, (builder) {
28-
builder.addDeletion(range.nodeInList(initializers, node));
29-
builder.addSimpleInsertion(initializers.last.end, ', $text');
38+
builder.addDeletion(deletionRange);
39+
builder.addSimpleInsertion(insertionOffset, ', $text');
3040
});
3141
}
3242
}

pkg/analysis_server/lib/src/utilities/extensions/range_factory.dart

Lines changed: 118 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import 'package:analyzer/source/line_info.dart';
99
import 'package:analyzer/source/source_range.dart';
1010
import 'package:analyzer_plugin/utilities/range_factory.dart';
1111

12+
class TokenWithOptionalComma {
13+
final Token token;
14+
15+
/// `true` if a comma is previously included.
16+
final bool includesComma;
17+
18+
TokenWithOptionalComma(this.token, this.includesComma);
19+
}
20+
1221
extension RangeFactoryExtensions on RangeFactory {
1322
/// Return a source range that covers the given [node] in the containing
1423
/// [list]. This includes a leading or trailing comma, as appropriate, and any
@@ -17,6 +26,10 @@ extension RangeFactoryExtensions on RangeFactory {
1726
/// comments (on lines between the start of the node and the preceding comma).
1827
///
1928
/// Throws an `ArgumentError` if the [node] is not an element of the [list].
29+
///
30+
/// This method is useful for deleting a node in a list.
31+
///
32+
/// See [nodeWithComments].
2033
SourceRange nodeInListWithComments<T extends AstNode>(
2134
LineInfo lineInfo, NodeList<T> list, T node) {
2235
// TODO(brianwilkerson) Improve the name and signature of this method and
@@ -34,9 +47,10 @@ extension RangeFactoryExtensions on RangeFactory {
3447
}
3548
// If there's only one item in the list, then delete everything including
3649
// any leading or trailing comments, including any trailing comma.
37-
var leadingComment = _leadingComment(lineInfo, node.beginToken);
38-
var trailingComment = _trailingComment(lineInfo, node.endToken, true);
39-
return startEnd(leadingComment, trailingComment);
50+
var leading = _leadingComment(lineInfo, node.beginToken);
51+
var trailing =
52+
trailingComment(lineInfo, node.endToken, returnComma: true);
53+
return startEnd(leading, trailing.token);
4054
}
4155
final index = list.indexOf(node);
4256
if (index < 0) {
@@ -53,18 +67,20 @@ extension RangeFactoryExtensions on RangeFactory {
5367
// If this isn't the first item in the list, then delete everything from
5468
// the end of the previous item, after the comma and any trailing comment,
5569
// to the end of this item, also after the comma and any trailing comment.
56-
var previousTrailingComment =
57-
_trailingComment(lineInfo, list[index - 1].endToken, false);
58-
var previousHasTrailingComment = previousTrailingComment is CommentToken;
59-
var thisTrailingComment =
60-
_trailingComment(lineInfo, node.endToken, previousHasTrailingComment);
61-
if (!previousHasTrailingComment && thisTrailingComment is CommentToken) {
62-
// But if this item has a trailing comment and the previous didn't, then
70+
var previousTrailingComment = trailingComment(
71+
lineInfo, list[index - 1].endToken,
72+
returnComma: false);
73+
var thisTrailingComment = trailingComment(lineInfo, node.endToken,
74+
returnComma: previousTrailingComment.includesComma);
75+
var previousToken = previousTrailingComment.token;
76+
if (!previousTrailingComment.includesComma &&
77+
thisTrailingComment.includesComma) {
78+
// But if this item has comma and the previous didn't, then
6379
// we'd be deleting both commas, which would leave invalid code. We
6480
// can't leave the comment, so instead we leave the preceding comma.
65-
previousTrailingComment = previousTrailingComment.next!;
81+
previousToken = previousToken.next!;
6682
}
67-
return endEnd(previousTrailingComment, thisTrailingComment);
83+
return endEnd(previousToken, thisTrailingComment.token);
6884
}
6985
}
7086

@@ -101,19 +117,74 @@ extension RangeFactoryExtensions on RangeFactory {
101117
///
102118
/// The range ends at the end of the trailing comment token or the end of the
103119
/// node itself if there is not one.
120+
///
121+
/// See [nodeInListWithComments].
104122
SourceRange nodeWithComments(LineInfo lineInfo, AstNode node) {
123+
var beginToken = node.beginToken;
105124
// If the node is the first thing in the unit, leading comments are treated
106125
// as headers and should never be included in the range.
107-
final isFirstItem = node.beginToken == node.root.beginToken;
126+
final isFirstItem = beginToken == node.root.beginToken;
108127

109-
var thisLeadingComment = isFirstItem
110-
? node.beginToken
111-
: _leadingComment(lineInfo, node.beginToken);
112-
var thisTrailingComment = _trailingComment(lineInfo, node.endToken, false);
128+
var thisLeadingComment =
129+
isFirstItem ? beginToken : _leadingComment(lineInfo, beginToken);
130+
var thisTrailingComment =
131+
trailingComment(lineInfo, node.endToken, returnComma: false);
113132

114-
return startEnd(thisLeadingComment, thisTrailingComment);
133+
return startEnd(thisLeadingComment, thisTrailingComment.token);
115134
}
116135

136+
/// Return the trailing comment token following the [token] if it is on the
137+
/// same line as the [token], or return the [token] if there is no trailing
138+
/// comment or if the comment is on a different line than the [token].
139+
///
140+
/// If there is a trailing comment, the returned `includesComma` indicates
141+
/// that there is a `comma` between the token and the trailing comment.
142+
///
143+
/// If [returnComma] is `true` and there is a comma after the
144+
/// [token], then the comma will be returned when the [token] would have been.
145+
///
146+
TokenWithOptionalComma trailingComment(LineInfo lineInfo, Token token,
147+
{required bool returnComma}) {
148+
var lastToken = token;
149+
var nextToken = lastToken.next!;
150+
var includesComma = nextToken.type == TokenType.COMMA &&
151+
_shouldIncludeCommentsAfterComma(lineInfo, nextToken);
152+
// There are comments after the comma that follows token, which are probably
153+
// meant to apply to token, so we must actually proceed with nextToken
154+
// instead of token.
155+
if (includesComma) {
156+
lastToken = nextToken;
157+
nextToken = lastToken.next!;
158+
}
159+
Token? comment = nextToken.precedingComments;
160+
161+
// If there is no comment after the next comma, and the comma is on a
162+
// different line than the token.
163+
if (comment == null &&
164+
includesComma &&
165+
_areDifferentLines(lineInfo, token, lastToken)) {
166+
comment = lastToken.precedingComments;
167+
lastToken = token;
168+
}
169+
if (comment != null) {
170+
var tokenLine = _lineNumber(lineInfo, lastToken);
171+
if (_lineNumber(lineInfo, comment) == tokenLine) {
172+
var next = comment.next;
173+
while (next != null && _lineNumber(lineInfo, next) == tokenLine) {
174+
comment = next;
175+
next = next.next;
176+
}
177+
return TokenWithOptionalComma(comment!, includesComma);
178+
}
179+
}
180+
return TokenWithOptionalComma(returnComma ? lastToken : token, false);
181+
}
182+
183+
/// Return `true` if the line number of the given [token] is different than
184+
/// the line number of the [other].
185+
bool _areDifferentLines(LineInfo lineInfo, Token token, Token other) =>
186+
_lineNumber(lineInfo, token) != _lineNumber(lineInfo, other);
187+
117188
/// Return the left-most comment immediately before the [token] that is not on
118189
/// the same line as the first non-comment token before the [token]. Return
119190
/// the [token] if there is no such comment.
@@ -122,38 +193,42 @@ extension RangeFactoryExtensions on RangeFactory {
122193
if (previous == null || previous.isEof) {
123194
return token.precedingComments ?? token;
124195
}
196+
var tokenLine = lineInfo.getLocation(token.offset).lineNumber;
125197
var previousLine = lineInfo.getLocation(previous.offset).lineNumber;
126198
Token? comment = token.precedingComments;
127-
while (comment != null) {
128-
var commentLine = lineInfo.getLocation(comment.offset).lineNumber;
129-
if (commentLine != previousLine) {
130-
break;
199+
if (tokenLine != previousLine) {
200+
while (comment != null) {
201+
var commentLine = lineInfo.getLocation(comment.offset).lineNumber;
202+
if (commentLine != previousLine) {
203+
break;
204+
}
205+
comment = comment.next;
131206
}
132-
comment = comment.next;
133207
}
134208
return comment ?? token;
135209
}
136210

137-
/// Return the comment token immediately following the [token] if it is on the
138-
/// same line as the [token], or the [token] if there is no comment after the
139-
/// [token] or if the comment is on a different line than the [token]. If
140-
/// [returnComma] is `true` and there is a comma after the [token], then the
141-
/// comma will be returned when the [token] would have been.
142-
Token _trailingComment(LineInfo lineInfo, Token token, bool returnComma) {
143-
var lastToken = token;
144-
var nextToken = lastToken.next!;
145-
if (nextToken.type == TokenType.COMMA) {
146-
lastToken = nextToken;
147-
nextToken = lastToken.next!;
148-
}
149-
var tokenLine = lineInfo.getLocation(lastToken.offset).lineNumber;
150-
Token? comment = nextToken.precedingComments;
151-
if (comment != null &&
152-
lineInfo.getLocation(comment.offset).lineNumber == tokenLine) {
153-
// This doesn't account for the possibility of multiple trailing block
154-
// comments.
155-
return comment;
211+
/// Return the line number of the given [token].
212+
int _lineNumber(LineInfo lineInfo, Token token) =>
213+
lineInfo.getLocation(token.offset).lineNumber;
214+
215+
/// Return `true` if the comments preceding the token after the given [comma]
216+
/// should be included.
217+
///
218+
/// This happens when the comments precede a closing token (e.g. parenthesis),
219+
/// or if the token next to [comma] is on a different line.
220+
bool _shouldIncludeCommentsAfterComma(LineInfo lineInfo, Token comma) {
221+
var tokenAfterComma = comma.next!;
222+
var tokenTypeAfterComma = tokenAfterComma.type;
223+
// Include the comment if the token next to comma is a closing token
224+
// (e.g. closing parenthesis).
225+
if (tokenTypeAfterComma == TokenType.CLOSE_CURLY_BRACKET ||
226+
tokenTypeAfterComma == TokenType.CLOSE_PAREN ||
227+
tokenTypeAfterComma == TokenType.CLOSE_SQUARE_BRACKET) {
228+
return true;
156229
}
157-
return returnComma ? lastToken : token;
230+
// Include the comment if the token next to comma is not on the same
231+
// line as the comma.
232+
return _areDifferentLines(lineInfo, comma, tokenAfterComma);
158233
}
159234
}

pkg/analysis_server/test/src/services/correction/fix/make_required_named_parameters_first_test.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ void f({required int a, /* bb */ int? b}) {}
8888
''');
8989
}
9090

91+
Future<void> test_comments() async {
92+
await resolveTestCode('''
93+
void f({int? a /* a */, required int b /* b1 */ /* b2 */}) {}
94+
''');
95+
await assertHasFix('''
96+
void f({required int b /* b1 */ /* b2 */, int? a /* a */}) {}
97+
''');
98+
}
99+
91100
Future<void> test_single() async {
92101
await resolveTestCode('''
93102
void f({int? b, required int a}) {}

pkg/analysis_server/test/src/services/correction/fix/make_super_invocation_last_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,25 @@ class A {
9797
final int y;
9898
A() : x = 1, /* a1 */ /* a2 */ y = 1, /* s1 */ /* s2 */ super();
9999
}
100+
''');
101+
}
102+
103+
Future<void> test_comment_trailing() async {
104+
await resolveTestCode('''
105+
class A {
106+
final int x;
107+
final int y;
108+
A() :
109+
x = 1, /* s1 */ super() /* s2 */ /* s3 */, /* a1 */ y = 1 /* a2 */ /* a2 */;
110+
}
111+
''');
112+
await assertHasFix('''
113+
class A {
114+
final int x;
115+
final int y;
116+
A() :
117+
x = 1, /* a1 */ y = 1 /* a2 */ /* a2 */, /* s1 */ super() /* s2 */ /* s3 */;
118+
}
100119
''');
101120
}
102121
}

0 commit comments

Comments
 (0)