diff --git a/apps/oxlint/src-js/plugins/visitor.ts b/apps/oxlint/src-js/plugins/visitor.ts index 6d4ce59e92689..aad35f07570de 100644 --- a/apps/oxlint/src-js/plugins/visitor.ts +++ b/apps/oxlint/src-js/plugins/visitor.ts @@ -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)[] = []; @@ -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(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. @@ -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(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