Skip to content

Commit ec816ba

Browse files
committed
refactor(linter/plugins): move comments-related code into separate file (#14753)
Follow-on after #14715. Pure refactor. Move code for `SourceCode`'s comment-related methods into a separate file. Code is completely unchanged - it's just moved.
1 parent 9700a56 commit ec816ba

File tree

2 files changed

+174
-157
lines changed

2 files changed

+174
-157
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { ast, initAst, sourceText } from './source_code.js';
2+
3+
import type { Comment, Node, NodeOrToken } from './types.ts';
4+
5+
// Regex that tests if a string is entirely whitespace.
6+
const WHITESPACE_ONLY_REGEXP = /^\s*$/;
7+
8+
/**
9+
* Retrieve an array containing all comments in the source code.
10+
* @returns Array of `Comment`s in occurrence order.
11+
*/
12+
export function getAllComments(): Comment[] {
13+
if (ast === null) initAst();
14+
// `comments` property is a getter. Comments are deserialized lazily.
15+
return ast.comments;
16+
}
17+
18+
/**
19+
* Get all comment tokens directly before the given node or token.
20+
* @param nodeOrToken - The AST node or token to check for adjacent comment tokens.
21+
* @returns Array of `Comment`s in occurrence order.
22+
*/
23+
export function getCommentsBefore(nodeOrToken: NodeOrToken): Comment[] {
24+
if (ast === null) initAst();
25+
26+
const { comments } = ast,
27+
commentsLength = comments.length;
28+
29+
let targetStart = nodeOrToken.range[0]; // start
30+
31+
let sliceStart = commentsLength;
32+
let sliceEnd = 0;
33+
34+
// Reverse iteration isn't ideal, but this entire implementation may need to be rewritten
35+
// with token-based APIs to match eslint.
36+
for (let i = commentsLength - 1; i >= 0; i--) {
37+
const comment = comments[i];
38+
const commentEnd = comment.end;
39+
40+
if (commentEnd < targetStart) {
41+
const gap = sourceText.slice(commentEnd, targetStart);
42+
if (WHITESPACE_ONLY_REGEXP.test(gap)) {
43+
// Nothing except whitespace between end of comment and start of `nodeOrToken`
44+
sliceStart = sliceEnd = i + 1;
45+
targetStart = comment.start;
46+
}
47+
break;
48+
}
49+
}
50+
51+
for (let i = sliceEnd - 1; i >= 0; i--) {
52+
const comment = comments[i];
53+
const gap = sourceText.slice(comment.end, targetStart);
54+
if (WHITESPACE_ONLY_REGEXP.test(gap)) {
55+
// Nothing except whitespace between end of comment and start of `nodeOrToken`
56+
sliceStart = i;
57+
targetStart = comment.start;
58+
} else {
59+
break;
60+
}
61+
}
62+
63+
return comments.slice(sliceStart, sliceEnd);
64+
}
65+
66+
/**
67+
* Get all comment tokens directly after the given node or token.
68+
* @param nodeOrToken - The AST node or token to check for adjacent comment tokens.
69+
* @returns Array of `Comment`s in occurrence order.
70+
*/
71+
export function getCommentsAfter(nodeOrToken: NodeOrToken): Comment[] {
72+
if (ast === null) initAst();
73+
74+
const { comments } = ast,
75+
commentsLength = comments.length;
76+
77+
let targetEnd = nodeOrToken.range[1]; // end
78+
79+
const commentsAfter: Comment[] = [];
80+
for (let i = 0; i < commentsLength; i++) {
81+
const comment = comments[i],
82+
commentStart = comment.start;
83+
84+
if (commentStart < targetEnd) {
85+
continue;
86+
}
87+
const gap = sourceText.slice(targetEnd, commentStart);
88+
if (WHITESPACE_ONLY_REGEXP.test(gap)) {
89+
// Nothing except whitespace between end of `nodeOrToken` and start of comment
90+
commentsAfter.push(comment);
91+
targetEnd = comment.end;
92+
} else {
93+
break;
94+
}
95+
}
96+
97+
return commentsAfter;
98+
}
99+
100+
/**
101+
* Get all comment tokens inside the given node.
102+
* @param node - The AST node to get the comments for.
103+
* @returns Array of `Comment`s in occurrence order.
104+
*/
105+
export function getCommentsInside(node: Node): Comment[] {
106+
if (ast === null) initAst();
107+
108+
const { comments } = ast,
109+
commentsLength = comments.length;
110+
111+
let sliceStart = commentsLength;
112+
let sliceEnd: number | undefined = undefined;
113+
114+
const { range } = node,
115+
rangeStart = range[0],
116+
rangeEnd = range[1];
117+
118+
// Linear search for first comment within `node`'s range.
119+
// TODO: Use binary search.
120+
for (let i = 0; i < commentsLength; i++) {
121+
const comment = comments[i];
122+
if (comment.start >= rangeStart) {
123+
sliceStart = i;
124+
break;
125+
}
126+
}
127+
128+
// Continued linear search for first comment outside `node`'s range.
129+
// Its index is used as `sliceEnd`, which is exclusive of the slice.
130+
for (let i = sliceStart; i < commentsLength; i++) {
131+
const comment = comments[i];
132+
if (comment.start > rangeEnd) {
133+
sliceEnd = i;
134+
break;
135+
}
136+
}
137+
138+
return comments.slice(sliceStart, sliceEnd);
139+
}
140+
141+
/**
142+
* Check whether any comments exist or not between the given 2 nodes.
143+
* @param nodeOrToken1 - The node to check.
144+
* @param nodeOrToken2 - The node to check.
145+
* @returns `true` if one or more comments exist.
146+
*/
147+
export function commentsExistBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: NodeOrToken): boolean {
148+
if (ast === null) initAst();
149+
150+
// Find the first comment after `nodeOrToken1` ends.
151+
// Check if it ends before `nodeOrToken2` starts.
152+
const { comments } = ast,
153+
commentsLength = comments.length;
154+
const betweenRangeStart = nodeOrToken1.range[1]; // end
155+
for (let i = 0; i < commentsLength; i++) {
156+
const comment = comments[i];
157+
if (comment.start >= betweenRangeStart) {
158+
return comment.end <= nodeOrToken2.range[0]; // start
159+
}
160+
}
161+
return false;
162+
}

