Skip to content
Merged
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
137 changes: 72 additions & 65 deletions apps/oxlint/src-js/plugins/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,16 @@ export function resetCompiledVisitor(): void {
activeNonLeafVisitorsCount = 0;
}

/**
* Merger that merges visit functions for an AST node type.
*/
type Merger = (...visitFns: VisitFn[]) => VisitFn;

/**
* Merger that merges handler functions for a CFG event.
*/
type CfgMerger = (...visitFns: CfgVisitFn[]) => CfgVisitFn;

// 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 | CfgVisitFn)[] = [];
Expand Down Expand Up @@ -599,7 +609,68 @@ function mergeVisitFns(visitProps: VisitProp[]): VisitFn {
return mergedFn;
}

type Merger = (...visitFns: VisitFn[]) => VisitFn;
/**
* Merge array of CFG visit functions into a single function, which calls each of input functions in turn.
*
* The difference between this function and `mergeVisitFns` is that this function is for CFG event handlers.
* Unlike all other visit functions, CFG event handlers are called with more than 1 argument.
* We keep this separate from `mergeVisitFns` because the merger functions use `...args` to pass all arguments,
* which is likely less performant than the simpler version which passes a single argument.
* AST node visitation is a lot more common than CFG event visitation, and we want to keep the common case fast.
*
* The array passed is cleared (length set to 0), so the array can be reused.
*
* The merged function is statically defined and does not contain a loop, to hopefully allow
* JS engine to heavily optimize it.
*
* `cfgMergers` contains pre-defined functions to merge up to 5 CFG visit functions.
* Merger functions for merging more than 5 visit functions are created dynamically on demand.
*
* @param visitProps - Array of `CfgVisitProp` objects
* @returns Function which calls all CFG visit functions in turn
*/
function mergeCfgVisitFns(visitProps: CfgVisitProp[]): CfgVisitFn {
const numVisitFns = visitProps.length;

debugAssert(numVisitFns > 0, "`visitProps` should have at least 1 element");

let mergedFn: CfgVisitFn;
if (numVisitFns === 1) {
// Only 1 visit function, so no need to merge
debugAssertIsNonNull(visitProps[0].fn);
mergedFn = visitProps[0].fn;
} else {
// No need to sort in order of specificity, because each rule can only have 1 handler for each CFG event

// Get or create merger for merging `numVisitFns` functions
while (cfgMergers.length <= numVisitFns) {
cfgMergers.push(null);
}

let merger = cfgMergers[numVisitFns];
if (merger === null) merger = cfgMergers[numVisitFns] = createMerger(numVisitFns, true);

// Merge functions.
// Reuse a temporary array to avoid creating a new array for each merge.
// TODO: Make merger functions take an array of `VisitProp`s to avoid this operation?
typeAssertIs<CfgVisitFn[]>(visitFns);
debugAssert(visitFns.length === 0, "`visitFns` should be empty");

for (let i = 0; i < numVisitFns; i++) {
debugAssertIsNonNull(visitProps[i].fn);
visitFns.push(visitProps[i].fn!);
}

mergedFn = merger(...visitFns);

visitFns.length = 0;
}

// Empty `visitProps` array, so it can be reused
visitProps.length = 0;

return mergedFn;
}

/**
* Create a merger function that merges `fnCount` functions.
Expand Down Expand Up @@ -674,70 +745,6 @@ const mergers: (Merger | null)[] = [
},
];

/**
* Merge array of CFG visit functions into a single function, which calls each of input functions in turn.
*
* The difference between this function and `mergeVisitFns` is that this function is for CFG event handlers.
* Unlike all other visit functions, CFG event handlers are called with more than 1 argument.
* We keep this separate from `mergeVisitFns` because the merger functions use `...args` to pass all arguments,
* which is likely less performant than the simpler version which passes a single argument.
* AST node visitation is a lot more common than CFG event visitation, and we want to keep the common case fast.
*
* The array passed is cleared (length set to 0), so the array can be reused.
*
* The merged function is statically defined and does not contain a loop, to hopefully allow
* JS engine to heavily optimize it.
*
* `cfgMergers` contains pre-defined functions to merge up to 5 CFG visit functions.
* Merger functions for merging more than 5 visit functions are created dynamically on demand.
*
* @param visitProps - Array of `VisitProp` objects
* @returns Function which calls all CFG visit functions in turn
*/
function mergeCfgVisitFns(visitProps: CfgVisitProp[]): CfgVisitFn {
const numVisitFns = visitProps.length;

debugAssert(numVisitFns > 0, "`visitProps` should have at least 1 element");

let mergedFn: CfgVisitFn;
if (numVisitFns === 1) {
// Only 1 visit function, so no need to merge
debugAssertIsNonNull(visitProps[0].fn);
mergedFn = visitProps[0].fn;
} else {
// No need to sort in order of specificity, because each rule can only have 1 handler for each CFG event

// Get or create merger for merging `numVisitFns` functions
while (cfgMergers.length <= numVisitFns) {
cfgMergers.push(null);
}

let merger = cfgMergers[numVisitFns];
if (merger === null) merger = cfgMergers[numVisitFns] = createMerger(numVisitFns, true);

// Merge functions.
// Reuse a temporary array to avoid creating a new array for each merge.
// TODO: Make merger functions take an array of `VisitProp`s to avoid this operation?
typeAssertIs<CfgVisitFn[]>(visitFns);
debugAssert(visitFns.length === 0, "`visitFns` should be empty");

for (let i = 0; i < numVisitFns; i++) {
debugAssertIsNonNull(visitProps[i].fn);
visitFns.push(visitProps[i].fn!);
}
mergedFn = merger(...visitFns);

visitFns.length = 0;
}

// Empty `visitProps` array, so it can be reused
visitProps.length = 0;

return mergedFn;
}

type CfgMerger = (...visitFns: CfgVisitFn[]) => CfgVisitFn;

// Pre-defined CFG mergers for merging up to 5 functions
const cfgMergers: (CfgMerger | null)[] = [
null, // No merger for 0 functions
Expand Down
Loading