@wyw-in-js/transform 2.0.0-alpha.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/transform/Entrypoint.types.js.map +1 -1
- package/esm/transform/generators/collect.js +1 -0
- package/esm/transform/generators/collect.js.map +1 -1
- package/esm/transform/generators/resolveStaticOxcValues/prune.js +91 -5
- package/esm/transform/generators/resolveStaticOxcValues/prune.js.map +1 -1
- package/esm/transform/generators/resolveStaticOxcValues/resolveStaticOxcPreevalValues.js +2 -1
- package/esm/transform/generators/resolveStaticOxcValues/resolveStaticOxcPreevalValues.js.map +1 -1
- package/esm/transform/generators/transform.js +1 -0
- package/esm/transform/generators/transform.js.map +1 -1
- package/esm/utils/EventEmitter.js +13 -0
- package/esm/utils/EventEmitter.js.map +1 -1
- package/esm/utils/applyOxcProcessors/applyOxcProcessors.js +1 -1
- package/esm/utils/applyOxcProcessors/applyOxcProcessors.js.map +1 -1
- package/esm/utils/applyOxcProcessors/cleanupRemovals.js +102 -16
- package/esm/utils/applyOxcProcessors/cleanupRemovals.js.map +1 -1
- package/esm/utils/collectOxcExportsAndImports.js +8 -0
- package/esm/utils/collectOxcExportsAndImports.js.map +1 -1
- package/esm/utils/collectOxcRuntime/types.js.map +1 -1
- package/esm/utils/collectOxcTemplateDependencies/expressionExtraction.js +6 -4
- package/esm/utils/collectOxcTemplateDependencies/expressionExtraction.js.map +1 -1
- package/esm/utils/oxc/ast.js +42 -12
- package/esm/utils/oxc/ast.js.map +1 -1
- package/esm/utils/oxcPreevalTransforms.js +94 -65
- package/esm/utils/oxcPreevalTransforms.js.map +1 -1
- package/esm/utils/parseOxc.js +6 -1
- package/esm/utils/parseOxc.js.map +1 -1
- package/package.json +5 -5
- package/types/transform/Entrypoint.types.d.ts +1 -0
- package/types/transform/generators/collect.js +1 -0
- package/types/transform/generators/resolveStaticOxcValues/prune.d.ts +1 -1
- package/types/transform/generators/resolveStaticOxcValues/prune.js +96 -4
- package/types/transform/generators/resolveStaticOxcValues/resolveStaticOxcPreevalValues.js +2 -1
- package/types/transform/generators/transform.js +1 -0
- package/types/utils/EventEmitter.js +13 -0
- package/types/utils/applyOxcProcessors/applyOxcProcessors.d.ts +1 -0
- package/types/utils/applyOxcProcessors/applyOxcProcessors.js +3 -1
- package/types/utils/applyOxcProcessors/cleanupRemovals.d.ts +2 -2
- package/types/utils/applyOxcProcessors/cleanupRemovals.js +104 -19
- package/types/utils/collectOxcExportsAndImports.js +8 -0
- package/types/utils/collectOxcRuntime/types.d.ts +1 -0
- package/types/utils/collectOxcTemplateDependencies/expressionExtraction.js +6 -4
- package/types/utils/oxc/ast.js +42 -18
- package/types/utils/oxcPreevalTransforms.js +97 -74
- package/types/utils/parseOxc.js +6 -1
|
@@ -18,6 +18,13 @@ export class EventEmitter {
|
|
|
18
18
|
this.enabled = enabled;
|
|
19
19
|
}
|
|
20
20
|
action(actonType, idx, entrypointRef, fn) {
|
|
21
|
+
// Fast path: when the emitter is disabled (the dummy used by the
|
|
22
|
+
// production transform path) skip start/finish bookkeeping and the
|
|
23
|
+
// Promise hookup. action() is on the hot path for every step of every
|
|
24
|
+
// action — its per-call overhead is small but ubiquitous.
|
|
25
|
+
if (!this.enabled) {
|
|
26
|
+
return fn();
|
|
27
|
+
}
|
|
21
28
|
const id = this.onAction('start', performance.now(), actonType, idx, entrypointRef);
|
|
22
29
|
try {
|
|
23
30
|
const result = fn();
|
|
@@ -38,6 +45,12 @@ export class EventEmitter {
|
|
|
38
45
|
this.onEntrypointEvent(sequenceId, performance.now(), event);
|
|
39
46
|
}
|
|
40
47
|
perf(method, fn) {
|
|
48
|
+
// Fast path: see action() above. perf() wraps ~28s of cumulative time
|
|
49
|
+
// in the production-path CPU profile; the wrapper overhead per call is
|
|
50
|
+
// small but everywhere.
|
|
51
|
+
if (!this.enabled) {
|
|
52
|
+
return fn();
|
|
53
|
+
}
|
|
41
54
|
const spanId = this.perfSpanId;
|
|
42
55
|
this.perfSpanId += 1;
|
|
43
56
|
const startedAt = performance.now();
|
|
@@ -4,5 +4,6 @@ import { EventEmitter } from '../EventEmitter';
|
|
|
4
4
|
import type { ApplyOxcProcessorsResult } from './types';
|
|
5
5
|
export declare const applyOxcProcessors: (code: string, fileContext: IFileContext, options: Pick<StrictOptions, "classNameSlug" | "displayName" | "eval" | "extensions" | "staticBindings" | "tagResolver"> & {
|
|
6
6
|
eventEmitter?: EventEmitter;
|
|
7
|
+
preserveSideEffectImportOrderLocals?: Set<string>;
|
|
7
8
|
preserveSideEffectImportLocals?: Set<string>;
|
|
8
9
|
}, callback: (processor: BaseProcessor) => void, cleanupUnused?: boolean) => ApplyOxcProcessorsResult;
|
|
@@ -157,7 +157,9 @@ export const applyOxcProcessors = (code, fileContext, options, callback, cleanup
|
|
|
157
157
|
const codeWithAddedImports = insertAddedImports(replacedCode, program, addedImports);
|
|
158
158
|
return {
|
|
159
159
|
code: cleanupUnused
|
|
160
|
-
? eventEmitter.perf('transform:preeval:processTemplate:cleanup', () => removeUnusedAfterReplacement(codeWithAddedImports, filename, removableImportLocals, new Set([...removableExpressionRefs, ...extracted.dependencyNames]), options.preserveSideEffectImportLocals ?? new Set()
|
|
160
|
+
? eventEmitter.perf('transform:preeval:processTemplate:cleanup', () => removeUnusedAfterReplacement(codeWithAddedImports, filename, removableImportLocals, new Set([...removableExpressionRefs, ...extracted.dependencyNames]), options.preserveSideEffectImportLocals ?? new Set(), options.preserveSideEffectImportOrderLocals ??
|
|
161
|
+
options.preserveSideEffectImportLocals ??
|
|
162
|
+
new Set()))
|
|
161
163
|
: codeWithAddedImports,
|
|
162
164
|
processorClassNamesByLocal,
|
|
163
165
|
processors,
|
|
@@ -8,9 +8,9 @@ export declare function expandImportRemovalRange(code: string, start: number, en
|
|
|
8
8
|
export declare const collectUnusedScopedDeclarationRemovals: (code: string, bindings: Map<string, ScopedBindingInfo>, initialRemovableNames: Set<string>) => Replacement[];
|
|
9
9
|
export declare const expandImportSpecifierRemovalRange: (code: string, start: number, end: number) => Replacement;
|
|
10
10
|
export declare const mergeEmptyRemovalRanges: (removals: Replacement[]) => Replacement[];
|
|
11
|
-
export declare const collectUnusedImportRemovals: (code: string, program: Program, referencedNames: Set<string>, removableNames: Set<string>, preserveSideEffectImportLocals: Set<string>) => Replacement[];
|
|
11
|
+
export declare const collectUnusedImportRemovals: (code: string, program: Program, referencedNames: Set<string>, removableNames: Set<string>, preserveSideEffectImportLocals: Set<string>, preserveSideEffectImportOrderLocals?: Set<string>) => Replacement[];
|
|
12
12
|
export declare const collectUnusedTopLevelDeclarationRemovals: (code: string, program: Program, referencedNames: Set<string>, removableNames: Set<string>) => Replacement[];
|
|
13
13
|
export declare const collectUnusedGeneratedHelperDeclarationRemovals: (code: string, program: Program, referencedNames: Set<string>) => Replacement[];
|
|
14
14
|
export declare const collectTopLevelExpressionStatementRemovals: (code: string, statements: TopLevelStatementInfo[], topLevelBindings: Set<string>, removableExpressionRefs: Set<string>) => Replacement[];
|
|
15
15
|
export declare const collectEmptyTopLevelBlockRemovals: (code: string, program: Program) => Replacement[];
|
|
16
|
-
export declare const removeUnusedAfterReplacement: (code: string, filename: string, initialRemovableNames: Set<string>, removableExpressionRefs: Set<string>, preserveSideEffectImportLocals: Set<string>) => string;
|
|
16
|
+
export declare const removeUnusedAfterReplacement: (code: string, filename: string, initialRemovableNames: Set<string>, removableExpressionRefs: Set<string>, preserveSideEffectImportLocals: Set<string>, preserveSideEffectImportOrderLocals?: Set<string>) => string;
|
|
@@ -335,28 +335,42 @@ export const mergeEmptyRemovalRanges = (removals) => {
|
|
|
335
335
|
});
|
|
336
336
|
return merged;
|
|
337
337
|
};
|
|
338
|
-
export const collectUnusedImportRemovals = (code, program, referencedNames, removableNames, preserveSideEffectImportLocals) => {
|
|
338
|
+
export const collectUnusedImportRemovals = (code, program, referencedNames, removableNames, preserveSideEffectImportLocals, preserveSideEffectImportOrderLocals = preserveSideEffectImportLocals) => {
|
|
339
339
|
const removals = [];
|
|
340
|
+
const importSourceByLocal = new Map();
|
|
341
|
+
const removedSideEffectImportRanges = [];
|
|
342
|
+
const keptImportRangesBySource = new Map();
|
|
340
343
|
program.body.forEach((statement) => {
|
|
341
344
|
if (statement.type !== 'ImportDeclaration') {
|
|
342
345
|
return;
|
|
343
346
|
}
|
|
344
347
|
const localNames = collectImportLocalNames(statement);
|
|
348
|
+
const source = code.slice(statement.source.start, statement.source.end);
|
|
349
|
+
const orderedLocalNames = localNames.filter((localName) => preserveSideEffectImportOrderLocals.has(localName));
|
|
350
|
+
const sideEffectLocalNames = localNames.filter((localName) => preserveSideEffectImportLocals.has(localName));
|
|
351
|
+
[...orderedLocalNames, ...sideEffectLocalNames].forEach((localName) => {
|
|
352
|
+
importSourceByLocal.set(localName, source);
|
|
353
|
+
});
|
|
345
354
|
const removableLocalNames = localNames.filter((localName) => removableNames.has(localName));
|
|
346
355
|
if (removableLocalNames.length > 0 &&
|
|
347
356
|
removableLocalNames.length === localNames.length &&
|
|
348
357
|
removableLocalNames.every((localName) => !referencedNames.has(localName))) {
|
|
349
358
|
if (removableLocalNames.some((localName) => preserveSideEffectImportLocals.has(localName))) {
|
|
350
|
-
|
|
359
|
+
removedSideEffectImportRanges.push({
|
|
351
360
|
end: statement.end,
|
|
352
361
|
start: statement.start,
|
|
353
|
-
value: `import ${code.slice(statement.source.start, statement.source.end)};`,
|
|
354
362
|
});
|
|
355
363
|
return;
|
|
356
364
|
}
|
|
357
365
|
removals.push(expandImportRemovalRange(code, statement.start, statement.end));
|
|
358
366
|
return;
|
|
359
367
|
}
|
|
368
|
+
if (orderedLocalNames.length > 0 && !keptImportRangesBySource.has(source)) {
|
|
369
|
+
keptImportRangesBySource.set(source, {
|
|
370
|
+
end: statement.end,
|
|
371
|
+
start: statement.start,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
360
374
|
const { specifiers } = statement;
|
|
361
375
|
if (!Array.isArray(specifiers) || specifiers.length <= 1) {
|
|
362
376
|
return;
|
|
@@ -373,6 +387,67 @@ export const collectUnusedImportRemovals = (code, program, referencedNames, remo
|
|
|
373
387
|
}
|
|
374
388
|
});
|
|
375
389
|
});
|
|
390
|
+
if (removedSideEffectImportRanges.length > 0) {
|
|
391
|
+
const seenSources = new Set();
|
|
392
|
+
const removedRanges = removedSideEffectImportRanges.sort((a, b) => a.start - b.start);
|
|
393
|
+
const [firstRemoved, ...restRemoved] = removedRanges;
|
|
394
|
+
const pendingImports = [];
|
|
395
|
+
let insertionAfterLastKept = null;
|
|
396
|
+
let usedFirstRemovedRange = false;
|
|
397
|
+
const flushBefore = (position) => {
|
|
398
|
+
if (pendingImports.length === 0) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
removals.push({
|
|
402
|
+
end: position,
|
|
403
|
+
start: position,
|
|
404
|
+
value: `${pendingImports.join('\n')}\n`,
|
|
405
|
+
});
|
|
406
|
+
pendingImports.length = 0;
|
|
407
|
+
};
|
|
408
|
+
[...preserveSideEffectImportOrderLocals].forEach((localName) => {
|
|
409
|
+
const source = importSourceByLocal.get(localName);
|
|
410
|
+
if (!source) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const keptRange = keptImportRangesBySource.get(source);
|
|
414
|
+
if (keptRange) {
|
|
415
|
+
flushBefore(keptRange.start);
|
|
416
|
+
insertionAfterLastKept = keptRange.end;
|
|
417
|
+
if (preserveSideEffectImportLocals.has(localName)) {
|
|
418
|
+
seenSources.add(source);
|
|
419
|
+
}
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (!preserveSideEffectImportLocals.has(localName) ||
|
|
423
|
+
seenSources.has(source)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
seenSources.add(source);
|
|
427
|
+
pendingImports.push(`import ${source};`);
|
|
428
|
+
});
|
|
429
|
+
if (pendingImports.length > 0) {
|
|
430
|
+
if (insertionAfterLastKept !== null) {
|
|
431
|
+
removals.push({
|
|
432
|
+
end: insertionAfterLastKept,
|
|
433
|
+
start: insertionAfterLastKept,
|
|
434
|
+
value: `\n${pendingImports.join('\n')}`,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
else if (firstRemoved) {
|
|
438
|
+
usedFirstRemovedRange = true;
|
|
439
|
+
removals.push({
|
|
440
|
+
end: firstRemoved.end,
|
|
441
|
+
start: firstRemoved.start,
|
|
442
|
+
value: pendingImports.join('\n'),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
removals.push(...(usedFirstRemovedRange ? restRemoved : removedRanges).map((range) => ({
|
|
447
|
+
...range,
|
|
448
|
+
value: '',
|
|
449
|
+
})));
|
|
450
|
+
}
|
|
376
451
|
return removals;
|
|
377
452
|
};
|
|
378
453
|
export const collectUnusedTopLevelDeclarationRemovals = (code, program, referencedNames, removableNames) => {
|
|
@@ -441,21 +516,20 @@ export const collectEmptyTopLevelBlockRemovals = (code, program) => {
|
|
|
441
516
|
});
|
|
442
517
|
return removals;
|
|
443
518
|
};
|
|
444
|
-
export const removeUnusedAfterReplacement = (code, filename, initialRemovableNames, removableExpressionRefs, preserveSideEffectImportLocals) => {
|
|
519
|
+
export const removeUnusedAfterReplacement = (code, filename, initialRemovableNames, removableExpressionRefs, preserveSideEffectImportLocals, preserveSideEffectImportOrderLocals = preserveSideEffectImportLocals) => {
|
|
445
520
|
let current = code;
|
|
521
|
+
let program = null;
|
|
446
522
|
const cumulativeRemovableNames = new Set(initialRemovableNames);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
catch {
|
|
453
|
-
return current;
|
|
454
|
-
}
|
|
455
|
-
};
|
|
523
|
+
// Incremental cleanup loop: validate-by-parsing the next iteration's
|
|
524
|
+
// candidate code AND reuse that parse as the next iter's `program` input,
|
|
525
|
+
// instead of re-parsing at the top of the next iter. Saves one parse per
|
|
526
|
+
// loop revolution (N+1 parses for an N-iter loop instead of 2N).
|
|
527
|
+
// Also short-circuits a round earlier when no removals were collected.
|
|
456
528
|
for (let idx = 0; idx < 5; idx += 1) {
|
|
457
529
|
const previous = current;
|
|
458
|
-
|
|
530
|
+
if (program === null) {
|
|
531
|
+
program = parseOxc(current, filename);
|
|
532
|
+
}
|
|
459
533
|
const statements = collectTopLevelStatementInfos(program);
|
|
460
534
|
const removableNames = collectRemovableNamesFromStatements(statements, cumulativeRemovableNames);
|
|
461
535
|
removableNames.forEach((name) => cumulativeRemovableNames.add(name));
|
|
@@ -466,14 +540,25 @@ export const removeUnusedAfterReplacement = (code, filename, initialRemovableNam
|
|
|
466
540
|
...collectUnusedScopedDeclarationRemovals(current, scopedBindings, cumulativeRemovableNames),
|
|
467
541
|
...collectUnusedTopLevelDeclarationRemovals(current, program, referencedNames, cumulativeRemovableNames),
|
|
468
542
|
...collectUnusedGeneratedHelperDeclarationRemovals(current, program, referencedNames),
|
|
469
|
-
...collectUnusedImportRemovals(current, program, referencedNames, cumulativeRemovableNames, preserveSideEffectImportLocals),
|
|
543
|
+
...collectUnusedImportRemovals(current, program, referencedNames, cumulativeRemovableNames, preserveSideEffectImportLocals, preserveSideEffectImportOrderLocals),
|
|
470
544
|
...collectTopLevelExpressionStatementRemovals(current, statements, topLevelBindings, removableExpressionRefs),
|
|
471
545
|
...collectEmptyTopLevelBlockRemovals(current, program),
|
|
472
546
|
]);
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
547
|
+
if (removals.length === 0) {
|
|
548
|
+
// Convergence: next iter would parse the same code and see the same
|
|
549
|
+
// removable set. Skip the round of walks + parse.
|
|
550
|
+
return current;
|
|
551
|
+
}
|
|
552
|
+
const next = applyOxcReplacements(current, removals);
|
|
553
|
+
try {
|
|
554
|
+
// Validate + capture the AST for the next iteration in one parse.
|
|
555
|
+
program = parseOxc(next, filename);
|
|
556
|
+
current = next;
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
// Pathological removal — drop this iteration and return prior state.
|
|
560
|
+
return current;
|
|
561
|
+
}
|
|
477
562
|
if (current === previous) {
|
|
478
563
|
return current;
|
|
479
564
|
}
|
|
@@ -894,6 +894,14 @@ const getChildren = (node) => {
|
|
|
894
894
|
return result;
|
|
895
895
|
};
|
|
896
896
|
const precollectRequireSources = (node, state) => {
|
|
897
|
+
// Cheap text precheck at the Program entry: if the file body has no
|
|
898
|
+
// 'require(' substring there can be no CommonJS require() init to collect.
|
|
899
|
+
// Skip the full AST walk — meaningful saving on ESM-only modules in large
|
|
900
|
+
// monorepos. Done at Program-level only so nested recursion doesn't pay
|
|
901
|
+
// the indexOf cost per node.
|
|
902
|
+
if (node.type === 'Program' && state.code.indexOf('require(') === -1) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
897
905
|
if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier') {
|
|
898
906
|
const source = node.init ? sourceFromRequireSyntax(node.init) : null;
|
|
899
907
|
if (source) {
|
|
@@ -2,6 +2,7 @@ import type { StrictOptions } from '@wyw-in-js/shared';
|
|
|
2
2
|
import type { RawSourceMap } from 'source-map';
|
|
3
3
|
import type { WYWTransformMetadata } from '../TransformMetadata';
|
|
4
4
|
export type OxcCollectOptions = Pick<StrictOptions, 'classNameSlug' | 'displayName' | 'eval' | 'extensions' | 'tagResolver' | 'variableNameConfig'> & {
|
|
5
|
+
preserveSideEffectImportOrderLocals?: Set<string>;
|
|
5
6
|
preserveSideEffectImportLocals?: Set<string>;
|
|
6
7
|
};
|
|
7
8
|
export type OxcCollectResult = {
|
|
@@ -326,10 +326,12 @@ const extractExpression = (expression, ctx, evaluate) => {
|
|
|
326
326
|
else {
|
|
327
327
|
hasNonStaticLocalReference = true;
|
|
328
328
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
if (!isProcessorManagedLocal) {
|
|
330
|
+
assertHoistable(binding, ctx);
|
|
331
|
+
addHoistedDeclaration(binding, ctx);
|
|
332
|
+
if (!binding.isRoot && binding.declarator?.id.type === 'Identifier') {
|
|
333
|
+
identifierReplacements.set(name, getHoistedBindingName(binding, ctx));
|
|
334
|
+
}
|
|
333
335
|
}
|
|
334
336
|
});
|
|
335
337
|
// Merge literal-const inlines (e.g. `const A = 32` -> "32") with
|
package/types/utils/oxc/ast.js
CHANGED
|
@@ -2,36 +2,60 @@ export const isOxcNode = (value) => !!value &&
|
|
|
2
2
|
typeof value === 'object' &&
|
|
3
3
|
'type' in value &&
|
|
4
4
|
typeof value.type === 'string';
|
|
5
|
+
// Cache visitor-key lists per node.type. oxc-parser AST nodes have a stable
|
|
6
|
+
// shape per `type` — optional children are present as null/undefined, not
|
|
7
|
+
// omitted — so Object.keys() returns the same key set for every instance of
|
|
8
|
+
// the same kind. First instance pays the discovery cost; the rest do an
|
|
9
|
+
// indexed lookup. getOxcNodeChildren is invoked tens of millions of times
|
|
10
|
+
// on a cold build of a large monorepo, so this matters a lot.
|
|
11
|
+
//
|
|
12
|
+
// An instance-level WeakMap cache of the resulting Node[] was tried and
|
|
13
|
+
// regressed wall time ~20% (WeakMap.get/set per call beat the recompute
|
|
14
|
+
// savings for small children arrays + pinned arrays into older generations
|
|
15
|
+
// and increased GC pressure). Per-type key cache only.
|
|
16
|
+
const META_KEYS = new Set([
|
|
17
|
+
'comments',
|
|
18
|
+
'end',
|
|
19
|
+
'errors',
|
|
20
|
+
'parent',
|
|
21
|
+
'range',
|
|
22
|
+
'span',
|
|
23
|
+
'start',
|
|
24
|
+
'type',
|
|
25
|
+
]);
|
|
26
|
+
const VISITOR_KEYS_BY_TYPE = new Map();
|
|
27
|
+
const visitorKeysFor = (node) => {
|
|
28
|
+
let keys = VISITOR_KEYS_BY_TYPE.get(node.type);
|
|
29
|
+
if (keys === undefined) {
|
|
30
|
+
keys = Object.keys(node).filter((key) => !META_KEYS.has(key));
|
|
31
|
+
VISITOR_KEYS_BY_TYPE.set(node.type, keys);
|
|
32
|
+
}
|
|
33
|
+
return keys;
|
|
34
|
+
};
|
|
5
35
|
export const getOxcNodeChildren = (node) => {
|
|
6
36
|
const result = [];
|
|
7
37
|
const record = node;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
key === 'errors' ||
|
|
12
|
-
key === 'parent' ||
|
|
13
|
-
key === 'range' ||
|
|
14
|
-
key === 'span' ||
|
|
15
|
-
key === 'start' ||
|
|
16
|
-
key === 'type') {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const value = record[key];
|
|
38
|
+
const keys = visitorKeysFor(record);
|
|
39
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
40
|
+
const value = record[keys[i]];
|
|
20
41
|
if (isOxcNode(value)) {
|
|
21
42
|
result.push(value);
|
|
22
|
-
return;
|
|
23
43
|
}
|
|
24
|
-
if (Array.isArray(value)) {
|
|
25
|
-
value.
|
|
44
|
+
else if (Array.isArray(value)) {
|
|
45
|
+
for (let j = 0; j < value.length; j += 1) {
|
|
46
|
+
const item = value[j];
|
|
26
47
|
if (isOxcNode(item)) {
|
|
27
48
|
result.push(item);
|
|
28
49
|
}
|
|
29
|
-
}
|
|
50
|
+
}
|
|
30
51
|
}
|
|
31
|
-
}
|
|
52
|
+
}
|
|
32
53
|
return result;
|
|
33
54
|
};
|
|
34
55
|
export const walkOxc = (node, enter, parent = null) => {
|
|
35
56
|
enter(node, parent);
|
|
36
|
-
|
|
57
|
+
const children = getOxcNodeChildren(node);
|
|
58
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
59
|
+
walkOxc(children[i], enter, node);
|
|
60
|
+
}
|
|
37
61
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-syntax,no-continue */
|
|
2
2
|
import { collectOxcExportsAndImports } from './collectOxcExportsAndImports';
|
|
3
3
|
import { EventEmitter } from './EventEmitter';
|
|
4
|
+
import { getOxcNodeChildren } from './oxc/ast';
|
|
4
5
|
import { parseOxcProgramCached } from './parseOxc';
|
|
5
6
|
const ssrCheckFields = new Set([
|
|
6
7
|
'document',
|
|
@@ -104,28 +105,11 @@ const isNode = (value) => !!value &&
|
|
|
104
105
|
typeof value === 'object' &&
|
|
105
106
|
'type' in value &&
|
|
106
107
|
typeof value.type === 'string';
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const value = record[key];
|
|
115
|
-
if (isNode(value)) {
|
|
116
|
-
result.push(value);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (Array.isArray(value)) {
|
|
120
|
-
value.forEach((item) => {
|
|
121
|
-
if (isNode(item)) {
|
|
122
|
-
result.push(item);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return result;
|
|
128
|
-
};
|
|
108
|
+
// Reuses the per-node.type visitor-key cache in utils/oxc/ast.ts. This file's
|
|
109
|
+
// getChildren shape historically diverged from the canonical one (a smaller
|
|
110
|
+
// metadata-key skip set), but the produced children list is identical in
|
|
111
|
+
// practice because oxc-parser nodes don't carry the extra metadata fields.
|
|
112
|
+
const getChildren = getOxcNodeChildren;
|
|
129
113
|
const parseOxc = (code, filename) => {
|
|
130
114
|
return parseOxcProgramCached(filename, code, 'unambiguous');
|
|
131
115
|
};
|
|
@@ -348,66 +332,95 @@ const predeclareScopeNames = (node, scope) => {
|
|
|
348
332
|
});
|
|
349
333
|
}
|
|
350
334
|
const visitScopeDescendants = (child) => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
335
|
+
switch (child.type) {
|
|
336
|
+
case 'VariableDeclarator': {
|
|
337
|
+
const names = collectBindingNames(child.id);
|
|
338
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
339
|
+
scope.names.add(names[i]);
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case 'FunctionDeclaration':
|
|
344
|
+
case 'ClassDeclaration':
|
|
345
|
+
if (child.id) {
|
|
346
|
+
scope.names.add(child.id.name);
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
case 'ImportDefaultSpecifier':
|
|
350
|
+
case 'ImportNamespaceSpecifier':
|
|
351
|
+
case 'ImportSpecifier':
|
|
352
|
+
scope.names.add(child.local.name);
|
|
353
|
+
break;
|
|
354
|
+
default:
|
|
355
|
+
break;
|
|
366
356
|
}
|
|
367
357
|
if (createsScope(child)) {
|
|
368
358
|
return;
|
|
369
359
|
}
|
|
370
|
-
getChildren(child)
|
|
360
|
+
const children = getChildren(child);
|
|
361
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
362
|
+
visitScopeDescendants(children[i]);
|
|
363
|
+
}
|
|
371
364
|
};
|
|
372
|
-
getChildren(node)
|
|
365
|
+
const rootChildren = getChildren(node);
|
|
366
|
+
for (let i = 0; i < rootChildren.length; i += 1) {
|
|
367
|
+
visitScopeDescendants(rootChildren[i]);
|
|
368
|
+
}
|
|
373
369
|
};
|
|
374
370
|
const declareBindings = (node, scope) => {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
if (node.type === 'FunctionDeclaration' && node.id) {
|
|
387
|
-
scope.names.add(node.id.name);
|
|
388
|
-
scope.bindings.set(node.id.name, null);
|
|
389
|
-
}
|
|
390
|
-
if (node.type === 'ClassDeclaration' && node.id) {
|
|
391
|
-
scope.names.add(node.id.name);
|
|
392
|
-
scope.bindings.set(node.id.name, null);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
if (node.type === 'ImportDefaultSpecifier' ||
|
|
396
|
-
node.type === 'ImportNamespaceSpecifier' ||
|
|
397
|
-
node.type === 'ImportSpecifier') {
|
|
398
|
-
scope.names.add(node.local.name);
|
|
399
|
-
scope.bindings.set(node.local.name, null);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (node.type === 'FunctionDeclaration' ||
|
|
403
|
-
node.type === 'FunctionExpression' ||
|
|
404
|
-
node.type === 'ArrowFunctionExpression') {
|
|
405
|
-
node.params.forEach((param) => {
|
|
406
|
-
collectBindingNames(param).forEach((name) => {
|
|
371
|
+
// Called for every visited AST node, so the dispatch is hot. A switch lets
|
|
372
|
+
// V8 generate a jump table on node.type; the previous chained `if`s walked
|
|
373
|
+
// each branch's string-compare for every non-matching node.
|
|
374
|
+
switch (node.type) {
|
|
375
|
+
case 'VariableDeclarator': {
|
|
376
|
+
const names = collectBindingNames(node.id);
|
|
377
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
378
|
+
const name = names[i];
|
|
407
379
|
scope.names.add(name);
|
|
408
380
|
scope.bindings.set(name, null);
|
|
409
|
-
}
|
|
410
|
-
|
|
381
|
+
}
|
|
382
|
+
if (node.id.type === 'Identifier' && node.init) {
|
|
383
|
+
scope.bindings.set(node.id.name, node.init);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
case 'ClassDeclaration': {
|
|
388
|
+
if (node.id) {
|
|
389
|
+
scope.names.add(node.id.name);
|
|
390
|
+
scope.bindings.set(node.id.name, null);
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
case 'ImportDefaultSpecifier':
|
|
395
|
+
case 'ImportNamespaceSpecifier':
|
|
396
|
+
case 'ImportSpecifier': {
|
|
397
|
+
scope.names.add(node.local.name);
|
|
398
|
+
scope.bindings.set(node.local.name, null);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
case 'FunctionDeclaration': {
|
|
402
|
+
if (node.id) {
|
|
403
|
+
scope.names.add(node.id.name);
|
|
404
|
+
scope.bindings.set(node.id.name, null);
|
|
405
|
+
}
|
|
406
|
+
// Fall through to declare params.
|
|
407
|
+
}
|
|
408
|
+
// eslint-disable-next-line no-fallthrough
|
|
409
|
+
case 'FunctionExpression':
|
|
410
|
+
case 'ArrowFunctionExpression': {
|
|
411
|
+
const { params } = node;
|
|
412
|
+
for (let i = 0; i < params.length; i += 1) {
|
|
413
|
+
const names = collectBindingNames(params[i]);
|
|
414
|
+
for (let j = 0; j < names.length; j += 1) {
|
|
415
|
+
const name = names[j];
|
|
416
|
+
scope.names.add(name);
|
|
417
|
+
scope.bindings.set(name, null);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
default:
|
|
423
|
+
break;
|
|
411
424
|
}
|
|
412
425
|
};
|
|
413
426
|
const visit = (node, scope, enter, parent = null, ancestors = []) => {
|
|
@@ -418,7 +431,17 @@ const visit = (node, scope, enter, parent = null, ancestors = []) => {
|
|
|
418
431
|
}
|
|
419
432
|
declareBindings(node, currentScope);
|
|
420
433
|
enter(node, currentScope, parent, ancestors);
|
|
421
|
-
|
|
434
|
+
// Push onto a shared ancestors stack instead of allocating `[...ancestors,
|
|
435
|
+
// node]` per child step (O(n × depth) extra allocation on deep ASTs).
|
|
436
|
+
// Every consumer of `ancestors` in this file reads it synchronously inside
|
|
437
|
+
// the enter callback; future callers that need to retain the reference
|
|
438
|
+
// must .slice() it themselves.
|
|
439
|
+
ancestors.push(node);
|
|
440
|
+
const children = getChildren(node);
|
|
441
|
+
for (let i = 0; i < children.length; i += 1) {
|
|
442
|
+
visit(children[i], currentScope, enter, node, ancestors);
|
|
443
|
+
}
|
|
444
|
+
ancestors.pop();
|
|
422
445
|
};
|
|
423
446
|
export const replaceImportMetaEnvWithOxc = (code, filename) => {
|
|
424
447
|
if (!importMetaEnvRe.test(code)) {
|
package/types/utils/parseOxc.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { parseSync } from 'oxc-parser';
|
|
2
|
-
|
|
2
|
+
// 200 evicts under sustained pressure on large monorepos — the
|
|
3
|
+
// removeUnusedAfterReplacement cleanup loop reparses on every iteration
|
|
4
|
+
// (new content -> new key) and applyOxcProcessors reparses after extraction.
|
|
5
|
+
// 1000 is still bounded (~50-100 MB worst case for an enormous build) and
|
|
6
|
+
// keeps every entry hot across the actions for a single file.
|
|
7
|
+
const MAX_PARSE_CACHE_ENTRIES = 1000;
|
|
3
8
|
const parseCache = new Map();
|
|
4
9
|
const getAstType = (filename) => filename.endsWith('.ts') || filename.endsWith('.tsx') ? 'ts' : 'js';
|
|
5
10
|
const makeCacheKey = (filename, code, sourceType) => `${sourceType}\0${filename}\0${code}`;
|