diff --git a/apps/oxlint/package.json b/apps/oxlint/package.json index 0d3df297fdd2f..35e2b941e50b4 100644 --- a/apps/oxlint/package.json +++ b/apps/oxlint/package.json @@ -24,7 +24,6 @@ }, "devDependencies": { "@arethetypeswrong/core": "catalog:", - "@eslint/plugin-kit": "^0.5.0", "@napi-rs/cli": "catalog:", "@types/esquery": "^1.5.4", "@types/estree": "^1.0.8", diff --git a/apps/oxlint/src-js/plugins/cfg.ts b/apps/oxlint/src-js/plugins/cfg.ts index 0a7f0b9a8d468..664926fb5a58a 100644 --- a/apps/oxlint/src-js/plugins/cfg.ts +++ b/apps/oxlint/src-js/plugins/cfg.ts @@ -9,7 +9,6 @@ import CodePathAnalyzer from "../../node_modules/eslint/lib/linter/code-path-ana // @ts-expect-error - internal module of ESLint with no types import Traverser from "../../node_modules/eslint/lib/shared/traverser.js"; -import { VisitNodeStep, CallMethodStep } from "@eslint/plugin-kit"; import visitorKeys from "../generated/keys.ts"; import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP } from "../generated/type_ids.ts"; import { ancestors } from "../generated/walk.js"; @@ -19,15 +18,41 @@ import type { EnterExit, VisitFn } from "./visitor.ts"; import type { Node, Program } from "../generated/types.d.ts"; import type { CompiledVisitors } from "../generated/walk.js"; +// Step type constants. +// Equivalent to an enum, but minifies better. +const STEP_TYPE_ENTER = 0; +const STEP_TYPE_EXIT = 1; +const STEP_TYPE_CALL = 2; + /** * Step to walk AST. */ -type Step = VisitNodeStep | CallMethodStep; +type Step = EnterStep | ExitStep | CallStep; -const STEP_KIND_VISIT = 1; +/** + * Step for entering AST node. + */ +interface EnterStep { + type: typeof STEP_TYPE_ENTER; + target: Node; +} -const STEP_PHASE_ENTER = 1; -const STEP_PHASE_EXIT = 2; +/** + * Step for exiting AST node. + */ +interface ExitStep { + type: typeof STEP_TYPE_EXIT; + target: Node; +} + +/** + * Step for calling a CFG event handler. + */ +interface CallStep { + type: typeof STEP_TYPE_CALL; + eventName: string; + args: unknown[]; +} // Array of steps to walk AST. // Singleton array which is re-used for each walk, and emptied after each walk. @@ -59,7 +84,8 @@ export function resetCfgWalk(): void { * 2. Visit AST with provided visitor. * Run through the steps, in order, calling visit functions for each step. * - * TODO: This is copied from ESLint and is not very efficient. We could improve its performance in many ways. + * TODO: This is was originally copied from ESLint, and has been adapted for better performance. + * But we could further improve its performance in many ways. * See TODO comments in the code below for some ideas for optimization. * * @param ast - AST @@ -75,44 +101,46 @@ export function walkProgramWithCfg(ast: Program, visitors: CompiledVisitors): vo for (let i = 0; i < stepsLen; i++) { const step = steps[i]; - if (step.kind === STEP_KIND_VISIT) { - const node = step.target as Node; - - if (step.phase === STEP_PHASE_ENTER) { - // Enter node - can be leaf or non-leaf node - const typeId = NODE_TYPE_IDS_MAP.get(node.type)!; - const visit = visitors[typeId]; - if (typeId < LEAF_NODE_TYPES_COUNT) { - // Leaf node - if (visit !== null) { - typeAssertIs(visit); - visit(node); - } - // Don't add node to `ancestors`, because we don't visit them on exit - } else { - // Non-leaf node - if (visit !== null) { - typeAssertIs(visit); - const { enter } = visit; - if (enter !== null) enter(node); - } - - ancestors.unshift(node); + const stepType = step.type; + + if (stepType === STEP_TYPE_ENTER) { + // Enter node - can be leaf or non-leaf node + const node = step.target; + const typeId = NODE_TYPE_IDS_MAP.get(node.type)!; + const visit = visitors[typeId]; + + if (typeId < LEAF_NODE_TYPES_COUNT) { + // Leaf node + if (visit !== null) { + typeAssertIs(visit); + visit(node); } + // Don't add node to `ancestors`, because we don't visit them on exit } else { - // Exit non-leaf node - ancestors.shift(); - - const typeId = NODE_TYPE_IDS_MAP.get(node.type)!; - const enterExit = visitors[typeId]; - if (enterExit !== null) { - typeAssertIs(enterExit); - const { exit } = enterExit; - if (exit !== null) exit(node); + // Non-leaf node + if (visit !== null) { + typeAssertIs(visit); + const { enter } = visit; + if (enter !== null) enter(node); } + + ancestors.unshift(node); + } + } else if (stepType === STEP_TYPE_EXIT) { + // Exit non-leaf node + const node = step.target; + ancestors.shift(); + + const typeId = NODE_TYPE_IDS_MAP.get(node.type)!; + const enterExit = visitors[typeId]; + if (enterExit !== null) { + typeAssertIs(enterExit); + const { exit } = enterExit; + if (exit !== null) exit(node); } } else { - const eventId = NODE_TYPE_IDS_MAP.get(step.target)!; + // Call method (CFG event) + const eventId = NODE_TYPE_IDS_MAP.get(step.eventName)!; const visit = visitors[eventId]; if (visit !== null) { (visit as any).apply(undefined, step.args); @@ -139,11 +167,8 @@ function prepareSteps(ast: Program) { // Create `CodePathAnalyzer`. // It stores steps to walk AST. // - // This is really inefficient code. - // We could improve it in several ways (in ascending order of complexity): + // We could improve performance in several ways (in ascending order of complexity): // - // * Get rid of the bloated `VisitNodeStep` and `CallMethodStep` classes. Just use plain objects. - // * Combine `step.kind` and `step.phase` into a single `step.type` property. // * Reduce object creation by storing steps as 2 arrays (struct of arrays pattern): // * Array 1: Step type (number). // * Array 2: Step data - AST node object for enter/exit node steps, args for CFG events. @@ -159,13 +184,10 @@ function prepareSteps(ast: Program) { // TODO: Apply these optimizations (or at least some of them). const analyzer = new CodePathAnalyzer({ enterNode(node: Node) { - steps.push( - new VisitNodeStep({ - target: node, - phase: STEP_PHASE_ENTER, - args: [node], - }), - ); + steps.push({ + type: STEP_TYPE_ENTER, + target: node, + }); if (DEBUG) stepsLenAfterEnter = steps.length; }, @@ -175,13 +197,10 @@ function prepareSteps(ast: Program) { if (typeId >= LEAF_NODE_TYPES_COUNT) { // Non-leaf node - steps.push( - new VisitNodeStep({ - target: node, - phase: STEP_PHASE_EXIT, - args: [node], - }), - ); + steps.push({ + type: STEP_TYPE_EXIT, + target: node, + }); } else { // Leaf node. // Don't add a step. @@ -194,7 +213,10 @@ function prepareSteps(ast: Program) { // visit functions are called in would be wrong. // `exit` visit fn would be called before the CFG event handlers, instead of after. if (DEBUG && steps.length !== stepsLenAfterEnter) { - const eventNames = steps.slice(stepsLenAfterEnter).map((step) => step.target) as string[]; + const eventNames = steps.slice(stepsLenAfterEnter).map((step) => { + if (step.type === STEP_TYPE_CALL) return step.eventName; + return `${step.type === STEP_TYPE_ENTER ? "enter" : "exit"} ${node.type}`; + }); throw new Error( `CFG events emitted during visiting leaf node \`${node.type}\`: ${eventNames.join(", ")}`, ); @@ -202,13 +224,12 @@ function prepareSteps(ast: Program) { } }, - emit(eventName: string, args: any[]) { - steps.push( - new CallMethodStep({ - target: eventName, - args, - }), - ); + emit(eventName: string, args: unknown[]) { + steps.push({ + type: STEP_TYPE_CALL, + eventName, + args, + }); }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42d3bca23e4d7..4df0825684929 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,9 +107,6 @@ importers: '@arethetypeswrong/core': specifier: 'catalog:' version: 0.18.2 - '@eslint/plugin-kit': - specifier: ^0.5.0 - version: 0.5.0 '@napi-rs/cli': specifier: 'catalog:' version: 3.5.1(@emnapi/runtime@1.7.1)(@types/node@24.1.0) @@ -1044,10 +1041,6 @@ packages: resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.0.0': - resolution: {integrity: sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.3': resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1064,10 +1057,6 @@ packages: resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.5.0': - resolution: {integrity: sha512-rSXBsAcmx80jI9OUevyNBU0f5pZRQJkNmk4bLX6hCbm1qKe5Z/TcU7vwXc2nR8814mhRlgbZIHL1+HSiYS0VkQ==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -6269,10 +6258,6 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@1.0.0': - dependencies: - '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 @@ -6296,11 +6281,6 @@ snapshots: '@eslint/core': 0.15.2 levn: 0.4.1 - '@eslint/plugin-kit@0.5.0': - dependencies: - '@eslint/core': 1.0.0 - levn: 0.4.1 - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7':