Skip to content

Commit b69028f

Browse files
committed
feat(linter/plugins): implement SourceCode#ast property (#14287)
Add support for obtaining the AST via `context.sourceCode.ast`.
1 parent 95a8cc4 commit b69028f

File tree

4 files changed

+41
-8
lines changed

4 files changed

+41
-8
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,13 @@ function lintFileImpl(filePath: string, bufferId: number, buffer: Uint8Array | n
112112

113113
const sourceText = textDecoder.decode(buffer.subarray(0, sourceByteLen));
114114

115+
// Deserialize AST from buffer.
116+
// `preserveParens` argument is `false`, to match ESLint.
117+
// ESLint does not include `ParenthesizedExpression` nodes in its AST.
118+
const program = deserializeProgramOnly(buffer, sourceText, sourceByteLen, false);
119+
115120
const hasBOM = false; // TODO: Set this correctly
116-
setupSourceForFile(sourceText, hasBOM);
121+
setupSourceForFile(sourceText, hasBOM, program);
117122

118123
// Get visitors for this file from all rules
119124
initCompiledVisitor();
@@ -150,9 +155,6 @@ function lintFileImpl(filePath: string, bufferId: number, buffer: Uint8Array | n
150155
// Some rules seen in the wild return an empty visitor object from `create` if some initial check fails
151156
// e.g. file extension is not one the rule acts on.
152157
if (needsVisit) {
153-
// `preserveParens` argument is `false`, to match ESLint.
154-
// ESLint does not include `ParenthesizedExpression` nodes in its AST.
155-
const program = deserializeProgramOnly(buffer, sourceText, sourceByteLen, false);
156158
walkProgram(program, compiledVisitor);
157159

158160
// Lazy implementation

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,27 @@ const { max } = Math;
99
let sourceText: string | null = null;
1010
// Set before linting each file by `setupSourceForFile`.
1111
let hasBOM = false;
12+
// Set before linting each file by `setupSourceForFile`.
13+
let ast: Program | null = null;
1214

1315
/**
1416
* Set up source for the file about to be linted.
1517
* @param sourceTextInput - Source text
1618
* @param hasBOMInput - `true` if file's original source text has Unicode BOM
19+
* @param astInput - The AST program for the file
1720
*/
18-
export function setupSourceForFile(sourceTextInput: string, hasBOMInput: boolean): void {
21+
export function setupSourceForFile(sourceTextInput: string, hasBOMInput: boolean, astInput: Program): void {
1922
sourceText = sourceTextInput;
2023
hasBOM = hasBOMInput;
24+
ast = astInput;
2125
}
2226

2327
/**
2428
* Reset source after file has been linted, to free memory.
2529
*/
2630
export function resetSource(): void {
2731
sourceText = null;
32+
ast = null;
2833
}
2934

3035
// `SourceCode` object.
@@ -49,7 +54,7 @@ export const SOURCE_CODE = Object.freeze({
4954

5055
// Get AST of the file.
5156
get ast(): Program {
52-
throw new Error('`sourceCode.ast` not implemented yet'); // TODO
57+
return ast;
5358
},
5459

5560
// Get `ScopeManager` for the file.

apps/oxlint/test/fixtures/sourceCode/output.snap.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
x source-code-plugin(create): create:
77
| text: "let foo, bar;\n"
88
| getText(): "let foo, bar;\n"
9+
| ast: "foo"
910
,-[files/1.js:1:1]
1011
1 | let foo, bar;
1112
: ^
@@ -21,6 +22,7 @@
2122
x source-code-plugin(create-once): before:
2223
| text: "let foo, bar;\n"
2324
| getText(): "let foo, bar;\n"
25+
| ast: "foo"
2426
,-[files/1.js:1:1]
2527
1 | let foo, bar;
2628
: ^
@@ -83,6 +85,7 @@
8385
x source-code-plugin(create): create:
8486
| text: "let qux;\n"
8587
| getText(): "let qux;\n"
88+
| ast: "qux"
8689
,-[files/2.js:1:1]
8790
1 | let qux;
8891
: ^
@@ -98,6 +101,7 @@
98101
x source-code-plugin(create-once): before:
99102
| text: "let qux;\n"
100103
| getText(): "let qux;\n"
104+
| ast: "qux"
101105
,-[files/2.js:1:1]
102106
1 | let qux;
103107
: ^

apps/oxlint/test/fixtures/sourceCode/plugin.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1+
import assert from 'node:assert';
2+
3+
import type { Program } from '@oxc-project/types';
14
import type { Plugin, Rule } from '../../../dist/index.js';
25

36
const SPAN = { start: 0, end: 0 };
47

58
const createRule: Rule = {
69
create(context) {
10+
const { ast } = context.sourceCode;
11+
712
context.report({
813
message: 'create:\n' +
914
`text: ${JSON.stringify(context.sourceCode.text)}\n` +
10-
`getText(): ${JSON.stringify(context.sourceCode.getText())}`,
15+
`getText(): ${JSON.stringify(context.sourceCode.getText())}\n` +
16+
// @ts-ignore
17+
`ast: "${ast.body[0].declarations[0].id.name}"`,
1118
node: SPAN,
1219
});
1320

1421
return {
22+
Program(node) {
23+
assert(node === ast);
24+
},
1525
VariableDeclaration(node) {
1626
context.report({
1727
message: `var decl:\nsource: "${context.sourceCode.getText(node)}"`,
@@ -34,15 +44,24 @@ const createRule: Rule = {
3444

3545
const createOnceRule: Rule = {
3646
createOnce(context) {
47+
let ast: Program | null = null;
48+
3749
return {
3850
before() {
51+
ast = context.sourceCode.ast;
52+
3953
context.report({
4054
message: 'before:\n' +
4155
`text: ${JSON.stringify(context.sourceCode.text)}\n` +
42-
`getText(): ${JSON.stringify(context.sourceCode.getText())}`,
56+
`getText(): ${JSON.stringify(context.sourceCode.getText())}\n` +
57+
// @ts-ignore
58+
`ast: "${ast.body[0].declarations[0].id.name}"`,
4359
node: SPAN,
4460
});
4561
},
62+
Program(node) {
63+
assert(node === ast);
64+
},
4665
VariableDeclaration(node) {
4766
context.report({
4867
message: `var decl:\nsource: "${context.sourceCode.getText(node)}"`,
@@ -60,6 +79,9 @@ const createOnceRule: Rule = {
6079
});
6180
},
6281
after() {
82+
assert(context.sourceCode.ast === ast);
83+
ast = null;
84+
6385
context.report({
6486
message: 'after:\n' +
6587
`source: ${JSON.stringify(context.sourceCode.text)}`,

0 commit comments

Comments
 (0)