apps/oxlint/src-js/plugins/source_code.ts

Lines changed: 12 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import { DATA_POINTER_POS_32, SOURCE_LEN_OFFSET } from '../generated/constants.j
66
import { deserializeProgramOnly, resetBuffer } from '../../dist/generated/deserialize.js';
77

88
import visitorKeys from '../generated/keys.js';
9+
import {
10+
commentsExistBetween,
11+
getAllComments,
12+
getCommentsAfter,
13+
getCommentsBefore,
14+
getCommentsInside,
15+
} from './comments.js';
916
import {
1017
getLineColumnFromOffset,
1118
getNodeLoc,
@@ -21,8 +28,6 @@ import type { BufferWithArrays, Comment, Node, NodeOrToken, Ranged, Token } from
2128

2229
const { max } = Math;
2330

24-
const WHITESPACE_ONLY_REGEXP = /^\s*$/;
25-
2631
// Text decoder, for decoding source text from buffer
2732
const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true });
2833

@@ -157,161 +162,11 @@ export const SOURCE_CODE = Object.freeze({
157162
return sourceText.slice(start, end);
158163
},
159164

160-
/**
161-
* Retrieve an array containing all comments in the source code.
162-
* @returns Array of `Comment`s in occurrence order.
163-
*/
164-
getAllComments(): Comment[] {
165-
if (ast === null) initAst();
166-
// `comments` property is a getter. Comments are deserialized lazily.
167-
return ast.comments;
168-
},
169-
170-
/**
171-
* Get all comment tokens directly before the given node or token.
172-
* @param nodeOrToken - The AST node or token to check for adjacent comment tokens.
173-
* @returns Array of `Comment`s in occurrence order.
174-
*/
175-
getCommentsBefore(nodeOrToken: NodeOrToken): Comment[] {
176-
if (ast === null) initAst();
177-
178-
const { comments } = ast,
179-
commentsLength = comments.length;
180-
181-
let targetStart = nodeOrToken.range[0]; // start
182-
183-
let sliceStart = commentsLength;
184-
let sliceEnd = 0;
185-
186-
// Reverse iteration isn't ideal, but this entire implementation may need to be rewritten
187-
// with token-based APIs to match eslint.
188-
for (let i = commentsLength - 1; i >= 0; i--) {
189-
const comment = comments[i];
190-
const commentEnd = comment.end;
191-
192-
if (commentEnd < targetStart) {
193-
const gap = sourceText.slice(commentEnd, targetStart);
194-
if (WHITESPACE_ONLY_REGEXP.test(gap)) {
195-
// Nothing except whitespace between end of comment and start of `nodeOrToken`
196-
sliceStart = sliceEnd = i + 1;
197-
targetStart = comment.start;
198-
}
199-
break;
200-
}
201-
}
202-
203-
for (let i = sliceEnd - 1; i >= 0; i--) {
204-
const comment = comments[i];
205-
const gap = sourceText.slice(comment.end, targetStart);
206-
if (WHITESPACE_ONLY_REGEXP.test(gap)) {
207-
// Nothing except whitespace between end of comment and start of `nodeOrToken`
208-
sliceStart = i;
209-
targetStart = comment.start;
210-
} else {
211-
break;
212-
}
213-
}
214-
215-
return comments.slice(sliceStart, sliceEnd);
216-
},
217-
218-
/**
219-
* Get all comment tokens directly after the given node or token.
220-
* @param nodeOrToken - The AST node or token to check for adjacent comment tokens.
221-
* @returns Array of `Comment`s in occurrence order.
222-
*/
223-
getCommentsAfter(nodeOrToken: NodeOrToken): Comment[] {
224-
if (ast === null) initAst();
225-
226-
const { comments } = ast,
227-
commentsLength = comments.length;
228-
229-
let targetEnd = nodeOrToken.range[1]; // end
230-
231-
const commentsAfter: Comment[] = [];
232-
for (let i = 0; i < commentsLength; i++) {
233-
const comment = comments[i],
234-
commentStart = comment.start;
235-
236-
if (commentStart < targetEnd) {
237-
continue;
238-
}
239-
const gap = sourceText.slice(targetEnd, commentStart);
240-
if (WHITESPACE_ONLY_REGEXP.test(gap)) {
241-
// Nothing except whitespace between end of `nodeOrToken` and start of comment
242-
commentsAfter.push(comment);
243-
targetEnd = comment.end;
244-
} else {
245-
break;
246-
}
247-
}
248-
249-
return commentsAfter;
250-
},
251-
252-
/**
253-
* Get all comment tokens inside the given node.
254-
* @param node - The AST node to get the comments for.
255-
* @returns Array of `Comment`s in occurrence order.
256-
*/
257-
getCommentsInside(node: Node): Comment[] {
258-
if (ast === null) initAst();
259-
260-
const { comments } = ast,
261-
commentsLength = comments.length;
262-
263-
let sliceStart = commentsLength;
264-
let sliceEnd: number | undefined = undefined;
265-
266-
const { range } = node,
267-
rangeStart = range[0],
268-
rangeEnd = range[1];
269-
270-
// Linear search for first comment within `node`'s range.
271-
// TODO: Use binary search.
272-
for (let i = 0; i < commentsLength; i++) {
273-
const comment = comments[i];
274-
if (comment.start >= rangeStart) {
275-
sliceStart = i;
276-
break;
277-
}
278-
}
279-
280-
// Continued linear search for first comment outside `node`'s range.
281-
// Its index is used as `sliceEnd`, which is exclusive of the slice.
282-
for (let i = sliceStart; i < commentsLength; i++) {
283-
const comment = comments[i];
284-
if (comment.start > rangeEnd) {
285-
sliceEnd = i;
286-
break;
287-
}
288-
}
289-
290-
return comments.slice(sliceStart, sliceEnd);
291-
},
292-
293-
/**
294-
* Check whether any comments exist or not between the given 2 nodes.
295-
* @param nodeOrToken1 - The node to check.
296-
* @param nodeOrToken2 - The node to check.
297-
* @returns `true` if one or more comments exist.
298-
*/
299-
commentsExistBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: NodeOrToken): boolean {
300-
if (ast === null) initAst();
301-
302-
// Find the first comment after `nodeOrToken1` ends.
303-
// Check if it ends before `nodeOrToken2` starts.
304-
const { comments } = ast,
305-
commentsLength = comments.length;
306-
const betweenRangeStart = nodeOrToken1.range[1]; // end
307-
for (let i = 0; i < commentsLength; i++) {
308-
const comment = comments[i];
309-
if (comment.start >= betweenRangeStart) {
310-
return comment.end <= nodeOrToken2.range[0]; // start
311-
}
312-
}
313-
return false;
314-
},
165+
getAllComments,
166+
getCommentsBefore,
167+
getCommentsAfter,
168+
getCommentsInside,
169+
commentsExistBetween,
315170

316171
/**
317172
* Determine if two nodes or tokens have at least one whitespace character between them.

0 commit comments

Comments
 (0)