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
10 changes: 6 additions & 4 deletions apps/oxlint/src-js/plugins/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
addVisitorToCompiled,
compiledVisitor,
finalizeCompiledVisitor,
initCompiledVisitor,
resetCompiledVisitor,
VISITOR_EMPTY,
VISITOR_CFG,
} from "./visitor.ts";
Expand Down Expand Up @@ -185,8 +185,6 @@ export function lintFileImpl(
setGlobalsForFile(globalsJSON);

// Get visitors for this file from all rules
initCompiledVisitor();

for (let i = 0, len = ruleIds.length; i < len; i++) {
const ruleId = ruleIds[i];
debugAssert(ruleId < registeredRules.length, "Rule ID out of bounds");
Expand Down Expand Up @@ -245,6 +243,9 @@ export function lintFileImpl(
}

debugAssert(ancestors.length === 0, "`ancestors` should be empty after walking AST");

// Reset compiled visitor, ready for next file
resetCompiledVisitor();
}

// Run any `after` hooks
Expand Down Expand Up @@ -317,8 +318,9 @@ export function resetStateAfterError() {
// so no leftovers bleed into next file.
// We could have a separate function to reset state which could be simpler and faster, but `resetStateAfterError`
// should never be called - only happens when rules return an invalid visitor or malfunction.
// So better to use the existing function, rather than bloat the package with more code which should never run.
// So better to use the existing functions, rather than bloat the package with more code which should never run.
finalizeCompiledVisitor();
resetCompiledVisitor();

diagnostics.length = 0;
ancestors.length = 0;
Expand Down
46 changes: 25 additions & 21 deletions apps/oxlint/src-js/plugins/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@
// No more than 1 compiled visitor exists at any time, so we reuse a single array `compiledVisitor`,
// rather than creating a new array for each file being linted.
//
// To compile visitors, call:
// * `initCompiledVisitor` once.
// * `addVisitorToCompiled` with each visitor object.
// * `finalizeCompiledVisitor` once.
// To compile a visitor and use it:
//
// After this sequence of calls, `compiledVisitor` is ready to be used to walk the AST.
// 1. Call `addVisitorToCompiled` with each visitor object.
// 2. Call `finalizeCompiledVisitor` once.
// 3. Walk AST with compiled visitor.
// 4. Call `resetCompiledVisitor` once.
//
// We also recycle:
//
Expand Down Expand Up @@ -230,22 +230,6 @@ let enterExitObjectCacheNextIndex = 0;
const visitPropsCache: VisitProp[] = [];
let visitPropsCacheNextIndex = 0;

/**
* Initialize compiled visitor, ready for calls to `addVisitor`.
*/
export function initCompiledVisitor(): void {
// Reset `compiledVisitor` array after previous compilation
compiledVisitor.fill(null);

// Reset enter+exit objects which were used in previous compilation
for (let i = 0; i < enterExitObjectCacheNextIndex; i++) {
const enterExit = enterExitObjectCache[i];
enterExit.enter = null;
enterExit.exit = null;
}
enterExitObjectCacheNextIndex = 0;
}

/**
* Add a visitor to compiled visitor.
*
Expand Down Expand Up @@ -492,6 +476,26 @@ export function finalizeCompiledVisitor(): VisitorState {
return visitState;
}

/**
* Reset compiled visitor.
*
* This frees visit functions stored in `compiledVisitor`, and makes them eligible for garbage collection.
*
* After calling this function, `compiledVisitor` is in a clean state, ready for next file's visitor to be compiled.
*/
export function resetCompiledVisitor(): void {
// Reset `compiledVisitor` array
compiledVisitor.fill(null);

// Reset enter+exit objects
for (let i = 0; i < enterExitObjectCacheNextIndex; i++) {
const enterExit = enterExitObjectCache[i];
enterExit.enter = null;
enterExit.exit = null;
}
enterExitObjectCacheNextIndex = 0;
}

// Array used by `mergeVisitFns` and `mergeCfgVisitFns` to store visit functions extracted from an array of `VisitProp`s.
// This array is used ephemerally, so we re-use same array for each merge.
const visitFns: VisitFn[] = [];
Expand Down
10 changes: 6 additions & 4 deletions apps/oxlint/test/compile_visitor.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// oxlint-disable jest/no-conditional-expect

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
NODE_TYPES_COUNT,
LEAF_NODE_TYPES_COUNT,
Expand All @@ -10,7 +10,7 @@ import {
addVisitorToCompiled,
compiledVisitor,
finalizeCompiledVisitor,
initCompiledVisitor,
resetCompiledVisitor,
VISITOR_EMPTY,
VISITOR_NOT_EMPTY,
} from "../src-js/plugins/visitor.ts";
Expand Down Expand Up @@ -40,8 +40,10 @@ const SPAN: Node = {
};

describe("compile visitor", () => {
beforeEach(initCompiledVisitor);
afterEach(finalizeCompiledVisitor);
afterEach(() => {
finalizeCompiledVisitor();
resetCompiledVisitor();
});

it("throws if visitor is not an object", () => {
const expectedErr = new TypeError("Visitor returned from `create` method must be an object");
Expand Down
Loading