Skip to content

Commit b1a9a03

Browse files
feat(linter/plugins): implement SourceCode#getAllComments (#14589)
- Towards #14564 ### Future PRs - look into `RawTransferData { program.comments, comments }` duplication. - oxlint's `interface Program` doesn't include the comments type. - lazy deserialization - remove `comment.parent` - UTF8 to UTF16 conversion on the JS side - correct start/end offsets - [ ] getCommentsBefore - [ ] getCommentsAfter - [ ] getCommentsInside - [ ] commentsExistBetween --------- Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
1 parent 9e54002 commit b1a9a03

File tree

9 files changed

+104
-9
lines changed

9 files changed

+104
-9
lines changed

apps/oxlint/src-js/generated/deserialize.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function deserializeProgram(pos) {
4848
body: null,
4949
sourceType: deserializeModuleKind(pos + 125),
5050
hashbang: null,
51+
comments: deserializeVecComment(pos + 24),
5152
start: 0,
5253
end,
5354
range: [0, end],

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,11 @@ export const SOURCE_CODE = Object.freeze({
159159
* @returns Array of `Comment`s in occurrence order.
160160
*/
161161
getAllComments(): Comment[] {
162-
throw new Error('`sourceCode.getAllComments` not implemented yet'); // TODO
162+
if (ast === null) initAst();
163+
// TODO: Deserializing strings is expensive, make this access lazy
164+
// @ts-expect-error types are generated from `Program` attributes
165+
// which are also twinned with ESTree generation so can't touch it
166+
return ast.comments;
163167
},
164168

165169
/**

apps/oxlint/test/e2e.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,8 @@ describe('oxlint CLI', () => {
216216
await fs.writeFile(fixtureFilePath, codeBefore);
217217
}
218218
});
219+
220+
it('SourceCode.getAllComments() should return all comments', async () => {
221+
await testFixture('getAllComments');
222+
});
219223
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"jsPlugins": ["./plugin.ts"],
3+
"rules": {
4+
"test-getAllComments/test-getAllComments": "error"
5+
}
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Line comment 1
2+
const x = 1; /* Block comment 1 */
3+
4+
/**
5+
* JSDoc comment
6+
*/
7+
export function foo() {
8+
// Line comment 2
9+
return x; // Line comment 3
10+
}
11+
12+
/* Block comment 2 */
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Exit code
2+
1
3+
4+
# stdout
5+
```
6+
x test-getAllComments(test-getAllComments): getAllComments() returned 6 comments:
7+
| [0] Line: " Line comment 1" at [0, 17]
8+
| [1] Block: " Block comment 1 " at [31, 52]
9+
| [2] Block: "*\n * JSDoc comment\n " at [54, 78]
10+
| [3] Line: " Line comment 2" at [105, 122]
11+
| [4] Line: " Line comment 3" at [135, 152]
12+
| [5] Block: " Block comment 2 " at [156, 177]
13+
,-[files/test.js:2:1]
14+
1 | // Line comment 1
15+
2 | ,-> const x = 1; /* Block comment 1 */
16+
3 | |
17+
4 | | /**
18+
5 | | * JSDoc comment
19+
6 | | */
20+
7 | | export function foo() {
21+
8 | | // Line comment 2
22+
9 | | return x; // Line comment 3
23+
10 | | }
24+
11 | |
25+
12 | `-> /* Block comment 2 */
26+
`----
27+
28+
Found 0 warnings and 1 error.
29+
Finished in Xms on 1 file using X threads.
30+
```
31+
32+
# stderr
33+
```
34+
WARNING: JS plugins are experimental and not subject to semver.
35+
Breaking changes are possible while JS plugins support is under development.
36+
```
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { definePlugin } from '../../../dist/index.js';
2+
import type { Rule } from '../../../dist/index.js';
3+
4+
const getAllCommentsRule: Rule = {
5+
create(context) {
6+
const comments = context.sourceCode.getAllComments();
7+
8+
context.report({
9+
message: `getAllComments() returned ${comments.length} comments:\n` +
10+
comments.map((c, i) => ` [${i}] ${c.type}: ${JSON.stringify(c.value)} at [${c.range[0]}, ${c.range[1]}]`).join(
11+
'\n',
12+
),
13+
node: context.sourceCode.ast,
14+
});
15+
16+
return {};
17+
},
18+
};
19+
20+
export default definePlugin({
21+
meta: {
22+
name: 'test-getAllComments',
23+
},
24+
rules: {
25+
'test-getAllComments': getAllCommentsRule,
26+
},
27+
});

crates/oxc_ast/src/serialize/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ impl Program<'_> {
129129
body: null,
130130
sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind),
131131
hashbang: null,
132+
...(COMMENTS && { comments: DESER[Vec<Comment>](POS_OFFSET.comments) }),
132133
start,
133134
end,
134135
...(RANGE && { range: [start, end] }),

tasks/ast_tools/src/generators/raw_transfer.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,21 @@ fn generate_deserializers(
209209
}
210210
}
211211

212-
// Create deserializers with various settings, by setting `IS_TS`, `RANGE`, `LOC`, `PARENT`
213-
// and `PRESERVE_PARENS` consts, and running through minifier to shake out irrelevant code
212+
// Create deserializers with various settings, by setting `IS_TS`, `RANGE`, `LOC`, `PARENT`,
213+
// `PRESERVE_PARENS`, and `COMMENTS` consts, and running through minifier to shake out
214+
// irrelevant code
214215
struct VariantGen {
215216
variant_paths: Vec<String>,
216217
}
217218

218-
impl VariantGenerator<5> for VariantGen {
219-
const FLAG_NAMES: [&str; 5] = ["IS_TS", "RANGE", "LOC", "PARENT", "PRESERVE_PARENS"];
219+
impl VariantGenerator<6> for VariantGen {
220+
const FLAG_NAMES: [&str; 6] =
221+
["IS_TS", "RANGE", "LOC", "PARENT", "PRESERVE_PARENS", "COMMENTS"];
220222

221-
fn variants(&mut self) -> Vec<[bool; 5]> {
223+
fn variants(&mut self) -> Vec<[bool; 6]> {
222224
let mut variants = Vec::with_capacity(9);
223225

226+
// Parser deserializers
224227
for is_ts in [false, true] {
225228
for range in [false, true] {
226229
for parent in [false, true] {
@@ -233,16 +236,17 @@ fn generate_deserializers(
233236

234237
variants.push([
235238
is_ts, range, /* loc */ false, parent,
236-
/* preserve_parens */ true,
239+
/* preserve_parens */ true, /* comments */ false,
237240
]);
238241
}
239242
}
240243
}
241244

245+
// Linter deserializer
242246
self.variant_paths.push(format!("{OXLINT_APP_PATH}/src-js/generated/deserialize.js"));
243247
variants.push([
244248
/* is_ts */ true, /* range */ true, /* loc */ true,
245-
/* parent */ true, /* preserve_parens */ false,
249+
/* parent */ true, /* preserve_parens */ false, /* comments */ true,
246250
]);
247251

248252
variants
@@ -251,7 +255,7 @@ fn generate_deserializers(
251255
fn pre_process_variant<'a>(
252256
&mut self,
253257
program: &mut Program<'a>,
254-
flags: [bool; 5],
258+
flags: [bool; 6],
255259
allocator: &'a Allocator,
256260
) {
257261
if flags[2] {

0 commit comments

Comments
 (0)