Skip to content

Commit 16f54cc

Browse files
committed
feat(linter/plugins): ESTree-compatible AST for JS plugins
1 parent 840fbb1 commit 16f54cc

File tree

13 files changed

+209
-12
lines changed

13 files changed

+209
-12
lines changed

apps/oxlint/scripts/build.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@ execSync('pnpm tsdown', { stdio: 'inherit', cwd: oxlintDirPath });
2424
console.log('Copying files from parser...');
2525

2626
const parserFilePaths = [
27+
// Lazy implementation
28+
/*
2729
'src-js/raw-transfer/lazy-common.mjs',
2830
'src-js/raw-transfer/node-array.mjs',
2931
'generated/lazy/constructors.mjs',
3032
'generated/lazy/types.mjs',
3133
'generated/lazy/walk.mjs',
34+
*/
35+
'generated/deserialize/ts.mjs',
36+
'generated/visit/types.mjs',
37+
'generated/visit/visitor.d.ts',
38+
'generated/visit/walk.mjs',
3239
];
3340

3441
for (const parserFilePath of parserFilePaths) {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@ import { registeredRules } from './load.js';
99
import { assertIs } from './utils.js';
1010
import { addVisitorToCompiled, compiledVisitor, finalizeCompiledVisitor, initCompiledVisitor } from './visitor.js';
1111

12+
// Lazy implementation
13+
/*
1214
// @ts-expect-error we need to generate `.d.ts` file for this module.
1315
import { TOKEN } from '../../dist/src-js/raw-transfer/lazy-common.mjs';
1416
// @ts-expect-error we need to generate `.d.ts` file for this module.
1517
import { walkProgram } from '../../dist/generated/lazy/walk.mjs';
18+
*/
19+
20+
// @ts-expect-error we need to generate `.d.ts` file for this module
21+
import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts.mjs';
22+
// @ts-expect-error we need to generate `.d.ts` file for this module
23+
import { walkProgram } from '../../dist/generated/visit/walk.mjs';
1624

1725
// Buffer with typed array views of itself stored as properties
1826
interface BufferWithArrays extends Uint8Array {
@@ -80,6 +88,14 @@ export function lintFile(filePath: string, bufferId: number, buffer: Uint8Array
8088
sourceByteLen = uint32[(programPos + SOURCE_LEN_OFFSET) >> 2];
8189

8290
const sourceText = textDecoder.decode(buffer.subarray(0, sourceByteLen));
91+
92+
// `preserveParens` argument is `false`, to match ESLint.
93+
// ESLint does not include `ParenthesizedExpression` nodes in its AST.
94+
const program = deserializeProgramOnly(buffer, sourceText, sourceByteLen, false);
95+
walkProgram(program, compiledVisitor);
96+
97+
// Lazy implementation
98+
/*
8399
const sourceIsAscii = sourceText.length === sourceByteLen;
84100
const ast = {
85101
buffer,
@@ -91,6 +107,7 @@ export function lintFile(filePath: string, bufferId: number, buffer: Uint8Array
91107
};
92108
93109
walkProgram(programPos, ast, compiledVisitor);
110+
*/
94111
}
95112

96113
// Send diagnostics back to Rust

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
// Lazy implementation
2+
/*
13
// Visitor object returned by a `Rule`'s `create` function.
24
export interface Visitor {
35
[key: string]: VisitFn;
46
}
7+
*/
8+
9+
export type { VisitorObject as Visitor } from '../../dist/generated/visit/visitor.d.ts';
510

611
// Visit function for a specific AST node type.
712
export type VisitFn = (node: Node) => void;

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,15 @@
7272
// for objects created by user code in visitors. If ephemeral user-created objects all fit in new space,
7373
// it will avoid full GC runs, which should greatly improve performance.
7474

75+
// Lazy implementation
76+
/*
7577
// TODO(camc314): we need to generate `.d.ts` file for this module.
76-
// @ts-expect-error
7778
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } from '../../dist/generated/lazy/types.mjs';
79+
*/
80+
81+
// TODO(camc314): we need to generate `.d.ts` file for this module.
82+
// @ts-expect-error
83+
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } from '../../dist/generated/visit/types.mjs';
7884
import { assertIs } from './utils.js';
7985

8086
import type { CompiledVisitorEntry, EnterExit, Node, VisitFn, Visitor } from './types.ts';
@@ -214,7 +220,7 @@ export function addVisitorToCompiled(visitor: Visitor): void {
214220
for (let i = 0; i < keysLen; i++) {
215221
let name = keys[i];
216222

217-
const visitFn = visitor[name];
223+
const visitFn = (visitor as { [key: string]: VisitFn })[name];
218224
if (typeof visitFn !== 'function') {
219225
throw new TypeError(`'${name}' property of visitor object is not a function`);
220226
}

apps/oxlint/test/__snapshots__/e2e.test.ts.snap

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,56 @@ Found 1 warning and 6 errors.
444444
Finished in Xms on 1 file using X threads."
445445
`;
446446

447+
exports[`oxlint CLI > should receive ESTree-compatible AST 1`] = `
448+
"
449+
x estree-check(check): Visited nodes:
450+
| * Program
451+
| * VariableDeclaration: let
452+
| * VariableDeclarator: (init: ObjectExpression)
453+
| * Identifier: a
454+
| * ObjectExpression
455+
| * Identifier: x
456+
| * Identifier: y
457+
| * VariableDeclaration:exit: let
458+
| * VariableDeclaration: const
459+
| * VariableDeclarator: (init: BinaryExpression)
460+
| * Identifier: b
461+
| * BinaryExpression: * (right: BinaryExpression)
462+
| * Identifier: x
463+
| * BinaryExpression: + (right: Literal)
464+
| * Literal: str
465+
| * Literal: 123
466+
| * VariableDeclaration:exit: const
467+
| * TSTypeAliasDeclaration: (typeAnnotation: TSStringKeyword)
468+
| * Identifier: T
469+
| * TSStringKeyword
470+
| * TSTypeAliasDeclaration:exit: (typeAnnotation: TSStringKeyword)
471+
| * TSTypeAliasDeclaration: (typeAnnotation: TSUnionType)
472+
| * Identifier: U
473+
| * TSUnionType: (types: TSStringKeyword, TSNumberKeyword)
474+
| * TSStringKeyword
475+
| * TSNumberKeyword
476+
| * TSUnionType:exit: (types: TSStringKeyword, TSNumberKeyword)
477+
| * TSTypeAliasDeclaration:exit: (typeAnnotation: TSUnionType)
478+
| * Program:exit
479+
,-[index.ts:2:1]
480+
1 | // All \`Identifier\`s
481+
2 | ,-> let a = { x: y };
482+
3 | |
483+
4 | | // No \`ParenthesizedExpression\`s in AST
484+
5 | | const b = (x * ((('str' + ((123))))));
485+
6 | |
486+
7 | | // TS syntax
487+
8 | | type T = string;
488+
9 | |
489+
10 | | // No \`TSParenthesizedType\`s in AST
490+
11 | \`-> type U = (((((string)) | ((number)))));
491+
\`----
492+
493+
Found 0 warnings and 1 error.
494+
Finished in Xms on 1 file using X threads."
495+
`;
496+
447497
exports[`oxlint CLI > should receive data via \`context\` 1`] = `
448498
"
449499
x context-plugin(log-context): id: context-plugin/log-context
@@ -612,7 +662,7 @@ exports[`oxlint CLI > should work with multiple rules 1`] = `
612662
\`----
613663
help: Remove the debugger statement
614664
615-
x basic-custom-plugin(no-ident-references-named-foo): Unexpected Identifier Reference named foo
665+
x basic-custom-plugin(no-identifiers-named-foo): Unexpected Identifier named foo
616666
,-[index.js:3:1]
617667
2 |
618668
3 | foo;

apps/oxlint/test/compile-visitor.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
// Lazy implementation
3+
/*
24
// TODO(camc314): we need to generate `.d.ts` file for this module.
35
// @ts-expect-error
46
import { NODE_TYPE_IDS_MAP } from '../dist/generated/lazy/types.mjs';
7+
*/
8+
// TODO(camc314): we need to generate `.d.ts` file for this module
9+
// @ts-expect-error
10+
import { NODE_TYPE_IDS_MAP } from '../dist/generated/visit/types.mjs';
511
import {
612
addVisitorToCompiled,
713
compiledVisitor,
@@ -30,6 +36,7 @@ describe('compile visitor', () => {
3036
});
3137

3238
it('throws if unknown visitor key', () => {
39+
// @ts-expect-error
3340
expect(() => addVisitorToCompiled({ Foo() {} }))
3441
.toThrow(new Error("Unknown node type 'Foo' in visitor object"));
3542
});

apps/oxlint/test/e2e.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ describe('oxlint CLI', () => {
136136
expect(normalizeOutput(stdout)).toMatchSnapshot();
137137
});
138138

139+
it('should receive ESTree-compatible AST', async () => {
140+
const { stdout, exitCode } = await runOxlint('test/fixtures/estree');
141+
expect(exitCode).toBe(1);
142+
expect(normalizeOutput(stdout)).toMatchSnapshot();
143+
});
144+
139145
it('should receive data via `context`', async () => {
140146
const { stdout, exitCode } = await runOxlint('test/fixtures/context_properties');
141147
expect(exitCode).toBe(1);

apps/oxlint/test/fixtures/basic_custom_plugin_multiple_rules/.oxlintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"rules": {
44
"basic-custom-plugin/no-debugger": "error",
55
"basic-custom-plugin/no-debugger-2": "error",
6-
"basic-custom-plugin/no-ident-references-named-foo": "error"
6+
"basic-custom-plugin/no-identifiers-named-foo": "error"
77
},
88
"ignorePatterns": ["test_plugin/**"]
99
}

apps/oxlint/test/fixtures/basic_custom_plugin_multiple_rules/test_plugin/index.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@ export default {
2727
};
2828
},
2929
},
30-
"no-ident-references-named-foo": {
30+
"no-identifiers-named-foo": {
3131
create(context) {
3232
return {
33-
IdentifierReference(identifierReference) {
34-
if (identifierReference.name == "foo") {
35-
context.report({
36-
message: "Unexpected Identifier Reference named foo",
37-
node: identifierReference,
38-
});
33+
Identifier(ident) {
34+
if (ident.name == "foo") {
35+
context.report({ message: "Unexpected Identifier named foo", node: ident});
3936
}
4037
},
4138
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": ["./test_plugin"],
3+
"categories": {
4+
"correctness": "off"
5+
},
6+
"rules": {
7+
"estree-check/check": "error"
8+
},
9+
"ignorePatterns": ["test_plugin/**"]
10+
}

0 commit comments

Comments
 (0)