@@ -9,6 +9,15 @@ import 'package:analyzer/source/line_info.dart';
9
9
import 'package:analyzer/source/source_range.dart' ;
10
10
import 'package:analyzer_plugin/utilities/range_factory.dart' ;
11
11
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
+
12
21
extension RangeFactoryExtensions on RangeFactory {
13
22
/// Return a source range that covers the given [node] in the containing
14
23
/// [list] . This includes a leading or trailing comma, as appropriate, and any
@@ -17,6 +26,10 @@ extension RangeFactoryExtensions on RangeFactory {
17
26
/// comments (on lines between the start of the node and the preceding comma).
18
27
///
19
28
/// 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] .
20
33
SourceRange nodeInListWithComments <T extends AstNode >(
21
34
LineInfo lineInfo, NodeList <T > list, T node) {
22
35
// TODO(brianwilkerson) Improve the name and signature of this method and
@@ -34,9 +47,10 @@ extension RangeFactoryExtensions on RangeFactory {
34
47
}
35
48
// If there's only one item in the list, then delete everything including
36
49
// 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);
40
54
}
41
55
final index = list.indexOf (node);
42
56
if (index < 0 ) {
@@ -53,18 +67,20 @@ extension RangeFactoryExtensions on RangeFactory {
53
67
// If this isn't the first item in the list, then delete everything from
54
68
// the end of the previous item, after the comma and any trailing comment,
55
69
// 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
63
79
// we'd be deleting both commas, which would leave invalid code. We
64
80
// can't leave the comment, so instead we leave the preceding comma.
65
- previousTrailingComment = previousTrailingComment .next! ;
81
+ previousToken = previousToken .next! ;
66
82
}
67
- return endEnd (previousTrailingComment , thisTrailingComment);
83
+ return endEnd (previousToken , thisTrailingComment.token );
68
84
}
69
85
}
70
86
@@ -101,19 +117,74 @@ extension RangeFactoryExtensions on RangeFactory {
101
117
///
102
118
/// The range ends at the end of the trailing comment token or the end of the
103
119
/// node itself if there is not one.
120
+ ///
121
+ /// See [nodeInListWithComments] .
104
122
SourceRange nodeWithComments (LineInfo lineInfo, AstNode node) {
123
+ var beginToken = node.beginToken;
105
124
// If the node is the first thing in the unit, leading comments are treated
106
125
// 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;
108
127
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 );
113
132
114
- return startEnd (thisLeadingComment, thisTrailingComment);
133
+ return startEnd (thisLeadingComment, thisTrailingComment.token );
115
134
}
116
135
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
+
117
188
/// Return the left-most comment immediately before the [token] that is not on
118
189
/// the same line as the first non-comment token before the [token] . Return
119
190
/// the [token] if there is no such comment.
@@ -122,38 +193,42 @@ extension RangeFactoryExtensions on RangeFactory {
122
193
if (previous == null || previous.isEof) {
123
194
return token.precedingComments ?? token;
124
195
}
196
+ var tokenLine = lineInfo.getLocation (token.offset).lineNumber;
125
197
var previousLine = lineInfo.getLocation (previous.offset).lineNumber;
126
198
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;
131
206
}
132
- comment = comment.next;
133
207
}
134
208
return comment ?? token;
135
209
}
136
210
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 ;
156
229
}
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);
158
233
}
159
234
}
0 commit comments