Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/oxlint/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ execSync('pnpm tsdown', { stdio: 'inherit', cwd: oxlintDirPath });
console.log('Copying files from parser...');

const parserFilePaths = [
// Lazy implementation
/*
'src-js/raw-transfer/lazy-common.mjs',
'src-js/raw-transfer/node-array.mjs',
'generated/lazy/constructors.mjs',
'generated/lazy/types.mjs',
'generated/lazy/walk.mjs',
*/
'generated/deserialize/ts.mjs',
'generated/visit/types.mjs',
'generated/visit/visitor.d.ts',
'generated/visit/walk.mjs',
];

for (const parserFilePath of parserFilePaths) {
Expand Down
17 changes: 17 additions & 0 deletions apps/oxlint/src-js/plugins/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ import { registeredRules } from './load.js';
import { assertIs } from './utils.js';
import { addVisitorToCompiled, compiledVisitor, finalizeCompiledVisitor, initCompiledVisitor } from './visitor.js';

// Lazy implementation
/*
// @ts-expect-error we need to generate `.d.ts` file for this module.
import { TOKEN } from '../../dist/src-js/raw-transfer/lazy-common.mjs';
// @ts-expect-error we need to generate `.d.ts` file for this module.
import { walkProgram } from '../../dist/generated/lazy/walk.mjs';
*/

// @ts-expect-error we need to generate `.d.ts` file for this module
import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts.mjs';
// @ts-expect-error we need to generate `.d.ts` file for this module
import { walkProgram } from '../../dist/generated/visit/walk.mjs';

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

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

// `preserveParens` argument is `false`, to match ESLint.
// ESLint does not include `ParenthesizedExpression` nodes in its AST.
const program = deserializeProgramOnly(buffer, sourceText, sourceByteLen, false);
walkProgram(program, compiledVisitor);

// Lazy implementation
/*
const sourceIsAscii = sourceText.length === sourceByteLen;
const ast = {
buffer,
Expand All @@ -91,6 +107,7 @@ export function lintFile(filePath: string, bufferId: number, buffer: Uint8Array
};

walkProgram(programPos, ast, compiledVisitor);
*/
}

// Send diagnostics back to Rust
Expand Down
5 changes: 5 additions & 0 deletions apps/oxlint/src-js/plugins/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Lazy implementation
/*
// Visitor object returned by a `Rule`'s `create` function.
export interface Visitor {
[key: string]: VisitFn;
}
*/

export type { VisitorObject as Visitor } from '../../dist/generated/visit/visitor.d.ts';

// Visit function for a specific AST node type.
export type VisitFn = (node: Node) => void;
Expand Down
10 changes: 8 additions & 2 deletions apps/oxlint/src-js/plugins/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@
// for objects created by user code in visitors. If ephemeral user-created objects all fit in new space,
// it will avoid full GC runs, which should greatly improve performance.

// Lazy implementation
/*
// TODO(camc314): we need to generate `.d.ts` file for this module.
// @ts-expect-error
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } from '../../dist/generated/lazy/types.mjs';
*/

// TODO(camc314): we need to generate `.d.ts` file for this module.
// @ts-expect-error
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } from '../../dist/generated/visit/types.mjs';
import { assertIs } from './utils.js';

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

