@bamboocss/extractor 1.45.5 → 1.46.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/dist/index.cjs +227 -76
- package/dist/index.d.cts +12 -1
- package/dist/index.d.mts +12 -1
- package/dist/index.mjs +227 -76
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -306,6 +306,65 @@ const box = {
|
|
|
306
306
|
}
|
|
307
307
|
};
|
|
308
308
|
//#endregion
|
|
309
|
+
//#region src/dependency-cache.ts
|
|
310
|
+
const EMPTY_DEPENDENCIES = [];
|
|
311
|
+
/** One synchronous extraction context owns one nested capture stack. */
|
|
312
|
+
const trackers = /* @__PURE__ */ new WeakMap();
|
|
313
|
+
/**
|
|
314
|
+
* Attribute one resolved local module to every cache computation currently enclosing it.
|
|
315
|
+
*
|
|
316
|
+
* Paths, rather than SourceFile objects, are the replay payload. A source replacement may
|
|
317
|
+
* retire every node in a file, while its stable path is exactly the fact a fresh ParserResult
|
|
318
|
+
* needs. The caller-facing callback stays outside the cache and therefore belongs to the
|
|
319
|
+
* current query/environment rather than whichever parse populated the entry.
|
|
320
|
+
*/
|
|
321
|
+
const recordModuleDependency = (ctx, filePath) => {
|
|
322
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
323
|
+
const tracker = trackers.get(ctx);
|
|
324
|
+
if (tracker) for (const frame of tracker.frames) {
|
|
325
|
+
frame.dependencies ??= /* @__PURE__ */ new Set();
|
|
326
|
+
frame.dependencies.add(normalized);
|
|
327
|
+
}
|
|
328
|
+
ctx.recordDependency?.(normalized);
|
|
329
|
+
};
|
|
330
|
+
/** Begin one nested cache computation without allocating a Set on dependency-free paths. */
|
|
331
|
+
const beginDependencyCapture = (ctx) => {
|
|
332
|
+
let tracker = trackers.get(ctx);
|
|
333
|
+
if (!tracker) {
|
|
334
|
+
tracker = { frames: [] };
|
|
335
|
+
trackers.set(ctx, tracker);
|
|
336
|
+
}
|
|
337
|
+
const frame = {};
|
|
338
|
+
tracker.frames.push(frame);
|
|
339
|
+
return {
|
|
340
|
+
entry(value) {
|
|
341
|
+
return {
|
|
342
|
+
dependencies: frame.dependencies ? [...frame.dependencies].sort() : EMPTY_DEPENDENCIES,
|
|
343
|
+
resolveModule: ctx.resolveModule,
|
|
344
|
+
value
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
end() {
|
|
348
|
+
tracker.frames.pop();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Replay a hit only inside the resolver scope which computed it.
|
|
354
|
+
*
|
|
355
|
+
* The same ts-morph node can be inspected under two extractor contexts with different module
|
|
356
|
+
* placement rules. Returning the first context's value or dependency paths to the second would
|
|
357
|
+
* be cross-Project leakage, so a scope mismatch is a cache miss and gets overwritten normally.
|
|
358
|
+
*/
|
|
359
|
+
const replayDependencyCache = (entry, ctx) => {
|
|
360
|
+
if (entry.resolveModule !== ctx.resolveModule) return { hit: false };
|
|
361
|
+
for (const dependency of entry.dependencies) recordModuleDependency(ctx, dependency);
|
|
362
|
+
return {
|
|
363
|
+
hit: true,
|
|
364
|
+
value: entry.value
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
//#endregion
|
|
309
368
|
//#region src/resolve-imported-value.ts
|
|
310
369
|
/**
|
|
311
370
|
* The values an expression borrows from other modules, resolved by reading the imports.
|
|
@@ -322,9 +381,9 @@ const box = {
|
|
|
322
381
|
* rather than with the number of style calls — a large project reported a 5.4x extraction
|
|
323
382
|
* slowdown and an OOM in CI.
|
|
324
383
|
*
|
|
325
|
-
* Following an import is two cheap steps
|
|
326
|
-
*
|
|
327
|
-
*
|
|
384
|
+
* Following an import is two cheap steps: ask the caller's source-graph resolver to place the
|
|
385
|
+
* specifier, then read that file's exported declaration. Crossing the import boundary is the
|
|
386
|
+
* only thing the checker was doing here.
|
|
328
387
|
*
|
|
329
388
|
* The evaluator resolves everything *within* a module by walking scopes, so a helper that
|
|
330
389
|
* refers to another binding in its own file needs nothing from us.
|
|
@@ -332,7 +391,14 @@ const box = {
|
|
|
332
391
|
/** How far a chain of helpers importing helpers is followed. */
|
|
333
392
|
const MAX_DEPTH = 4;
|
|
334
393
|
/** Evaluated once per declaration, however many call sites reach it. */
|
|
335
|
-
|
|
394
|
+
let evaluatedValues = /* @__PURE__ */ new WeakMap();
|
|
395
|
+
let hasEvaluatedValues = false;
|
|
396
|
+
/** @internal Drop imported declarations whose closure may include an edited module. */
|
|
397
|
+
const clearImportedValueCache = () => {
|
|
398
|
+
if (!hasEvaluatedValues) return;
|
|
399
|
+
evaluatedValues = /* @__PURE__ */ new WeakMap();
|
|
400
|
+
hasEvaluatedValues = false;
|
|
401
|
+
};
|
|
336
402
|
/**
|
|
337
403
|
* Local name to the name and declaration it was imported from.
|
|
338
404
|
*
|
|
@@ -362,17 +428,28 @@ const importBindingsFor = (sourceFile) => {
|
|
|
362
428
|
};
|
|
363
429
|
/** What an imported name refers to, or nothing if it cannot be reached safely. */
|
|
364
430
|
const valueForBinding = (binding, ctx, stack, depth, evaluateExpression) => {
|
|
365
|
-
const sourceFile = getModuleSpecifierSourceFile(binding.declaration);
|
|
431
|
+
const sourceFile = getModuleSpecifierSourceFile(binding.declaration, ctx);
|
|
366
432
|
if (!sourceFile) return;
|
|
367
433
|
if (sourceFile.isInNodeModules()) return;
|
|
368
434
|
const declaration = getExportedVarDeclarationWithName(binding.exportedName, sourceFile, stack, ctx);
|
|
369
435
|
if (!declaration) return;
|
|
370
|
-
|
|
436
|
+
const cached = evaluatedValues.get(declaration);
|
|
437
|
+
if (cached) {
|
|
438
|
+
const replayed = replayDependencyCache(cached, ctx);
|
|
439
|
+
if (replayed.hit) return { value: replayed.value };
|
|
440
|
+
}
|
|
371
441
|
const initializer = declaration.getInitializer();
|
|
372
442
|
if (!initializer) return;
|
|
373
|
-
const
|
|
443
|
+
const capture = beginDependencyCapture(ctx);
|
|
444
|
+
let value;
|
|
445
|
+
try {
|
|
446
|
+
value = evaluateExpression(initializer, stack, ctx, depth + 1);
|
|
447
|
+
} finally {
|
|
448
|
+
capture.end();
|
|
449
|
+
}
|
|
374
450
|
if (value === void 0) return;
|
|
375
|
-
evaluatedValues.set(declaration, value);
|
|
451
|
+
evaluatedValues.set(declaration, capture.entry(value));
|
|
452
|
+
hasEvaluatedValues = true;
|
|
376
453
|
return { value };
|
|
377
454
|
};
|
|
378
455
|
/**
|
|
@@ -385,27 +462,34 @@ const valueForBinding = (binding, ctx, stack, depth, evaluateExpression) => {
|
|
|
385
462
|
*/
|
|
386
463
|
const importedEnvironmentFor = (node, ctx, stack, depth, evaluateExpression) => {
|
|
387
464
|
if (depth >= MAX_DEPTH) return;
|
|
388
|
-
const calls = node.getDescendantsOfKind(ts_morph.SyntaxKind.CallExpression);
|
|
389
|
-
if (ts_morph.Node.isCallExpression(node)) calls.unshift(node);
|
|
390
|
-
if (!calls.length) return;
|
|
391
465
|
const bindings = importBindingsFor(node.getSourceFile());
|
|
392
466
|
if (!bindings.size) return;
|
|
467
|
+
const references = node.getDescendantsOfKind(ts_morph.SyntaxKind.Identifier);
|
|
468
|
+
if (ts_morph.Node.isIdentifier(node)) references.unshift(node);
|
|
469
|
+
if (!references.length) return;
|
|
393
470
|
let environment;
|
|
394
|
-
for (const
|
|
395
|
-
const
|
|
396
|
-
const binding = bindings.get(
|
|
397
|
-
if (!binding || environment?.[
|
|
471
|
+
for (const reference of references) {
|
|
472
|
+
const name = reference.getText();
|
|
473
|
+
const binding = bindings.get(name);
|
|
474
|
+
if (!binding || environment?.[name] !== void 0) continue;
|
|
398
475
|
const resolved = valueForBinding(binding, ctx, stack, depth, evaluateExpression);
|
|
399
476
|
if (!resolved) continue;
|
|
400
477
|
environment ??= {};
|
|
401
|
-
environment[
|
|
478
|
+
environment[name] = resolved.value;
|
|
402
479
|
}
|
|
403
480
|
return environment;
|
|
404
481
|
};
|
|
405
482
|
//#endregion
|
|
406
483
|
//#region src/evaluate-node.ts
|
|
407
484
|
const TsEvalError = Symbol("EvalError");
|
|
408
|
-
|
|
485
|
+
let cacheMap$2 = /* @__PURE__ */ new WeakMap();
|
|
486
|
+
let hasCachedEntries$1 = false;
|
|
487
|
+
/** @internal Drop values whose expression may close over an edited source file. */
|
|
488
|
+
const clearEvaluateNodeCache = () => {
|
|
489
|
+
if (!hasCachedEntries$1) return;
|
|
490
|
+
cacheMap$2 = /* @__PURE__ */ new WeakMap();
|
|
491
|
+
hasCachedEntries$1 = false;
|
|
492
|
+
};
|
|
409
493
|
/** @see https://github.com/wessberg/ts-evaluator#setting-up-policies */
|
|
410
494
|
const POLICY = {
|
|
411
495
|
console: false,
|
|
@@ -436,13 +520,39 @@ const POLICY = {
|
|
|
436
520
|
const evaluateNode = (node, stack, ctx, depth = 0) => {
|
|
437
521
|
if (ctx.flags?.skipEvaluate) return;
|
|
438
522
|
if (ctx.canEval && !ctx.canEval?.(node, stack)) return;
|
|
439
|
-
|
|
440
|
-
|
|
523
|
+
const cached = depth === 0 ? cacheMap$2.get(node) : void 0;
|
|
524
|
+
if (cached) {
|
|
525
|
+
const replayed = replayDependencyCache(cached, ctx);
|
|
526
|
+
if (replayed.hit) return replayed.value;
|
|
527
|
+
}
|
|
528
|
+
const capture = depth === 0 ? beginDependencyCapture(ctx) : void 0;
|
|
529
|
+
try {
|
|
530
|
+
return evaluateNodeUncached(node, stack, ctx, depth, capture?.entry);
|
|
531
|
+
} finally {
|
|
532
|
+
capture?.end();
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
const evaluateNodeUncached = (node, stack, ctx, depth, dependencyEntry) => {
|
|
536
|
+
let options = {
|
|
441
537
|
policy: { ...POLICY },
|
|
442
538
|
...ctx.getEvaluateOptions?.(node, stack),
|
|
443
539
|
node: node.compilerNode,
|
|
444
540
|
typescript: ts_morph.ts
|
|
445
541
|
};
|
|
542
|
+
let imported = depth > 0 ? importedEnvironmentFor(node, ctx, stack, depth, safeEvaluateNode) : void 0;
|
|
543
|
+
if (imported) {
|
|
544
|
+
const environment = options.environment;
|
|
545
|
+
options = {
|
|
546
|
+
...options,
|
|
547
|
+
environment: {
|
|
548
|
+
...environment,
|
|
549
|
+
extra: {
|
|
550
|
+
...environment?.extra,
|
|
551
|
+
...imported
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
446
556
|
let result = (0, ts_evaluator.evaluate)(options);
|
|
447
557
|
/**
|
|
448
558
|
* Retried only on failure, with whatever this expression imports put in scope.
|
|
@@ -454,17 +564,26 @@ const evaluateNode = (node, stack, ctx, depth = 0) => {
|
|
|
454
564
|
* the path of every build.
|
|
455
565
|
*/
|
|
456
566
|
if (!result.success) {
|
|
457
|
-
|
|
458
|
-
if (imported)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
...options
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
567
|
+
imported ??= importedEnvironmentFor(node, ctx, stack, depth, safeEvaluateNode);
|
|
568
|
+
if (imported) {
|
|
569
|
+
const environment = options.environment;
|
|
570
|
+
result = (0, ts_evaluator.evaluate)({
|
|
571
|
+
...options,
|
|
572
|
+
environment: {
|
|
573
|
+
...environment,
|
|
574
|
+
extra: {
|
|
575
|
+
...environment?.extra,
|
|
576
|
+
...imported
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
465
581
|
}
|
|
466
582
|
const expr = result.success ? result.value : TsEvalError;
|
|
467
|
-
if (depth === 0)
|
|
583
|
+
if (depth === 0) {
|
|
584
|
+
cacheMap$2.set(node, dependencyEntry(expr));
|
|
585
|
+
hasCachedEntries$1 = true;
|
|
586
|
+
}
|
|
468
587
|
return expr;
|
|
469
588
|
};
|
|
470
589
|
const safeEvaluateNode = (node, stack, ctx, depth = 0) => {
|
|
@@ -487,7 +606,7 @@ function getDeclarationFor(node, stack, ctx) {
|
|
|
487
606
|
declaration = parent;
|
|
488
607
|
} else if (ts_morph.Node.isImportSpecifier(parent) && (parent.getNameNode() == node || parent.getAliasNode() == node)) {
|
|
489
608
|
if (ctx.flags?.skipTraverseFiles) return;
|
|
490
|
-
const sourceFile = getModuleSpecifierSourceFile(parent.getImportDeclaration());
|
|
609
|
+
const sourceFile = getModuleSpecifierSourceFile(parent.getImportDeclaration(), ctx);
|
|
491
610
|
if (sourceFile) {
|
|
492
611
|
const exportStack = [parent, sourceFile];
|
|
493
612
|
const maybeVar = getExportedVarDeclarationWithName(parent.getNameNode().getText(), sourceFile, exportStack, ctx);
|
|
@@ -505,53 +624,74 @@ const getInnermostScope = (from) => {
|
|
|
505
624
|
while (scope && !isScope(scope)) scope = scope.getParent();
|
|
506
625
|
return scope;
|
|
507
626
|
};
|
|
627
|
+
/**
|
|
628
|
+
* Keyed on the compiler node, never the ts-morph wrapper.
|
|
629
|
+
*
|
|
630
|
+
* `Project.replaceWithText` — which is how a re-parse installs a source transform — keeps the
|
|
631
|
+
* wrapper identity and swaps the compiler node underneath it. A wrapper-keyed cache would
|
|
632
|
+
* therefore answer a rebuild from the previous revision of the file, which is a wrong
|
|
633
|
+
* stylesheet rather than a slow one. Keying on the compiler node makes the cache
|
|
634
|
+
* self-invalidating: ts-morph reuses a compiler node exactly when its subtree did not change.
|
|
635
|
+
*/
|
|
636
|
+
const declarationIndexes = /* @__PURE__ */ new WeakMap();
|
|
637
|
+
const isDeclarationName = (node, parent) => {
|
|
638
|
+
if (ts_morph.Node.isVariableDeclaration(parent) || ts_morph.Node.isParameterDeclaration(parent) || ts_morph.Node.isFunctionDeclaration(parent) || ts_morph.Node.isEnumDeclaration(parent) || ts_morph.Node.isBindingElement(parent)) return parent.getNameNode() == node;
|
|
639
|
+
return ts_morph.Node.isImportSpecifier(parent) && (parent.getNameNode() == node || parent.getAliasNode() == node);
|
|
640
|
+
};
|
|
641
|
+
/**
|
|
642
|
+
* Walk a scope once, so that every later lookup inside it is a map read.
|
|
643
|
+
*
|
|
644
|
+
* This used to be a `forEachDescendant` per identifier, widening to each enclosing scope and
|
|
645
|
+
* re-walking from scratch, calling `getText()` on every identifier it passed. A module whose
|
|
646
|
+
* declarations are referenced n times paid n full traversals of itself, and traversal is where
|
|
647
|
+
* ts-morph charges for wrapping each node — it measured ~10% of extraction on its own.
|
|
648
|
+
*/
|
|
649
|
+
const declarationIndexFor = (scope) => {
|
|
650
|
+
const cached = declarationIndexes.get(scope.compilerNode);
|
|
651
|
+
if (cached) return cached;
|
|
652
|
+
const index = /* @__PURE__ */ new Map();
|
|
653
|
+
scope.forEachDescendant((node) => {
|
|
654
|
+
if (!ts_morph.Node.isIdentifier(node)) return;
|
|
655
|
+
const parent = node.getParent();
|
|
656
|
+
if (!parent || !isDeclarationName(node, parent)) return;
|
|
657
|
+
const name = node.getText();
|
|
658
|
+
const declared = index.get(name);
|
|
659
|
+
if (declared) declared.push(node);
|
|
660
|
+
else index.set(name, [node]);
|
|
661
|
+
});
|
|
662
|
+
declarationIndexes.set(scope.compilerNode, index);
|
|
663
|
+
return index;
|
|
664
|
+
};
|
|
508
665
|
function findIdentifierValueDeclaration(identifier, stack, ctx, visitedsWithStack = /* @__PURE__ */ new Map()) {
|
|
509
666
|
let scope = identifier;
|
|
510
|
-
let foundNode;
|
|
511
|
-
let isUnresolvable = false;
|
|
512
667
|
let count = 0;
|
|
513
668
|
const innerStack = [];
|
|
669
|
+
const refName = identifier.getText();
|
|
514
670
|
do {
|
|
515
671
|
scope = getInnermostScope(scope);
|
|
516
672
|
count++;
|
|
517
673
|
if (!scope) return;
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
if (visitedsWithStack.has(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
674
|
+
for (const candidate of declarationIndexFor(scope).get(refName) ?? []) {
|
|
675
|
+
if (candidate == identifier) continue;
|
|
676
|
+
if (visitedsWithStack.has(candidate)) continue;
|
|
677
|
+
visitedsWithStack.set(candidate, innerStack);
|
|
678
|
+
const declarationStack = [candidate];
|
|
679
|
+
const maybeDeclaration = getDeclarationFor(candidate, declarationStack, ctx);
|
|
680
|
+
if (!maybeDeclaration) continue;
|
|
681
|
+
if (ts_morph.Node.isParameterDeclaration(maybeDeclaration)) {
|
|
682
|
+
const initializer = maybeDeclaration.getInitializer();
|
|
683
|
+
const typeNode = maybeDeclaration.getTypeNode();
|
|
684
|
+
if (initializer) innerStack.push(...declarationStack.concat(initializer));
|
|
685
|
+
else if (typeNode && ts_morph.Node.isTypeLiteral(typeNode)) innerStack.push(...declarationStack.concat(typeNode));
|
|
686
|
+
else return;
|
|
687
|
+
stack.push(...innerStack);
|
|
688
|
+
return maybeDeclaration;
|
|
524
689
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const declarationStack = [node];
|
|
529
|
-
const maybeDeclaration = getDeclarationFor(node, declarationStack, ctx);
|
|
530
|
-
if (maybeDeclaration) {
|
|
531
|
-
if (ts_morph.Node.isParameterDeclaration(maybeDeclaration)) {
|
|
532
|
-
const initializer = maybeDeclaration.getInitializer();
|
|
533
|
-
const typeNode = maybeDeclaration.getTypeNode();
|
|
534
|
-
if (initializer) {
|
|
535
|
-
innerStack.push(...declarationStack.concat(initializer));
|
|
536
|
-
foundNode = maybeDeclaration;
|
|
537
|
-
} else if (typeNode && ts_morph.Node.isTypeLiteral(typeNode)) {
|
|
538
|
-
innerStack.push(...declarationStack.concat(typeNode));
|
|
539
|
-
foundNode = maybeDeclaration;
|
|
540
|
-
} else isUnresolvable = true;
|
|
541
|
-
traversal.stop();
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
innerStack.push(...declarationStack);
|
|
545
|
-
foundNode = maybeDeclaration;
|
|
546
|
-
traversal.stop();
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
if (foundNode || isUnresolvable) {
|
|
551
|
-
if (foundNode) stack.push(...innerStack);
|
|
552
|
-
return foundNode;
|
|
690
|
+
innerStack.push(...declarationStack);
|
|
691
|
+
stack.push(...innerStack);
|
|
692
|
+
return maybeDeclaration;
|
|
553
693
|
}
|
|
554
|
-
} while (scope && !ts_morph.Node.isSourceFile(scope) &&
|
|
694
|
+
} while (scope && !ts_morph.Node.isSourceFile(scope) && count < 100);
|
|
555
695
|
}
|
|
556
696
|
//#endregion
|
|
557
697
|
//#region src/get-property-name.ts
|
|
@@ -665,7 +805,6 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
665
805
|
//#endregion
|
|
666
806
|
//#region src/maybe-box-node.ts
|
|
667
807
|
let cacheMap$1 = /* @__PURE__ */ new WeakMap();
|
|
668
|
-
const isCached = (node) => cacheMap$1.has(node);
|
|
669
808
|
const getCached = (node) => cacheMap$1.get(node);
|
|
670
809
|
/**
|
|
671
810
|
* Drops every memoized resolution.
|
|
@@ -676,6 +815,8 @@ const getCached = (node) => cacheMap$1.get(node);
|
|
|
676
815
|
* otherwise keep serving the value read before the edit.
|
|
677
816
|
*/
|
|
678
817
|
const clearBoxNodeCache = () => {
|
|
818
|
+
clearEvaluateNodeCache();
|
|
819
|
+
clearImportedValueCache();
|
|
679
820
|
if (!hasCachedEntries) return;
|
|
680
821
|
cacheMap$1 = /* @__PURE__ */ new WeakMap();
|
|
681
822
|
hasCachedEntries = false;
|
|
@@ -688,12 +829,24 @@ const canReturnWhenTrueInLogicalExpression = (op) => {
|
|
|
688
829
|
return op === ts_morph.ts.SyntaxKind.BarBarToken || op === ts_morph.ts.SyntaxKind.QuestionQuestionToken;
|
|
689
830
|
};
|
|
690
831
|
function maybeBoxNode(node, stack, ctx, matchProp) {
|
|
832
|
+
const cached = getCached(node);
|
|
833
|
+
if (cached) {
|
|
834
|
+
const replayed = replayDependencyCache(cached, ctx);
|
|
835
|
+
if (replayed.hit) return replayed.value;
|
|
836
|
+
}
|
|
837
|
+
const capture = beginDependencyCapture(ctx);
|
|
838
|
+
try {
|
|
839
|
+
return maybeBoxNodeUncached(node, stack, ctx, matchProp, capture.entry);
|
|
840
|
+
} finally {
|
|
841
|
+
capture.end();
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function maybeBoxNodeUncached(node, stack, ctx, matchProp, dependencyEntry) {
|
|
691
845
|
const cache = (value) => {
|
|
692
|
-
cacheMap$1.set(node, value);
|
|
846
|
+
cacheMap$1.set(node, dependencyEntry(value));
|
|
693
847
|
hasCachedEntries = true;
|
|
694
848
|
return value;
|
|
695
849
|
};
|
|
696
|
-
if (isCached(node)) return getCached(node);
|
|
697
850
|
if (ts_morph.Node.isStringLiteral(node) || ts_morph.Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
698
851
|
const value = trimWhitespace(node.getLiteralValue());
|
|
699
852
|
return cache(box.literal(value, node, stack));
|
|
@@ -1127,21 +1280,19 @@ const resolveExportedName = (name, exportDeclaration) => {
|
|
|
1127
1280
|
* which costs a minimum of around 90ms (and scales up with the file/project, could be hundreds of ms)
|
|
1128
1281
|
* @see https://github.com/dsherret/ts-morph/blob/42d811ed9a5177fc678a5bfec4923a2048124fe0/packages/ts-morph/src/compiler/ast/module/ExportDeclaration.ts#L160
|
|
1129
1282
|
*/
|
|
1130
|
-
const getModuleSpecifierSourceFile = (declaration) => {
|
|
1131
|
-
const project = declaration.getProject();
|
|
1283
|
+
const getModuleSpecifierSourceFile = (declaration, ctx) => {
|
|
1132
1284
|
const moduleName = declaration.getModuleSpecifierValue();
|
|
1133
1285
|
if (!moduleName) return;
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
return project.addSourceFileAtPath(resolved.resolvedModule.resolvedFileName);
|
|
1286
|
+
const sourceFile = ctx.resolveModule?.(moduleName, declaration.getSourceFile());
|
|
1287
|
+
if (sourceFile) recordModuleDependency(ctx, sourceFile.getFilePath());
|
|
1288
|
+
return sourceFile;
|
|
1138
1289
|
};
|
|
1139
1290
|
function resolveVarDeclarationFromExportWithName(symbolName, sourceFile, stack, ctx, visited) {
|
|
1140
1291
|
for (const exportDeclaration of sourceFile.getExportDeclarations()) {
|
|
1141
1292
|
const exportStack = [exportDeclaration];
|
|
1142
1293
|
const sourceName = resolveExportedName(symbolName, exportDeclaration);
|
|
1143
1294
|
if (sourceName === void 0) continue;
|
|
1144
|
-
const maybeFile = getModuleSpecifierSourceFile(exportDeclaration);
|
|
1295
|
+
const maybeFile = getModuleSpecifierSourceFile(exportDeclaration, ctx);
|
|
1145
1296
|
if (!maybeFile) {
|
|
1146
1297
|
const localVar = sourceFile.getVariableDeclaration(sourceName);
|
|
1147
1298
|
if (localVar) {
|
package/dist/index.d.cts
CHANGED
|
@@ -184,7 +184,18 @@ interface ComponentMatchers {
|
|
|
184
184
|
matchTag: (element: MatchTagArgs) => boolean;
|
|
185
185
|
matchProp: (prop: Pick<MatchTagArgs, 'tagName' | 'tagNode'> & MatchPropArgs) => boolean;
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Place a module specifier inside the source graph owned by the caller.
|
|
189
|
+
*
|
|
190
|
+
* @internal Bamboo's parser supplies its Project resolver. The extractor deliberately has
|
|
191
|
+
* no filesystem fallback: a cross-file read that bypasses the owner cannot be invalidated or
|
|
192
|
+
* represented in the owner's dependency graph.
|
|
193
|
+
*/
|
|
194
|
+
type ResolveModule = (specifier: string, from: SourceFile) => SourceFile | undefined;
|
|
187
195
|
interface BoxContext {
|
|
196
|
+
resolveModule?: ResolveModule;
|
|
197
|
+
/** @internal Receives local source paths crossed by this exact extraction context. */
|
|
198
|
+
recordDependency?: (filePath: string) => void;
|
|
188
199
|
getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
|
|
189
200
|
canEval?: (node: Expression, stack: Node[]) => boolean;
|
|
190
201
|
tokens?: {
|
|
@@ -324,4 +335,4 @@ interface Unboxed {
|
|
|
324
335
|
}
|
|
325
336
|
declare const unbox: (node: BoxNodeType, ctx?: Pick<UnboxContext, "cache">) => Unboxed;
|
|
326
337
|
//#endregion
|
|
327
|
-
export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
|
|
338
|
+
export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type ResolveModule, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
|
package/dist/index.d.mts
CHANGED
|
@@ -184,7 +184,18 @@ interface ComponentMatchers {
|
|
|
184
184
|
matchTag: (element: MatchTagArgs) => boolean;
|
|
185
185
|
matchProp: (prop: Pick<MatchTagArgs, 'tagName' | 'tagNode'> & MatchPropArgs) => boolean;
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Place a module specifier inside the source graph owned by the caller.
|
|
189
|
+
*
|
|
190
|
+
* @internal Bamboo's parser supplies its Project resolver. The extractor deliberately has
|
|
191
|
+
* no filesystem fallback: a cross-file read that bypasses the owner cannot be invalidated or
|
|
192
|
+
* represented in the owner's dependency graph.
|
|
193
|
+
*/
|
|
194
|
+
type ResolveModule = (specifier: string, from: SourceFile) => SourceFile | undefined;
|
|
187
195
|
interface BoxContext {
|
|
196
|
+
resolveModule?: ResolveModule;
|
|
197
|
+
/** @internal Receives local source paths crossed by this exact extraction context. */
|
|
198
|
+
recordDependency?: (filePath: string) => void;
|
|
188
199
|
getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
|
|
189
200
|
canEval?: (node: Expression, stack: Node[]) => boolean;
|
|
190
201
|
tokens?: {
|
|
@@ -324,4 +335,4 @@ interface Unboxed {
|
|
|
324
335
|
}
|
|
325
336
|
declare const unbox: (node: BoxNodeType, ctx?: Pick<UnboxContext, "cache">) => Unboxed;
|
|
326
337
|
//#endregion
|
|
327
|
-
export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
|
|
338
|
+
export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type ResolveModule, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
|
package/dist/index.mjs
CHANGED
|
@@ -305,6 +305,65 @@ const box = {
|
|
|
305
305
|
}
|
|
306
306
|
};
|
|
307
307
|
//#endregion
|
|
308
|
+
//#region src/dependency-cache.ts
|
|
309
|
+
const EMPTY_DEPENDENCIES = [];
|
|
310
|
+
/** One synchronous extraction context owns one nested capture stack. */
|
|
311
|
+
const trackers = /* @__PURE__ */ new WeakMap();
|
|
312
|
+
/**
|
|
313
|
+
* Attribute one resolved local module to every cache computation currently enclosing it.
|
|
314
|
+
*
|
|
315
|
+
* Paths, rather than SourceFile objects, are the replay payload. A source replacement may
|
|
316
|
+
* retire every node in a file, while its stable path is exactly the fact a fresh ParserResult
|
|
317
|
+
* needs. The caller-facing callback stays outside the cache and therefore belongs to the
|
|
318
|
+
* current query/environment rather than whichever parse populated the entry.
|
|
319
|
+
*/
|
|
320
|
+
const recordModuleDependency = (ctx, filePath) => {
|
|
321
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
322
|
+
const tracker = trackers.get(ctx);
|
|
323
|
+
if (tracker) for (const frame of tracker.frames) {
|
|
324
|
+
frame.dependencies ??= /* @__PURE__ */ new Set();
|
|
325
|
+
frame.dependencies.add(normalized);
|
|
326
|
+
}
|
|
327
|
+
ctx.recordDependency?.(normalized);
|
|
328
|
+
};
|
|
329
|
+
/** Begin one nested cache computation without allocating a Set on dependency-free paths. */
|
|
330
|
+
const beginDependencyCapture = (ctx) => {
|
|
331
|
+
let tracker = trackers.get(ctx);
|
|
332
|
+
if (!tracker) {
|
|
333
|
+
tracker = { frames: [] };
|
|
334
|
+
trackers.set(ctx, tracker);
|
|
335
|
+
}
|
|
336
|
+
const frame = {};
|
|
337
|
+
tracker.frames.push(frame);
|
|
338
|
+
return {
|
|
339
|
+
entry(value) {
|
|
340
|
+
return {
|
|
341
|
+
dependencies: frame.dependencies ? [...frame.dependencies].sort() : EMPTY_DEPENDENCIES,
|
|
342
|
+
resolveModule: ctx.resolveModule,
|
|
343
|
+
value
|
|
344
|
+
};
|
|
345
|
+
},
|
|
346
|
+
end() {
|
|
347
|
+
tracker.frames.pop();
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* Replay a hit only inside the resolver scope which computed it.
|
|
353
|
+
*
|
|
354
|
+
* The same ts-morph node can be inspected under two extractor contexts with different module
|
|
355
|
+
* placement rules. Returning the first context's value or dependency paths to the second would
|
|
356
|
+
* be cross-Project leakage, so a scope mismatch is a cache miss and gets overwritten normally.
|
|
357
|
+
*/
|
|
358
|
+
const replayDependencyCache = (entry, ctx) => {
|
|
359
|
+
if (entry.resolveModule !== ctx.resolveModule) return { hit: false };
|
|
360
|
+
for (const dependency of entry.dependencies) recordModuleDependency(ctx, dependency);
|
|
361
|
+
return {
|
|
362
|
+
hit: true,
|
|
363
|
+
value: entry.value
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
//#endregion
|
|
308
367
|
//#region src/resolve-imported-value.ts
|
|
309
368
|
/**
|
|
310
369
|
* The values an expression borrows from other modules, resolved by reading the imports.
|
|
@@ -321,9 +380,9 @@ const box = {
|
|
|
321
380
|
* rather than with the number of style calls — a large project reported a 5.4x extraction
|
|
322
381
|
* slowdown and an OOM in CI.
|
|
323
382
|
*
|
|
324
|
-
* Following an import is two cheap steps
|
|
325
|
-
*
|
|
326
|
-
*
|
|
383
|
+
* Following an import is two cheap steps: ask the caller's source-graph resolver to place the
|
|
384
|
+
* specifier, then read that file's exported declaration. Crossing the import boundary is the
|
|
385
|
+
* only thing the checker was doing here.
|
|
327
386
|
*
|
|
328
387
|
* The evaluator resolves everything *within* a module by walking scopes, so a helper that
|
|
329
388
|
* refers to another binding in its own file needs nothing from us.
|
|
@@ -331,7 +390,14 @@ const box = {
|
|
|
331
390
|
/** How far a chain of helpers importing helpers is followed. */
|
|
332
391
|
const MAX_DEPTH = 4;
|
|
333
392
|
/** Evaluated once per declaration, however many call sites reach it. */
|
|
334
|
-
|
|
393
|
+
let evaluatedValues = /* @__PURE__ */ new WeakMap();
|
|
394
|
+
let hasEvaluatedValues = false;
|
|
395
|
+
/** @internal Drop imported declarations whose closure may include an edited module. */
|
|
396
|
+
const clearImportedValueCache = () => {
|
|
397
|
+
if (!hasEvaluatedValues) return;
|
|
398
|
+
evaluatedValues = /* @__PURE__ */ new WeakMap();
|
|
399
|
+
hasEvaluatedValues = false;
|
|
400
|
+
};
|
|
335
401
|
/**
|
|
336
402
|
* Local name to the name and declaration it was imported from.
|
|
337
403
|
*
|
|
@@ -361,17 +427,28 @@ const importBindingsFor = (sourceFile) => {
|
|
|
361
427
|
};
|
|
362
428
|
/** What an imported name refers to, or nothing if it cannot be reached safely. */
|
|
363
429
|
const valueForBinding = (binding, ctx, stack, depth, evaluateExpression) => {
|
|
364
|
-
const sourceFile = getModuleSpecifierSourceFile(binding.declaration);
|
|
430
|
+
const sourceFile = getModuleSpecifierSourceFile(binding.declaration, ctx);
|
|
365
431
|
if (!sourceFile) return;
|
|
366
432
|
if (sourceFile.isInNodeModules()) return;
|
|
367
433
|
const declaration = getExportedVarDeclarationWithName(binding.exportedName, sourceFile, stack, ctx);
|
|
368
434
|
if (!declaration) return;
|
|
369
|
-
|
|
435
|
+
const cached = evaluatedValues.get(declaration);
|
|
436
|
+
if (cached) {
|
|
437
|
+
const replayed = replayDependencyCache(cached, ctx);
|
|
438
|
+
if (replayed.hit) return { value: replayed.value };
|
|
439
|
+
}
|
|
370
440
|
const initializer = declaration.getInitializer();
|
|
371
441
|
if (!initializer) return;
|
|
372
|
-
const
|
|
442
|
+
const capture = beginDependencyCapture(ctx);
|
|
443
|
+
let value;
|
|
444
|
+
try {
|
|
445
|
+
value = evaluateExpression(initializer, stack, ctx, depth + 1);
|
|
446
|
+
} finally {
|
|
447
|
+
capture.end();
|
|
448
|
+
}
|
|
373
449
|
if (value === void 0) return;
|
|
374
|
-
evaluatedValues.set(declaration, value);
|
|
450
|
+
evaluatedValues.set(declaration, capture.entry(value));
|
|
451
|
+
hasEvaluatedValues = true;
|
|
375
452
|
return { value };
|
|
376
453
|
};
|
|
377
454
|
/**
|
|
@@ -384,27 +461,34 @@ const valueForBinding = (binding, ctx, stack, depth, evaluateExpression) => {
|
|
|
384
461
|
*/
|
|
385
462
|
const importedEnvironmentFor = (node, ctx, stack, depth, evaluateExpression) => {
|
|
386
463
|
if (depth >= MAX_DEPTH) return;
|
|
387
|
-
const calls = node.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
388
|
-
if (Node.isCallExpression(node)) calls.unshift(node);
|
|
389
|
-
if (!calls.length) return;
|
|
390
464
|
const bindings = importBindingsFor(node.getSourceFile());
|
|
391
465
|
if (!bindings.size) return;
|
|
466
|
+
const references = node.getDescendantsOfKind(SyntaxKind.Identifier);
|
|
467
|
+
if (Node.isIdentifier(node)) references.unshift(node);
|
|
468
|
+
if (!references.length) return;
|
|
392
469
|
let environment;
|
|
393
|
-
for (const
|
|
394
|
-
const
|
|
395
|
-
const binding = bindings.get(
|
|
396
|
-
if (!binding || environment?.[
|
|
470
|
+
for (const reference of references) {
|
|
471
|
+
const name = reference.getText();
|
|
472
|
+
const binding = bindings.get(name);
|
|
473
|
+
if (!binding || environment?.[name] !== void 0) continue;
|
|
397
474
|
const resolved = valueForBinding(binding, ctx, stack, depth, evaluateExpression);
|
|
398
475
|
if (!resolved) continue;
|
|
399
476
|
environment ??= {};
|
|
400
|
-
environment[
|
|
477
|
+
environment[name] = resolved.value;
|
|
401
478
|
}
|
|
402
479
|
return environment;
|
|
403
480
|
};
|
|
404
481
|
//#endregion
|
|
405
482
|
//#region src/evaluate-node.ts
|
|
406
483
|
const TsEvalError = Symbol("EvalError");
|
|
407
|
-
|
|
484
|
+
let cacheMap$2 = /* @__PURE__ */ new WeakMap();
|
|
485
|
+
let hasCachedEntries$1 = false;
|
|
486
|
+
/** @internal Drop values whose expression may close over an edited source file. */
|
|
487
|
+
const clearEvaluateNodeCache = () => {
|
|
488
|
+
if (!hasCachedEntries$1) return;
|
|
489
|
+
cacheMap$2 = /* @__PURE__ */ new WeakMap();
|
|
490
|
+
hasCachedEntries$1 = false;
|
|
491
|
+
};
|
|
408
492
|
/** @see https://github.com/wessberg/ts-evaluator#setting-up-policies */
|
|
409
493
|
const POLICY = {
|
|
410
494
|
console: false,
|
|
@@ -435,13 +519,39 @@ const POLICY = {
|
|
|
435
519
|
const evaluateNode = (node, stack, ctx, depth = 0) => {
|
|
436
520
|
if (ctx.flags?.skipEvaluate) return;
|
|
437
521
|
if (ctx.canEval && !ctx.canEval?.(node, stack)) return;
|
|
438
|
-
|
|
439
|
-
|
|
522
|
+
const cached = depth === 0 ? cacheMap$2.get(node) : void 0;
|
|
523
|
+
if (cached) {
|
|
524
|
+
const replayed = replayDependencyCache(cached, ctx);
|
|
525
|
+
if (replayed.hit) return replayed.value;
|
|
526
|
+
}
|
|
527
|
+
const capture = depth === 0 ? beginDependencyCapture(ctx) : void 0;
|
|
528
|
+
try {
|
|
529
|
+
return evaluateNodeUncached(node, stack, ctx, depth, capture?.entry);
|
|
530
|
+
} finally {
|
|
531
|
+
capture?.end();
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
const evaluateNodeUncached = (node, stack, ctx, depth, dependencyEntry) => {
|
|
535
|
+
let options = {
|
|
440
536
|
policy: { ...POLICY },
|
|
441
537
|
...ctx.getEvaluateOptions?.(node, stack),
|
|
442
538
|
node: node.compilerNode,
|
|
443
539
|
typescript: ts
|
|
444
540
|
};
|
|
541
|
+
let imported = depth > 0 ? importedEnvironmentFor(node, ctx, stack, depth, safeEvaluateNode) : void 0;
|
|
542
|
+
if (imported) {
|
|
543
|
+
const environment = options.environment;
|
|
544
|
+
options = {
|
|
545
|
+
...options,
|
|
546
|
+
environment: {
|
|
547
|
+
...environment,
|
|
548
|
+
extra: {
|
|
549
|
+
...environment?.extra,
|
|
550
|
+
...imported
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
445
555
|
let result = evaluate(options);
|
|
446
556
|
/**
|
|
447
557
|
* Retried only on failure, with whatever this expression imports put in scope.
|
|
@@ -453,17 +563,26 @@ const evaluateNode = (node, stack, ctx, depth = 0) => {
|
|
|
453
563
|
* the path of every build.
|
|
454
564
|
*/
|
|
455
565
|
if (!result.success) {
|
|
456
|
-
|
|
457
|
-
if (imported)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
...options
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
566
|
+
imported ??= importedEnvironmentFor(node, ctx, stack, depth, safeEvaluateNode);
|
|
567
|
+
if (imported) {
|
|
568
|
+
const environment = options.environment;
|
|
569
|
+
result = evaluate({
|
|
570
|
+
...options,
|
|
571
|
+
environment: {
|
|
572
|
+
...environment,
|
|
573
|
+
extra: {
|
|
574
|
+
...environment?.extra,
|
|
575
|
+
...imported
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
464
580
|
}
|
|
465
581
|
const expr = result.success ? result.value : TsEvalError;
|
|
466
|
-
if (depth === 0)
|
|
582
|
+
if (depth === 0) {
|
|
583
|
+
cacheMap$2.set(node, dependencyEntry(expr));
|
|
584
|
+
hasCachedEntries$1 = true;
|
|
585
|
+
}
|
|
467
586
|
return expr;
|
|
468
587
|
};
|
|
469
588
|
const safeEvaluateNode = (node, stack, ctx, depth = 0) => {
|
|
@@ -486,7 +605,7 @@ function getDeclarationFor(node, stack, ctx) {
|
|
|
486
605
|
declaration = parent;
|
|
487
606
|
} else if (Node.isImportSpecifier(parent) && (parent.getNameNode() == node || parent.getAliasNode() == node)) {
|
|
488
607
|
if (ctx.flags?.skipTraverseFiles) return;
|
|
489
|
-
const sourceFile = getModuleSpecifierSourceFile(parent.getImportDeclaration());
|
|
608
|
+
const sourceFile = getModuleSpecifierSourceFile(parent.getImportDeclaration(), ctx);
|
|
490
609
|
if (sourceFile) {
|
|
491
610
|
const exportStack = [parent, sourceFile];
|
|
492
611
|
const maybeVar = getExportedVarDeclarationWithName(parent.getNameNode().getText(), sourceFile, exportStack, ctx);
|
|
@@ -504,53 +623,74 @@ const getInnermostScope = (from) => {
|
|
|
504
623
|
while (scope && !isScope(scope)) scope = scope.getParent();
|
|
505
624
|
return scope;
|
|
506
625
|
};
|
|
626
|
+
/**
|
|
627
|
+
* Keyed on the compiler node, never the ts-morph wrapper.
|
|
628
|
+
*
|
|
629
|
+
* `Project.replaceWithText` — which is how a re-parse installs a source transform — keeps the
|
|
630
|
+
* wrapper identity and swaps the compiler node underneath it. A wrapper-keyed cache would
|
|
631
|
+
* therefore answer a rebuild from the previous revision of the file, which is a wrong
|
|
632
|
+
* stylesheet rather than a slow one. Keying on the compiler node makes the cache
|
|
633
|
+
* self-invalidating: ts-morph reuses a compiler node exactly when its subtree did not change.
|
|
634
|
+
*/
|
|
635
|
+
const declarationIndexes = /* @__PURE__ */ new WeakMap();
|
|
636
|
+
const isDeclarationName = (node, parent) => {
|
|
637
|
+
if (Node.isVariableDeclaration(parent) || Node.isParameterDeclaration(parent) || Node.isFunctionDeclaration(parent) || Node.isEnumDeclaration(parent) || Node.isBindingElement(parent)) return parent.getNameNode() == node;
|
|
638
|
+
return Node.isImportSpecifier(parent) && (parent.getNameNode() == node || parent.getAliasNode() == node);
|
|
639
|
+
};
|
|
640
|
+
/**
|
|
641
|
+
* Walk a scope once, so that every later lookup inside it is a map read.
|
|
642
|
+
*
|
|
643
|
+
* This used to be a `forEachDescendant` per identifier, widening to each enclosing scope and
|
|
644
|
+
* re-walking from scratch, calling `getText()` on every identifier it passed. A module whose
|
|
645
|
+
* declarations are referenced n times paid n full traversals of itself, and traversal is where
|
|
646
|
+
* ts-morph charges for wrapping each node — it measured ~10% of extraction on its own.
|
|
647
|
+
*/
|
|
648
|
+
const declarationIndexFor = (scope) => {
|
|
649
|
+
const cached = declarationIndexes.get(scope.compilerNode);
|
|
650
|
+
if (cached) return cached;
|
|
651
|
+
const index = /* @__PURE__ */ new Map();
|
|
652
|
+
scope.forEachDescendant((node) => {
|
|
653
|
+
if (!Node.isIdentifier(node)) return;
|
|
654
|
+
const parent = node.getParent();
|
|
655
|
+
if (!parent || !isDeclarationName(node, parent)) return;
|
|
656
|
+
const name = node.getText();
|
|
657
|
+
const declared = index.get(name);
|
|
658
|
+
if (declared) declared.push(node);
|
|
659
|
+
else index.set(name, [node]);
|
|
660
|
+
});
|
|
661
|
+
declarationIndexes.set(scope.compilerNode, index);
|
|
662
|
+
return index;
|
|
663
|
+
};
|
|
507
664
|
function findIdentifierValueDeclaration(identifier, stack, ctx, visitedsWithStack = /* @__PURE__ */ new Map()) {
|
|
508
665
|
let scope = identifier;
|
|
509
|
-
let foundNode;
|
|
510
|
-
let isUnresolvable = false;
|
|
511
666
|
let count = 0;
|
|
512
667
|
const innerStack = [];
|
|
668
|
+
const refName = identifier.getText();
|
|
513
669
|
do {
|
|
514
670
|
scope = getInnermostScope(scope);
|
|
515
671
|
count++;
|
|
516
672
|
if (!scope) return;
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
if (visitedsWithStack.has(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
673
|
+
for (const candidate of declarationIndexFor(scope).get(refName) ?? []) {
|
|
674
|
+
if (candidate == identifier) continue;
|
|
675
|
+
if (visitedsWithStack.has(candidate)) continue;
|
|
676
|
+
visitedsWithStack.set(candidate, innerStack);
|
|
677
|
+
const declarationStack = [candidate];
|
|
678
|
+
const maybeDeclaration = getDeclarationFor(candidate, declarationStack, ctx);
|
|
679
|
+
if (!maybeDeclaration) continue;
|
|
680
|
+
if (Node.isParameterDeclaration(maybeDeclaration)) {
|
|
681
|
+
const initializer = maybeDeclaration.getInitializer();
|
|
682
|
+
const typeNode = maybeDeclaration.getTypeNode();
|
|
683
|
+
if (initializer) innerStack.push(...declarationStack.concat(initializer));
|
|
684
|
+
else if (typeNode && Node.isTypeLiteral(typeNode)) innerStack.push(...declarationStack.concat(typeNode));
|
|
685
|
+
else return;
|
|
686
|
+
stack.push(...innerStack);
|
|
687
|
+
return maybeDeclaration;
|
|
523
688
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const declarationStack = [node];
|
|
528
|
-
const maybeDeclaration = getDeclarationFor(node, declarationStack, ctx);
|
|
529
|
-
if (maybeDeclaration) {
|
|
530
|
-
if (Node.isParameterDeclaration(maybeDeclaration)) {
|
|
531
|
-
const initializer = maybeDeclaration.getInitializer();
|
|
532
|
-
const typeNode = maybeDeclaration.getTypeNode();
|
|
533
|
-
if (initializer) {
|
|
534
|
-
innerStack.push(...declarationStack.concat(initializer));
|
|
535
|
-
foundNode = maybeDeclaration;
|
|
536
|
-
} else if (typeNode && Node.isTypeLiteral(typeNode)) {
|
|
537
|
-
innerStack.push(...declarationStack.concat(typeNode));
|
|
538
|
-
foundNode = maybeDeclaration;
|
|
539
|
-
} else isUnresolvable = true;
|
|
540
|
-
traversal.stop();
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
innerStack.push(...declarationStack);
|
|
544
|
-
foundNode = maybeDeclaration;
|
|
545
|
-
traversal.stop();
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
if (foundNode || isUnresolvable) {
|
|
550
|
-
if (foundNode) stack.push(...innerStack);
|
|
551
|
-
return foundNode;
|
|
689
|
+
innerStack.push(...declarationStack);
|
|
690
|
+
stack.push(...innerStack);
|
|
691
|
+
return maybeDeclaration;
|
|
552
692
|
}
|
|
553
|
-
} while (scope && !Node.isSourceFile(scope) &&
|
|
693
|
+
} while (scope && !Node.isSourceFile(scope) && count < 100);
|
|
554
694
|
}
|
|
555
695
|
//#endregion
|
|
556
696
|
//#region src/get-property-name.ts
|
|
@@ -664,7 +804,6 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
664
804
|
//#endregion
|
|
665
805
|
//#region src/maybe-box-node.ts
|
|
666
806
|
let cacheMap$1 = /* @__PURE__ */ new WeakMap();
|
|
667
|
-
const isCached = (node) => cacheMap$1.has(node);
|
|
668
807
|
const getCached = (node) => cacheMap$1.get(node);
|
|
669
808
|
/**
|
|
670
809
|
* Drops every memoized resolution.
|
|
@@ -675,6 +814,8 @@ const getCached = (node) => cacheMap$1.get(node);
|
|
|
675
814
|
* otherwise keep serving the value read before the edit.
|
|
676
815
|
*/
|
|
677
816
|
const clearBoxNodeCache = () => {
|
|
817
|
+
clearEvaluateNodeCache();
|
|
818
|
+
clearImportedValueCache();
|
|
678
819
|
if (!hasCachedEntries) return;
|
|
679
820
|
cacheMap$1 = /* @__PURE__ */ new WeakMap();
|
|
680
821
|
hasCachedEntries = false;
|
|
@@ -687,12 +828,24 @@ const canReturnWhenTrueInLogicalExpression = (op) => {
|
|
|
687
828
|
return op === ts.SyntaxKind.BarBarToken || op === ts.SyntaxKind.QuestionQuestionToken;
|
|
688
829
|
};
|
|
689
830
|
function maybeBoxNode(node, stack, ctx, matchProp) {
|
|
831
|
+
const cached = getCached(node);
|
|
832
|
+
if (cached) {
|
|
833
|
+
const replayed = replayDependencyCache(cached, ctx);
|
|
834
|
+
if (replayed.hit) return replayed.value;
|
|
835
|
+
}
|
|
836
|
+
const capture = beginDependencyCapture(ctx);
|
|
837
|
+
try {
|
|
838
|
+
return maybeBoxNodeUncached(node, stack, ctx, matchProp, capture.entry);
|
|
839
|
+
} finally {
|
|
840
|
+
capture.end();
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function maybeBoxNodeUncached(node, stack, ctx, matchProp, dependencyEntry) {
|
|
690
844
|
const cache = (value) => {
|
|
691
|
-
cacheMap$1.set(node, value);
|
|
845
|
+
cacheMap$1.set(node, dependencyEntry(value));
|
|
692
846
|
hasCachedEntries = true;
|
|
693
847
|
return value;
|
|
694
848
|
};
|
|
695
|
-
if (isCached(node)) return getCached(node);
|
|
696
849
|
if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
697
850
|
const value = trimWhitespace(node.getLiteralValue());
|
|
698
851
|
return cache(box.literal(value, node, stack));
|
|
@@ -1126,21 +1279,19 @@ const resolveExportedName = (name, exportDeclaration) => {
|
|
|
1126
1279
|
* which costs a minimum of around 90ms (and scales up with the file/project, could be hundreds of ms)
|
|
1127
1280
|
* @see https://github.com/dsherret/ts-morph/blob/42d811ed9a5177fc678a5bfec4923a2048124fe0/packages/ts-morph/src/compiler/ast/module/ExportDeclaration.ts#L160
|
|
1128
1281
|
*/
|
|
1129
|
-
const getModuleSpecifierSourceFile = (declaration) => {
|
|
1130
|
-
const project = declaration.getProject();
|
|
1282
|
+
const getModuleSpecifierSourceFile = (declaration, ctx) => {
|
|
1131
1283
|
const moduleName = declaration.getModuleSpecifierValue();
|
|
1132
1284
|
if (!moduleName) return;
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
return project.addSourceFileAtPath(resolved.resolvedModule.resolvedFileName);
|
|
1285
|
+
const sourceFile = ctx.resolveModule?.(moduleName, declaration.getSourceFile());
|
|
1286
|
+
if (sourceFile) recordModuleDependency(ctx, sourceFile.getFilePath());
|
|
1287
|
+
return sourceFile;
|
|
1137
1288
|
};
|
|
1138
1289
|
function resolveVarDeclarationFromExportWithName(symbolName, sourceFile, stack, ctx, visited) {
|
|
1139
1290
|
for (const exportDeclaration of sourceFile.getExportDeclarations()) {
|
|
1140
1291
|
const exportStack = [exportDeclaration];
|
|
1141
1292
|
const sourceName = resolveExportedName(symbolName, exportDeclaration);
|
|
1142
1293
|
if (sourceName === void 0) continue;
|
|
1143
|
-
const maybeFile = getModuleSpecifierSourceFile(exportDeclaration);
|
|
1294
|
+
const maybeFile = getModuleSpecifierSourceFile(exportDeclaration, ctx);
|
|
1144
1295
|
if (!maybeFile) {
|
|
1145
1296
|
const localVar = sourceFile.getVariableDeclaration(sourceName);
|
|
1146
1297
|
if (localVar) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/extractor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.1",
|
|
4
4
|
"description": "The css extractor for css bamboo",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"ts-evaluator": "2.0.0",
|
|
37
37
|
"ts-morph": "28.0.0",
|
|
38
|
-
"@bamboocss/shared": "1.
|
|
38
|
+
"@bamboocss/shared": "1.46.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsdown src/index.ts --format=cjs,esm --shims --dts",
|