const visitFn = visitor[name];
const visitFn = (visitor as { [key: string]: VisitFn })[name];
if (typeof visitFn !== 'function') {
throw new TypeError(`'${name}' property of visitor object is not a function`);
}
Expand Down
52 changes: 51 additions & 1 deletion apps/oxlint/test/__snapshots__/e2e.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,56 @@ Found 1 warning and 6 errors.
Finished in Xms on 1 file using X threads."
`;

exports[`oxlint CLI > should receive ESTree-compatible AST 1`] = `
"
x estree-check(check): Visited nodes:
| * Program
| * VariableDeclaration: let
| * VariableDeclarator: (init: ObjectExpression)
| * Identifier: a
| * ObjectExpression
| * Identifier: x
| * Identifier: y
| * VariableDeclaration:exit: let
| * VariableDeclaration: const
| * VariableDeclarator: (init: BinaryExpression)
| * Identifier: b
| * BinaryExpression: * (right: BinaryExpression)
| * Identifier: x
| * BinaryExpression: + (right: Literal)
| * Literal: str
| * Literal: 123
| * VariableDeclaration:exit: const
| * TSTypeAliasDeclaration: (typeAnnotation: TSStringKeyword)
| * Identifier: T
| * TSStringKeyword
| * TSTypeAliasDeclaration:exit: (typeAnnotation: TSStringKeyword)
| * TSTypeAliasDeclaration: (typeAnnotation: TSUnionType)
| * Identifier: U
| * TSUnionType: (types: TSStringKeyword, TSNumberKeyword)
| * TSStringKeyword
| * TSNumberKeyword
| * TSUnionType:exit: (types: TSStringKeyword, TSNumberKeyword)
| * TSTypeAliasDeclaration:exit: (typeAnnotation: TSUnionType)
| * Program:exit
,-[index.ts:2:1]
1 | // All \`Identifier\`s
2 | ,-> let a = { x: y };
3 | |
4 | | // No \`ParenthesizedExpression\`s in AST
5 | | const b = (x * ((('str' + ((123))))));
6 | |
7 | | // TS syntax
8 | | type T = string;
9 | |
10 | | // No \`TSParenthesizedType\`s in AST
11 | \`-> type U = (((((string)) | ((number)))));
\`----

Found 0 warnings and 1 error.
Finished in Xms on 1 file using X threads."
`;

exports[`oxlint CLI > should receive data via \`context\` 1`] = `
"
x context-plugin(log-context): id: context-plugin/log-context
Expand Down Expand Up @@ -612,7 +662,7 @@ exports[`oxlint CLI > should work with multiple rules 1`] = `
\`----
help: Remove the debugger statement

x basic-custom-plugin(no-ident-references-named-foo): Unexpected Identifier Reference named foo
x basic-custom-plugin(no-identifiers-named-foo): Unexpected Identifier named foo
,-[index.js:3:1]
2 |
3 | foo;
Expand Down
7 changes: 7 additions & 0 deletions apps/oxlint/test/compile-visitor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Lazy implementation
/*
// TODO(camc314): we need to generate `.d.ts` file for this module.
// @ts-expect-error
import { NODE_TYPE_IDS_MAP } from '../dist/generated/lazy/types.mjs';
*/
// TODO(camc314): we need to generate `.d.ts` file for this module
// @ts-expect-error
import { NODE_TYPE_IDS_MAP } from '../dist/generated/visit/types.mjs';
import {
addVisitorToCompiled,
compiledVisitor,
Expand Down Expand Up @@ -30,6 +36,7 @@ describe('compile visitor', () => {
});

it('throws if unknown visitor key', () => {
// @ts-expect-error
expect(() => addVisitorToCompiled({ Foo() {} }))
.toThrow(new Error("Unknown node type 'Foo' in visitor object"));
});
Expand Down
6 changes: 6 additions & 0 deletions apps/oxlint/test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ describe('oxlint CLI', () => {
expect(normalizeOutput(stdout)).toMatchSnapshot();
});

it('should receive ESTree-compatible AST', async () => {
const { stdout, exitCode } = await runOxlint('test/fixtures/estree');
expect(exitCode).toBe(1);
expect(normalizeOutput(stdout)).toMatchSnapshot();
});

it('should receive data via `context`', async () => {
const { stdout, exitCode } = await runOxlint('test/fixtures/context_properties');
expect(exitCode).toBe(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"rules": {
"basic-custom-plugin/no-debugger": "error",
"basic-custom-plugin/no-debugger-2": "error",
"basic-custom-plugin/no-ident-references-named-foo": "error"
"basic-custom-plugin/no-identifiers-named-foo": "error"
},
"ignorePatterns": ["test_plugin/**"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,12 @@ export default {
};
},
},
"no-ident-references-named-foo": {
"no-identifiers-named-foo": {
create(context) {
return {
IdentifierReference(identifierReference) {
if (identifierReference.name == "foo") {
context.report({
message: "Unexpected Identifier Reference named foo",
node: identifierReference,
});
Identifier(ident) {
if (ident.name == "foo") {
context.report({ message: "Unexpected Identifier named foo", node: ident});
}
},
};
Expand Down
10 changes: 10 additions & 0 deletions apps/oxlint/test/fixtures/estree/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"plugins": ["./test_plugin"],
"categories": {
"correctness": "off"
},
"rules": {
"estree-check/check": "error"
},
"ignorePatterns": ["test_plugin/**"]
}
11 changes: 11 additions & 0 deletions apps/oxlint/test/fixtures/estree/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// All `Identifier`s
let a = { x: y };

// No `ParenthesizedExpression`s in AST
const b = (x * ((('str' + ((123))))));

// TS syntax
type T = string;

// No `TSParenthesizedType`s in AST
type U = (((((string)) | ((number)))));
80 changes: 80 additions & 0 deletions apps/oxlint/test/fixtures/estree/test_plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export default {
meta: {
name: "estree-check",
},
rules: {
check: {
create(context) {
// Note: Collect visits in an array instead of `context.report` in each visitor function,
// to ensure visitation happens in right order.
// Diagnostics may be output in different order from the order they're created in.
const visits = [];
return {
Program(program) {
visits.push(program.type);
},
VariableDeclaration(decl) {
visits.push(`${decl.type}: ${decl.kind}`);
},
'VariableDeclaration:exit'(decl) {
visits.push(`${decl.type}:exit: ${decl.kind}`);
},
VariableDeclarator(decl) {
// `init` should not be `ParenthesizedExpression`
visits.push(`${decl.type}: (init: ${decl.init.type})`);
},
Identifier(ident) {
visits.push(`${ident.type}: ${ident.name}`);
},
ObjectExpression(expr) {
visits.push(expr.type);
},
ParenthesizedExpression(paren) {
// Should not be visited - no `ParenthesizedExpression`s in AST in ESLint
visits.push(paren.type);
},
BinaryExpression(expr) {
// `right` should not be `ParenthesizedExpression`
visits.push(`${expr.type}: ${expr.operator} (right: ${expr.right.type})`);
},
Literal(lit) {
visits.push(`${lit.type}: ${lit.value}`);
},
TSTypeAliasDeclaration(decl) {
// `typeAnnotation` should not be `TSParenthesizedType`
visits.push(`${decl.type}: (typeAnnotation: ${decl.typeAnnotation.type})`);
},
'TSTypeAliasDeclaration:exit'(decl) {
// `typeAnnotation` should not be `TSParenthesizedType`
visits.push(`${decl.type}:exit: (typeAnnotation: ${decl.typeAnnotation.type})`);
},
TSStringKeyword(keyword) {
visits.push(keyword.type);
},
TSParenthesizedType(paren) {
// Should not be visited - no `TSParenthesizedType`s in AST in TS-ESLint
visits.push(paren.type);
},
TSUnionType(union) {
// `types` should not be `TSParenthesizedType`
visits.push(`${union.type}: (types: ${union.types.map(t => t.type).join(', ')})`);
},
'TSUnionType:exit'(union) {
// `types` should not be `TSParenthesizedType`
visits.push(`${union.type}:exit: (types: ${union.types.map(t => t.type).join(', ')})`);
},
TSNumberKeyword(keyword) {
visits.push(keyword.type);
},
'Program:exit'(program) {
visits.push(`${program.type}:exit`);
context.report({
message: `Visited nodes:\n* ${visits.join('\n* ')}`,
node: program,
});
},
};
},
},
},
};
3 changes: 2 additions & 1 deletion apps/oxlint/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"exclude": [
"node_modules",
"fixtures"
"fixtures",
"test/fixtures"
]
}
Loading