@bamboocss/parser 1.26.0 → 1.28.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 +257 -12
- package/dist/index.d.cts +36 -2
- package/dist/index.d.mts +36 -2
- package/dist/index.mjs +257 -12
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -65,7 +65,7 @@ function classifyProject(ctx, resultMap) {
|
|
|
65
65
|
const { item, kind, filepath, localMaps } = opts;
|
|
66
66
|
if (!item.box || _bamboocss_extractor.box.isUnresolvable(item.box)) return;
|
|
67
67
|
if (!item.data) return;
|
|
68
|
-
if (item.type === "cva-call") return;
|
|
68
|
+
if (item.type === "cva-call" || item.type === "tokenVar") return;
|
|
69
69
|
const componentReportItem = {
|
|
70
70
|
componentIndex: String(componentIndex++),
|
|
71
71
|
componentName: item.name,
|
|
@@ -410,6 +410,11 @@ function createReportMaps() {
|
|
|
410
410
|
}
|
|
411
411
|
//#endregion
|
|
412
412
|
//#region src/get-module-specifier-value.ts
|
|
413
|
+
/**
|
|
414
|
+
* Both declaration kinds carry a specifier, and `export { x } from './m'` is how a barrel
|
|
415
|
+
* forwards a recipe — so this reads either. An `export { x }` with no `from` returns
|
|
416
|
+
* undefined, which is the same answer the throwing case gives.
|
|
417
|
+
*/
|
|
413
418
|
const getModuleSpecifierValue = (node) => {
|
|
414
419
|
try {
|
|
415
420
|
return node.getModuleSpecifierValue();
|
|
@@ -418,6 +423,194 @@ const getModuleSpecifierValue = (node) => {
|
|
|
418
423
|
}
|
|
419
424
|
};
|
|
420
425
|
//#endregion
|
|
426
|
+
//#region src/imported-recipes.ts
|
|
427
|
+
/**
|
|
428
|
+
* A module's exported recipes, memoized.
|
|
429
|
+
*
|
|
430
|
+
* Per *target*, so a barrel imported by two hundred files is walked once. Cleared alongside
|
|
431
|
+
* the box-node cache, which is the same invalidation this needs: both memoize a conclusion
|
|
432
|
+
* drawn from another file's contents.
|
|
433
|
+
*/
|
|
434
|
+
const exportedRecipes = /* @__PURE__ */ new Map();
|
|
435
|
+
const clearImportedRecipeCache = () => {
|
|
436
|
+
exportedRecipes.clear();
|
|
437
|
+
};
|
|
438
|
+
/** The names a file bound `cva`/`sva` to, or empty when it imports neither. */
|
|
439
|
+
const recipeFactoryAliases = (sourceFile, imports) => {
|
|
440
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
441
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
442
|
+
if (declaration.isTypeOnly()) continue;
|
|
443
|
+
const mod = getModuleSpecifierValue(declaration);
|
|
444
|
+
if (!mod) continue;
|
|
445
|
+
for (const specifier of declaration.getNamedImports()) {
|
|
446
|
+
if (specifier.isTypeOnly()) continue;
|
|
447
|
+
const name = specifier.getNameNode().getText();
|
|
448
|
+
if (name !== "cva" && name !== "sva") continue;
|
|
449
|
+
const alias = specifier.getAliasNode()?.getText() || name;
|
|
450
|
+
if (!imports.match({
|
|
451
|
+
name,
|
|
452
|
+
alias,
|
|
453
|
+
mod,
|
|
454
|
+
kind: "named"
|
|
455
|
+
})) continue;
|
|
456
|
+
aliases.add(alias);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return aliases;
|
|
460
|
+
};
|
|
461
|
+
/** Recipes this module declares itself, by the name it declared them under. */
|
|
462
|
+
const declaredRecipes = (sourceFile, imports) => {
|
|
463
|
+
const declared = /* @__PURE__ */ new Map();
|
|
464
|
+
const exported = /* @__PURE__ */ new Set();
|
|
465
|
+
const factories = recipeFactoryAliases(sourceFile, imports);
|
|
466
|
+
if (factories.size === 0) return {
|
|
467
|
+
declared,
|
|
468
|
+
exported
|
|
469
|
+
};
|
|
470
|
+
const filePath = sourceFile.getFilePath();
|
|
471
|
+
for (const statement of sourceFile.compilerNode.statements) {
|
|
472
|
+
if (!ts_morph.ts.isVariableStatement(statement)) continue;
|
|
473
|
+
if (!(statement.declarationList.flags & ts_morph.ts.NodeFlags.Const)) continue;
|
|
474
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
475
|
+
const initializer = declaration.initializer;
|
|
476
|
+
if (!initializer || !ts_morph.ts.isCallExpression(initializer)) continue;
|
|
477
|
+
const callee = initializer.expression;
|
|
478
|
+
if (!ts_morph.ts.isIdentifier(callee) || !ts_morph.ts.isIdentifier(declaration.name)) continue;
|
|
479
|
+
if (!factories.has(callee.text)) continue;
|
|
480
|
+
declared.set(declaration.name.text, {
|
|
481
|
+
filePath,
|
|
482
|
+
name: declaration.name.text
|
|
483
|
+
});
|
|
484
|
+
if (statement.modifiers?.some((modifier) => modifier.kind === ts_morph.ts.SyntaxKind.ExportKeyword)) exported.add(declaration.name.text);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
declared,
|
|
489
|
+
exported
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
/**
|
|
493
|
+
* Every name this module exports that is bound to a recipe, and where it was declared.
|
|
494
|
+
*
|
|
495
|
+
* Follows `export { x } from './m'`, `export * from './m'`, and `import { x } … export { x }`,
|
|
496
|
+
* because a barrel is how these are reached in practice. The origin is carried through each
|
|
497
|
+
* hop rather than recomputed, so a consumer learns the *declaring* module however many
|
|
498
|
+
* re-exports stand between them.
|
|
499
|
+
*
|
|
500
|
+
* A star export contributes only names nothing else exports, which is what the language does:
|
|
501
|
+
* an explicit export shadows one arriving through `export *`. Resolving that the other way
|
|
502
|
+
* folded a call against a config it would never have run.
|
|
503
|
+
*/
|
|
504
|
+
const walkExports = (sourceFile, imports, resolveModule, seen) => {
|
|
505
|
+
const cached = exportedRecipes.get(sourceFile);
|
|
506
|
+
if (cached) return {
|
|
507
|
+
names: cached,
|
|
508
|
+
complete: true
|
|
509
|
+
};
|
|
510
|
+
if (seen.has(sourceFile)) return {
|
|
511
|
+
names: /* @__PURE__ */ new Map(),
|
|
512
|
+
complete: false
|
|
513
|
+
};
|
|
514
|
+
seen.add(sourceFile);
|
|
515
|
+
const names = /* @__PURE__ */ new Map();
|
|
516
|
+
/** Reachable under a local name — declarations, plus recipes this module imports. */
|
|
517
|
+
const local = /* @__PURE__ */ new Map();
|
|
518
|
+
/** Held back so an explicit export of the same name wins. */
|
|
519
|
+
const starred = /* @__PURE__ */ new Map();
|
|
520
|
+
/** Names two different star exports both carry, which resolve to neither. */
|
|
521
|
+
const ambiguous = /* @__PURE__ */ new Set();
|
|
522
|
+
let complete = true;
|
|
523
|
+
const { declared, exported } = declaredRecipes(sourceFile, imports);
|
|
524
|
+
for (const [name, origin] of declared) {
|
|
525
|
+
local.set(name, origin);
|
|
526
|
+
if (exported.has(name)) names.set(name, origin);
|
|
527
|
+
}
|
|
528
|
+
const imported = walkImports(sourceFile, imports, resolveModule, seen);
|
|
529
|
+
complete &&= imported.complete;
|
|
530
|
+
for (const [alias, origin] of imported.bindings) if (!local.has(alias)) local.set(alias, origin);
|
|
531
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
532
|
+
if (declaration.isTypeOnly()) continue;
|
|
533
|
+
const specifier = getModuleSpecifierValue(declaration);
|
|
534
|
+
const target = specifier ? resolveModule(specifier, sourceFile) : void 0;
|
|
535
|
+
if (specifier && !target) continue;
|
|
536
|
+
if (declaration.getNamespaceExport()) continue;
|
|
537
|
+
if (declaration.isNamespaceExport()) {
|
|
538
|
+
if (!target) continue;
|
|
539
|
+
const walk = walkExports(target, imports, resolveModule, seen);
|
|
540
|
+
complete &&= walk.complete;
|
|
541
|
+
for (const [name, origin] of walk.names) {
|
|
542
|
+
const existing = starred.get(name);
|
|
543
|
+
if (existing && (existing.filePath !== origin.filePath || existing.name !== origin.name)) {
|
|
544
|
+
ambiguous.add(name);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
starred.set(name, origin);
|
|
548
|
+
}
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
let source = local;
|
|
552
|
+
if (target) {
|
|
553
|
+
const walk = walkExports(target, imports, resolveModule, seen);
|
|
554
|
+
complete &&= walk.complete;
|
|
555
|
+
source = walk.names;
|
|
556
|
+
}
|
|
557
|
+
for (const exportSpecifier of declaration.getNamedExports()) {
|
|
558
|
+
if (exportSpecifier.isTypeOnly()) continue;
|
|
559
|
+
const name = exportSpecifier.getNameNode().getText();
|
|
560
|
+
const origin = source.get(name);
|
|
561
|
+
if (origin) names.set(exportSpecifier.getAliasNode()?.getText() || name, origin);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
for (const [name, origin] of starred) if (!names.has(name) && !ambiguous.has(name)) names.set(name, origin);
|
|
565
|
+
if (complete) exportedRecipes.set(sourceFile, names);
|
|
566
|
+
return {
|
|
567
|
+
names,
|
|
568
|
+
complete
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
/**
|
|
572
|
+
* Local names in this file bound, through an import, to a recipe declared elsewhere.
|
|
573
|
+
*
|
|
574
|
+
* Reports truncation for the same reason `walkExports` does: `export { x }` with no `from`
|
|
575
|
+
* resolves through here, so a walk cut short by a cycle can leave a name out — and caching
|
|
576
|
+
* that as the whole answer is what makes visibility depend on where the walk began.
|
|
577
|
+
*/
|
|
578
|
+
const walkImports = (sourceFile, imports, resolveModule, seen) => {
|
|
579
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
580
|
+
let complete = true;
|
|
581
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
582
|
+
if (declaration.isTypeOnly()) continue;
|
|
583
|
+
const named = declaration.getNamedImports();
|
|
584
|
+
if (named.length === 0) continue;
|
|
585
|
+
const specifier = getModuleSpecifierValue(declaration);
|
|
586
|
+
if (!specifier) continue;
|
|
587
|
+
const target = resolveModule(specifier, sourceFile);
|
|
588
|
+
if (!target || target === sourceFile) continue;
|
|
589
|
+
const walk = walkExports(target, imports, resolveModule, seen);
|
|
590
|
+
complete &&= walk.complete;
|
|
591
|
+
const { names } = walk;
|
|
592
|
+
if (names.size === 0) continue;
|
|
593
|
+
for (const importSpecifier of named) {
|
|
594
|
+
if (importSpecifier.isTypeOnly()) continue;
|
|
595
|
+
const origin = names.get(importSpecifier.getNameNode().getText());
|
|
596
|
+
if (!origin) continue;
|
|
597
|
+
bindings.set(importSpecifier.getAliasNode()?.getText() || importSpecifier.getNameNode().getText(), origin);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
bindings,
|
|
602
|
+
complete
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* Local names in this file bound, through an import, to a recipe declared elsewhere.
|
|
607
|
+
*
|
|
608
|
+
* Only specifiers the caller's resolver can place inside the project. A recipe outside the
|
|
609
|
+
* project's `include` is never parsed, so it has no rules in the stylesheet and could not be
|
|
610
|
+
* folded against even if the binding resolved.
|
|
611
|
+
*/
|
|
612
|
+
const importedRecipeBindings = (sourceFile, imports, resolveModule) => walkImports(sourceFile, imports, resolveModule, new Set([sourceFile])).bindings;
|
|
613
|
+
//#endregion
|
|
421
614
|
//#region src/get-import-declarations.ts
|
|
422
615
|
function getImportDeclarations(context, sourceFile) {
|
|
423
616
|
const { imports, tsOptions } = context;
|
|
@@ -769,8 +962,14 @@ var ParserResult = class {
|
|
|
769
962
|
const unresolved = findUnresolvedRecipeStyles(result);
|
|
770
963
|
if (unresolved.length) this.unresolved.push(...unresolved);
|
|
771
964
|
}
|
|
772
|
-
|
|
773
|
-
|
|
965
|
+
/**
|
|
966
|
+
* `kind` separates `token()` from `token.var()`, which resolve to the value and to the
|
|
967
|
+
* variable reference respectively. They share this set deliberately: everything that reads
|
|
968
|
+
* a result for the token *path* — `collectTokenReferences`, keeping a declaration alive
|
|
969
|
+
* through pruning — wants both, and only the fold cares which half was asked for.
|
|
970
|
+
*/
|
|
971
|
+
setToken(result, kind = "token") {
|
|
972
|
+
this.token.add(this.append(Object.assign({ type: kind }, result)));
|
|
774
973
|
}
|
|
775
974
|
setViewTransition(result) {
|
|
776
975
|
this.viewTransition.add(this.append(Object.assign({ type: "viewTransition" }, result)));
|
|
@@ -860,14 +1059,16 @@ const fallbackImpl = (...values) => values.some((value) => value === void 0) ? v
|
|
|
860
1059
|
const evaluateOptions = { environment: defaultEnv };
|
|
861
1060
|
function createParser(context) {
|
|
862
1061
|
const { jsx, imports, recipes } = context;
|
|
863
|
-
return function parse(sourceFile, encoder, options) {
|
|
1062
|
+
return function parse(sourceFile, encoder, options, resolveModule) {
|
|
864
1063
|
if (!sourceFile) return;
|
|
865
1064
|
const importDeclarations = getImportDeclarations(context, sourceFile);
|
|
866
1065
|
const file = imports.file(importDeclarations);
|
|
867
1066
|
const filePath = sourceFile.getFilePath();
|
|
868
1067
|
_bamboocss_logger.logger.debug("ast:import", !file.isEmpty() ? `Found import { ${file.toString()} } in ${filePath}` : `No import found in ${filePath}`);
|
|
869
1068
|
const parserResult = new ParserResult(context, encoder);
|
|
870
|
-
|
|
1069
|
+
const importedRecipes = resolveModule ? importedRecipeBindings(sourceFile, imports, resolveModule) : /* @__PURE__ */ new Map();
|
|
1070
|
+
if (file.isEmpty() && !jsx.isEnabled && importedRecipes.size === 0) return parserResult;
|
|
1071
|
+
for (const binding of importedRecipes.keys()) file.addLocalRecipe(binding);
|
|
871
1072
|
if (file.importsRecipeFactory()) for (const statement of sourceFile.compilerNode.statements) {
|
|
872
1073
|
if (!ts_morph.ts.isVariableStatement(statement)) continue;
|
|
873
1074
|
if (!(statement.declarationList.flags & ts_morph.ts.NodeFlags.Const)) continue;
|
|
@@ -964,11 +1165,22 @@ function createParser(context) {
|
|
|
964
1165
|
if (query.kind === "call-expression") parserResult.setCvaCall(alias, {
|
|
965
1166
|
name: alias,
|
|
966
1167
|
box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
|
|
967
|
-
data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
|
|
1168
|
+
data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0])),
|
|
1169
|
+
origin: importedRecipes.get(alias)
|
|
968
1170
|
});
|
|
969
1171
|
});
|
|
970
1172
|
return;
|
|
971
1173
|
}
|
|
1174
|
+
if (file.isTokenVarFn(alias)) {
|
|
1175
|
+
result.queryList.forEach((query) => {
|
|
1176
|
+
if (query.kind === "call-expression") parserResult.setToken({
|
|
1177
|
+
name: "token.var",
|
|
1178
|
+
box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
|
|
1179
|
+
data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
|
|
1180
|
+
}, "tokenVar");
|
|
1181
|
+
});
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
972
1184
|
(0, ts_pattern.match)(name).when(imports.matchers.css.match, (name) => {
|
|
973
1185
|
result.queryList.forEach((query) => {
|
|
974
1186
|
if (query.kind === "call-expression") if (query.box.value.length > 1) parserResult.set(name, {
|
|
@@ -1036,6 +1248,17 @@ function createParser(context) {
|
|
|
1036
1248
|
}
|
|
1037
1249
|
//#endregion
|
|
1038
1250
|
//#region src/project.ts
|
|
1251
|
+
/**
|
|
1252
|
+
* Everything memoized against another file's contents.
|
|
1253
|
+
*
|
|
1254
|
+
* Both caches answer a question about a *different* module than the one being parsed — what
|
|
1255
|
+
* an identifier resolved to, and which recipes a module exports — so both go stale on exactly
|
|
1256
|
+
* the same events, and clearing one without the other leaves the pair disagreeing.
|
|
1257
|
+
*/
|
|
1258
|
+
const invalidateResolutions = () => {
|
|
1259
|
+
(0, _bamboocss_extractor.clearBoxNodeCache)();
|
|
1260
|
+
clearImportedRecipeCache();
|
|
1261
|
+
};
|
|
1039
1262
|
const normalizeCompilerOptions = (raw) => {
|
|
1040
1263
|
if (!raw) return {};
|
|
1041
1264
|
const { options } = ts_morph.ts.convertCompilerOptionsFromJson(raw, process.cwd());
|
|
@@ -1119,6 +1342,18 @@ var Project = class {
|
|
|
1119
1342
|
* `.d.ts` into the project. The graph only tracks files bamboo already scans.
|
|
1120
1343
|
*/
|
|
1121
1344
|
moduleResolutionCache;
|
|
1345
|
+
/**
|
|
1346
|
+
* Everything memoized against the shape of the file tree, including the negative half.
|
|
1347
|
+
*
|
|
1348
|
+
* `resolveModuleName` caches failures too, so a specifier that resolved to nothing before
|
|
1349
|
+
* its target existed stays unresolved for the life of the process. That silently dropped a
|
|
1350
|
+
* dependency edge; now it would also leave a recipe permanently invisible to the module
|
|
1351
|
+
* importing it, since resolution is what finds one.
|
|
1352
|
+
*/
|
|
1353
|
+
invalidate = (fileTreeChanged = true) => {
|
|
1354
|
+
invalidateResolutions();
|
|
1355
|
+
if (fileTreeChanged) this.moduleResolutionCache = void 0;
|
|
1356
|
+
};
|
|
1122
1357
|
resolveImport = (decl) => {
|
|
1123
1358
|
const moduleName = decl.getModuleSpecifierValue();
|
|
1124
1359
|
if (!moduleName) return;
|
|
@@ -1127,6 +1362,16 @@ var Project = class {
|
|
|
1127
1362
|
const name = ts_morph.ts.resolveModuleName(moduleName, decl.getSourceFile().getFilePath(), compilerOptions, this.project.getModuleResolutionHost(), this.moduleResolutionCache).resolvedModule?.resolvedFileName;
|
|
1128
1363
|
return name ? this.project.getSourceFile(name) : void 0;
|
|
1129
1364
|
};
|
|
1365
|
+
/**
|
|
1366
|
+
* `resolveImport` in the shape a caller can use, for a specifier read off any declaration.
|
|
1367
|
+
*
|
|
1368
|
+
* Shares the module resolution cache above, so a barrel resolved while tracking
|
|
1369
|
+
* dependencies is not resolved again while looking for recipes.
|
|
1370
|
+
*/
|
|
1371
|
+
resolveModule = (specifier, from) => this.resolveImport({
|
|
1372
|
+
getModuleSpecifierValue: () => specifier,
|
|
1373
|
+
getSourceFile: () => from
|
|
1374
|
+
});
|
|
1130
1375
|
trackDependencies = (filePath, sourceFile) => {
|
|
1131
1376
|
const importer = this.normalizePath(sourceFile.getFilePath());
|
|
1132
1377
|
this.canonicalPaths.set(this.normalizePath(filePath), importer);
|
|
@@ -1173,7 +1418,7 @@ var Project = class {
|
|
|
1173
1418
|
};
|
|
1174
1419
|
createSourceFile = (filePath) => {
|
|
1175
1420
|
const { readFile } = this.options;
|
|
1176
|
-
|
|
1421
|
+
this.invalidate();
|
|
1177
1422
|
return this.project.createSourceFile(filePath, readFile(filePath), {
|
|
1178
1423
|
overwrite: true,
|
|
1179
1424
|
scriptKind: ts_morph.ScriptKind.TSX
|
|
@@ -1184,7 +1429,7 @@ var Project = class {
|
|
|
1184
1429
|
for (const file of files) this.createSourceFile(file);
|
|
1185
1430
|
};
|
|
1186
1431
|
addSourceFile = (filePath, content) => {
|
|
1187
|
-
(
|
|
1432
|
+
this.invalidate(!(filePath.includes("/") && this.project.getSourceFile(filePath)));
|
|
1188
1433
|
return this.project.createSourceFile(filePath, content, {
|
|
1189
1434
|
overwrite: true,
|
|
1190
1435
|
scriptKind: ts_morph.ScriptKind.TSX
|
|
@@ -1193,18 +1438,18 @@ var Project = class {
|
|
|
1193
1438
|
removeSourceFile = (filePath) => {
|
|
1194
1439
|
const sourceFile = this.project.getSourceFile(filePath);
|
|
1195
1440
|
if (sourceFile) {
|
|
1196
|
-
|
|
1441
|
+
this.invalidate();
|
|
1197
1442
|
return this.project.removeSourceFile(sourceFile);
|
|
1198
1443
|
}
|
|
1199
1444
|
return false;
|
|
1200
1445
|
};
|
|
1201
1446
|
reloadSourceFile = (filePath) => {
|
|
1202
|
-
|
|
1447
|
+
this.invalidate(false);
|
|
1203
1448
|
return this.getSourceFile(filePath)?.refreshFromFileSystemSync();
|
|
1204
1449
|
};
|
|
1205
1450
|
reloadSourceFiles = () => {
|
|
1206
1451
|
const files = this.getFiles();
|
|
1207
|
-
|
|
1452
|
+
this.invalidate();
|
|
1208
1453
|
for (const file of files) this.getSourceFile(file)?.refreshFromFileSystemSync() ?? this.project.addSourceFileAtPath(file);
|
|
1209
1454
|
};
|
|
1210
1455
|
get readFile() {
|
|
@@ -1238,7 +1483,7 @@ var Project = class {
|
|
|
1238
1483
|
}
|
|
1239
1484
|
}) ?? this.transformFile(filePath, original);
|
|
1240
1485
|
if (original !== transformed) sourceFile.replaceWithText(transformed);
|
|
1241
|
-
const result = this.parser(sourceFile, encoder, options)?.setFilePath(filePath);
|
|
1486
|
+
const result = this.parser(sourceFile, encoder, options, this.resolveModule)?.setFilePath(filePath);
|
|
1242
1487
|
hooks["parser:after"]?.({
|
|
1243
1488
|
filePath,
|
|
1244
1489
|
result
|
package/dist/index.d.cts
CHANGED
|
@@ -136,6 +136,9 @@ declare class Generator extends Context {
|
|
|
136
136
|
* Get CSS for a specific theme
|
|
137
137
|
*/
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region src/imported-recipes.d.ts
|
|
140
|
+
type ResolveModule = (specifier: string, from: SourceFile) => SourceFile | undefined;
|
|
141
|
+
//#endregion
|
|
139
142
|
//#region src/unresolved-styles.d.ts
|
|
140
143
|
interface UnresolvedStyle {
|
|
141
144
|
/**
|
|
@@ -226,7 +229,13 @@ declare class ParserResult implements ParserResultInterface {
|
|
|
226
229
|
* hash except an explicit `className`, which is what the message says to reach for.
|
|
227
230
|
*/
|
|
228
231
|
private reportUnresolvedRecipe;
|
|
229
|
-
|
|
232
|
+
/**
|
|
233
|
+
* `kind` separates `token()` from `token.var()`, which resolve to the value and to the
|
|
234
|
+
* variable reference respectively. They share this set deliberately: everything that reads
|
|
235
|
+
* a result for the token *path* — `collectTokenReferences`, keeping a declaration alive
|
|
236
|
+
* through pruning — wants both, and only the fold cares which half was asked for.
|
|
237
|
+
*/
|
|
238
|
+
setToken(result: ResultItem, kind?: 'token' | 'tokenVar'): void;
|
|
230
239
|
setViewTransition(result: ResultItem): void;
|
|
231
240
|
setPattern(name: string, result: ResultItem): void;
|
|
232
241
|
setRecipe(recipeName: string, result: ResultItem): void;
|
|
@@ -251,7 +260,16 @@ declare class ParserResult implements ParserResultInterface {
|
|
|
251
260
|
}
|
|
252
261
|
//#endregion
|
|
253
262
|
//#region src/parser.d.ts
|
|
254
|
-
declare function createParser(context: ParserOptions): (sourceFile: SourceFile | undefined, encoder?: Generator["encoder"], options?: ParserResultConfigureOptions
|
|
263
|
+
declare function createParser(context: ParserOptions): (sourceFile: SourceFile | undefined, encoder?: Generator["encoder"], options?: ParserResultConfigureOptions,
|
|
264
|
+
/**
|
|
265
|
+
* How to place a module specifier inside the project.
|
|
266
|
+
*
|
|
267
|
+
* Supplied by the caller because it owns the resolution cache, and because the cheap
|
|
268
|
+
* implementation is a filesystem lookup rather than a symbol-table walk. Absent, a
|
|
269
|
+
* recipe declared in another module stays invisible exactly as before.
|
|
270
|
+
*/
|
|
271
|
+
|
|
272
|
+
resolveModule?: ResolveModule) => ParserResult | undefined;
|
|
255
273
|
//#endregion
|
|
256
274
|
//#region src/project.d.ts
|
|
257
275
|
interface ProjectOptions extends ProjectOptions$1 {
|
|
@@ -315,7 +333,23 @@ declare class Project {
|
|
|
315
333
|
* `.d.ts` into the project. The graph only tracks files bamboo already scans.
|
|
316
334
|
*/
|
|
317
335
|
private moduleResolutionCache;
|
|
336
|
+
/**
|
|
337
|
+
* Everything memoized against the shape of the file tree, including the negative half.
|
|
338
|
+
*
|
|
339
|
+
* `resolveModuleName` caches failures too, so a specifier that resolved to nothing before
|
|
340
|
+
* its target existed stays unresolved for the life of the process. That silently dropped a
|
|
341
|
+
* dependency edge; now it would also leave a recipe permanently invisible to the module
|
|
342
|
+
* importing it, since resolution is what finds one.
|
|
343
|
+
*/
|
|
344
|
+
private invalidate;
|
|
318
345
|
private resolveImport;
|
|
346
|
+
/**
|
|
347
|
+
* `resolveImport` in the shape a caller can use, for a specifier read off any declaration.
|
|
348
|
+
*
|
|
349
|
+
* Shares the module resolution cache above, so a barrel resolved while tracking
|
|
350
|
+
* dependencies is not resolved again while looking for recipes.
|
|
351
|
+
*/
|
|
352
|
+
private resolveModule;
|
|
319
353
|
private trackDependencies;
|
|
320
354
|
/**
|
|
321
355
|
* Every file that transitively imports `filePath`, so a watcher can re-parse the
|
package/dist/index.d.mts
CHANGED
|
@@ -136,6 +136,9 @@ declare class Generator extends Context {
|
|
|
136
136
|
* Get CSS for a specific theme
|
|
137
137
|
*/
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region src/imported-recipes.d.ts
|
|
140
|
+
type ResolveModule = (specifier: string, from: SourceFile) => SourceFile | undefined;
|
|
141
|
+
//#endregion
|
|
139
142
|
//#region src/unresolved-styles.d.ts
|
|
140
143
|
interface UnresolvedStyle {
|
|
141
144
|
/**
|
|
@@ -226,7 +229,13 @@ declare class ParserResult implements ParserResultInterface {
|
|
|
226
229
|
* hash except an explicit `className`, which is what the message says to reach for.
|
|
227
230
|
*/
|
|
228
231
|
private reportUnresolvedRecipe;
|
|
229
|
-
|
|
232
|
+
/**
|
|
233
|
+
* `kind` separates `token()` from `token.var()`, which resolve to the value and to the
|
|
234
|
+
* variable reference respectively. They share this set deliberately: everything that reads
|
|
235
|
+
* a result for the token *path* — `collectTokenReferences`, keeping a declaration alive
|
|
236
|
+
* through pruning — wants both, and only the fold cares which half was asked for.
|
|
237
|
+
*/
|
|
238
|
+
setToken(result: ResultItem, kind?: 'token' | 'tokenVar'): void;
|
|
230
239
|
setViewTransition(result: ResultItem): void;
|
|
231
240
|
setPattern(name: string, result: ResultItem): void;
|
|
232
241
|
setRecipe(recipeName: string, result: ResultItem): void;
|
|
@@ -251,7 +260,16 @@ declare class ParserResult implements ParserResultInterface {
|
|
|
251
260
|
}
|
|
252
261
|
//#endregion
|
|
253
262
|
//#region src/parser.d.ts
|
|
254
|
-
declare function createParser(context: ParserOptions): (sourceFile: SourceFile | undefined, encoder?: Generator["encoder"], options?: ParserResultConfigureOptions
|
|
263
|
+
declare function createParser(context: ParserOptions): (sourceFile: SourceFile | undefined, encoder?: Generator["encoder"], options?: ParserResultConfigureOptions,
|
|
264
|
+
/**
|
|
265
|
+
* How to place a module specifier inside the project.
|
|
266
|
+
*
|
|
267
|
+
* Supplied by the caller because it owns the resolution cache, and because the cheap
|
|
268
|
+
* implementation is a filesystem lookup rather than a symbol-table walk. Absent, a
|
|
269
|
+
* recipe declared in another module stays invisible exactly as before.
|
|
270
|
+
*/
|
|
271
|
+
|
|
272
|
+
resolveModule?: ResolveModule) => ParserResult | undefined;
|
|
255
273
|
//#endregion
|
|
256
274
|
//#region src/project.d.ts
|
|
257
275
|
interface ProjectOptions extends ProjectOptions$1 {
|
|
@@ -315,7 +333,23 @@ declare class Project {
|
|
|
315
333
|
* `.d.ts` into the project. The graph only tracks files bamboo already scans.
|
|
316
334
|
*/
|
|
317
335
|
private moduleResolutionCache;
|
|
336
|
+
/**
|
|
337
|
+
* Everything memoized against the shape of the file tree, including the negative half.
|
|
338
|
+
*
|
|
339
|
+
* `resolveModuleName` caches failures too, so a specifier that resolved to nothing before
|
|
340
|
+
* its target existed stays unresolved for the life of the process. That silently dropped a
|
|
341
|
+
* dependency edge; now it would also leave a recipe permanently invisible to the module
|
|
342
|
+
* importing it, since resolution is what finds one.
|
|
343
|
+
*/
|
|
344
|
+
private invalidate;
|
|
318
345
|
private resolveImport;
|
|
346
|
+
/**
|
|
347
|
+
* `resolveImport` in the shape a caller can use, for a specifier read off any declaration.
|
|
348
|
+
*
|
|
349
|
+
* Shares the module resolution cache above, so a barrel resolved while tracking
|
|
350
|
+
* dependencies is not resolved again while looking for recipes.
|
|
351
|
+
*/
|
|
352
|
+
private resolveModule;
|
|
319
353
|
private trackDependencies;
|
|
320
354
|
/**
|
|
321
355
|
* Every file that transitively imports `filePath`, so a watcher can re-parse the
|
package/dist/index.mjs
CHANGED
|
@@ -64,7 +64,7 @@ function classifyProject(ctx, resultMap) {
|
|
|
64
64
|
const { item, kind, filepath, localMaps } = opts;
|
|
65
65
|
if (!item.box || box.isUnresolvable(item.box)) return;
|
|
66
66
|
if (!item.data) return;
|
|
67
|
-
if (item.type === "cva-call") return;
|
|
67
|
+
if (item.type === "cva-call" || item.type === "tokenVar") return;
|
|
68
68
|
const componentReportItem = {
|
|
69
69
|
componentIndex: String(componentIndex++),
|
|
70
70
|
componentName: item.name,
|
|
@@ -409,6 +409,11 @@ function createReportMaps() {
|
|
|
409
409
|
}
|
|
410
410
|
//#endregion
|
|
411
411
|
//#region src/get-module-specifier-value.ts
|
|
412
|
+
/**
|
|
413
|
+
* Both declaration kinds carry a specifier, and `export { x } from './m'` is how a barrel
|
|
414
|
+
* forwards a recipe — so this reads either. An `export { x }` with no `from` returns
|
|
415
|
+
* undefined, which is the same answer the throwing case gives.
|
|
416
|
+
*/
|
|
412
417
|
const getModuleSpecifierValue = (node) => {
|
|
413
418
|
try {
|
|
414
419
|
return node.getModuleSpecifierValue();
|
|
@@ -417,6 +422,194 @@ const getModuleSpecifierValue = (node) => {
|
|
|
417
422
|
}
|
|
418
423
|
};
|
|
419
424
|
//#endregion
|
|
425
|
+
//#region src/imported-recipes.ts
|
|
426
|
+
/**
|
|
427
|
+
* A module's exported recipes, memoized.
|
|
428
|
+
*
|
|
429
|
+
* Per *target*, so a barrel imported by two hundred files is walked once. Cleared alongside
|
|
430
|
+
* the box-node cache, which is the same invalidation this needs: both memoize a conclusion
|
|
431
|
+
* drawn from another file's contents.
|
|
432
|
+
*/
|
|
433
|
+
const exportedRecipes = /* @__PURE__ */ new Map();
|
|
434
|
+
const clearImportedRecipeCache = () => {
|
|
435
|
+
exportedRecipes.clear();
|
|
436
|
+
};
|
|
437
|
+
/** The names a file bound `cva`/`sva` to, or empty when it imports neither. */
|
|
438
|
+
const recipeFactoryAliases = (sourceFile, imports) => {
|
|
439
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
440
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
441
|
+
if (declaration.isTypeOnly()) continue;
|
|
442
|
+
const mod = getModuleSpecifierValue(declaration);
|
|
443
|
+
if (!mod) continue;
|
|
444
|
+
for (const specifier of declaration.getNamedImports()) {
|
|
445
|
+
if (specifier.isTypeOnly()) continue;
|
|
446
|
+
const name = specifier.getNameNode().getText();
|
|
447
|
+
if (name !== "cva" && name !== "sva") continue;
|
|
448
|
+
const alias = specifier.getAliasNode()?.getText() || name;
|
|
449
|
+
if (!imports.match({
|
|
450
|
+
name,
|
|
451
|
+
alias,
|
|
452
|
+
mod,
|
|
453
|
+
kind: "named"
|
|
454
|
+
})) continue;
|
|
455
|
+
aliases.add(alias);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return aliases;
|
|
459
|
+
};
|
|
460
|
+
/** Recipes this module declares itself, by the name it declared them under. */
|
|
461
|
+
const declaredRecipes = (sourceFile, imports) => {
|
|
462
|
+
const declared = /* @__PURE__ */ new Map();
|
|
463
|
+
const exported = /* @__PURE__ */ new Set();
|
|
464
|
+
const factories = recipeFactoryAliases(sourceFile, imports);
|
|
465
|
+
if (factories.size === 0) return {
|
|
466
|
+
declared,
|
|
467
|
+
exported
|
|
468
|
+
};
|
|
469
|
+
const filePath = sourceFile.getFilePath();
|
|
470
|
+
for (const statement of sourceFile.compilerNode.statements) {
|
|
471
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
472
|
+
if (!(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
|
|
473
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
474
|
+
const initializer = declaration.initializer;
|
|
475
|
+
if (!initializer || !ts.isCallExpression(initializer)) continue;
|
|
476
|
+
const callee = initializer.expression;
|
|
477
|
+
if (!ts.isIdentifier(callee) || !ts.isIdentifier(declaration.name)) continue;
|
|
478
|
+
if (!factories.has(callee.text)) continue;
|
|
479
|
+
declared.set(declaration.name.text, {
|
|
480
|
+
filePath,
|
|
481
|
+
name: declaration.name.text
|
|
482
|
+
});
|
|
483
|
+
if (statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) exported.add(declaration.name.text);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
declared,
|
|
488
|
+
exported
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
/**
|
|
492
|
+
* Every name this module exports that is bound to a recipe, and where it was declared.
|
|
493
|
+
*
|
|
494
|
+
* Follows `export { x } from './m'`, `export * from './m'`, and `import { x } … export { x }`,
|
|
495
|
+
* because a barrel is how these are reached in practice. The origin is carried through each
|
|
496
|
+
* hop rather than recomputed, so a consumer learns the *declaring* module however many
|
|
497
|
+
* re-exports stand between them.
|
|
498
|
+
*
|
|
499
|
+
* A star export contributes only names nothing else exports, which is what the language does:
|
|
500
|
+
* an explicit export shadows one arriving through `export *`. Resolving that the other way
|
|
501
|
+
* folded a call against a config it would never have run.
|
|
502
|
+
*/
|
|
503
|
+
const walkExports = (sourceFile, imports, resolveModule, seen) => {
|
|
504
|
+
const cached = exportedRecipes.get(sourceFile);
|
|
505
|
+
if (cached) return {
|
|
506
|
+
names: cached,
|
|
507
|
+
complete: true
|
|
508
|
+
};
|
|
509
|
+
if (seen.has(sourceFile)) return {
|
|
510
|
+
names: /* @__PURE__ */ new Map(),
|
|
511
|
+
complete: false
|
|
512
|
+
};
|
|
513
|
+
seen.add(sourceFile);
|
|
514
|
+
const names = /* @__PURE__ */ new Map();
|
|
515
|
+
/** Reachable under a local name — declarations, plus recipes this module imports. */
|
|
516
|
+
const local = /* @__PURE__ */ new Map();
|
|
517
|
+
/** Held back so an explicit export of the same name wins. */
|
|
518
|
+
const starred = /* @__PURE__ */ new Map();
|
|
519
|
+
/** Names two different star exports both carry, which resolve to neither. */
|
|
520
|
+
const ambiguous = /* @__PURE__ */ new Set();
|
|
521
|
+
let complete = true;
|
|
522
|
+
const { declared, exported } = declaredRecipes(sourceFile, imports);
|
|
523
|
+
for (const [name, origin] of declared) {
|
|
524
|
+
local.set(name, origin);
|
|
525
|
+
if (exported.has(name)) names.set(name, origin);
|
|
526
|
+
}
|
|
527
|
+
const imported = walkImports(sourceFile, imports, resolveModule, seen);
|
|
528
|
+
complete &&= imported.complete;
|
|
529
|
+
for (const [alias, origin] of imported.bindings) if (!local.has(alias)) local.set(alias, origin);
|
|
530
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
531
|
+
if (declaration.isTypeOnly()) continue;
|
|
532
|
+
const specifier = getModuleSpecifierValue(declaration);
|
|
533
|
+
const target = specifier ? resolveModule(specifier, sourceFile) : void 0;
|
|
534
|
+
if (specifier && !target) continue;
|
|
535
|
+
if (declaration.getNamespaceExport()) continue;
|
|
536
|
+
if (declaration.isNamespaceExport()) {
|
|
537
|
+
if (!target) continue;
|
|
538
|
+
const walk = walkExports(target, imports, resolveModule, seen);
|
|
539
|
+
complete &&= walk.complete;
|
|
540
|
+
for (const [name, origin] of walk.names) {
|
|
541
|
+
const existing = starred.get(name);
|
|
542
|
+
if (existing && (existing.filePath !== origin.filePath || existing.name !== origin.name)) {
|
|
543
|
+
ambiguous.add(name);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
starred.set(name, origin);
|
|
547
|
+
}
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
let source = local;
|
|
551
|
+
if (target) {
|
|
552
|
+
const walk = walkExports(target, imports, resolveModule, seen);
|
|
553
|
+
complete &&= walk.complete;
|
|
554
|
+
source = walk.names;
|
|
555
|
+
}
|
|
556
|
+
for (const exportSpecifier of declaration.getNamedExports()) {
|
|
557
|
+
if (exportSpecifier.isTypeOnly()) continue;
|
|
558
|
+
const name = exportSpecifier.getNameNode().getText();
|
|
559
|
+
const origin = source.get(name);
|
|
560
|
+
if (origin) names.set(exportSpecifier.getAliasNode()?.getText() || name, origin);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
for (const [name, origin] of starred) if (!names.has(name) && !ambiguous.has(name)) names.set(name, origin);
|
|
564
|
+
if (complete) exportedRecipes.set(sourceFile, names);
|
|
565
|
+
return {
|
|
566
|
+
names,
|
|
567
|
+
complete
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* Local names in this file bound, through an import, to a recipe declared elsewhere.
|
|
572
|
+
*
|
|
573
|
+
* Reports truncation for the same reason `walkExports` does: `export { x }` with no `from`
|
|
574
|
+
* resolves through here, so a walk cut short by a cycle can leave a name out — and caching
|
|
575
|
+
* that as the whole answer is what makes visibility depend on where the walk began.
|
|
576
|
+
*/
|
|
577
|
+
const walkImports = (sourceFile, imports, resolveModule, seen) => {
|
|
578
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
579
|
+
let complete = true;
|
|
580
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
581
|
+
if (declaration.isTypeOnly()) continue;
|
|
582
|
+
const named = declaration.getNamedImports();
|
|
583
|
+
if (named.length === 0) continue;
|
|
584
|
+
const specifier = getModuleSpecifierValue(declaration);
|
|
585
|
+
if (!specifier) continue;
|
|
586
|
+
const target = resolveModule(specifier, sourceFile);
|
|
587
|
+
if (!target || target === sourceFile) continue;
|
|
588
|
+
const walk = walkExports(target, imports, resolveModule, seen);
|
|
589
|
+
complete &&= walk.complete;
|
|
590
|
+
const { names } = walk;
|
|
591
|
+
if (names.size === 0) continue;
|
|
592
|
+
for (const importSpecifier of named) {
|
|
593
|
+
if (importSpecifier.isTypeOnly()) continue;
|
|
594
|
+
const origin = names.get(importSpecifier.getNameNode().getText());
|
|
595
|
+
if (!origin) continue;
|
|
596
|
+
bindings.set(importSpecifier.getAliasNode()?.getText() || importSpecifier.getNameNode().getText(), origin);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
bindings,
|
|
601
|
+
complete
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
/**
|
|
605
|
+
* Local names in this file bound, through an import, to a recipe declared elsewhere.
|
|
606
|
+
*
|
|
607
|
+
* Only specifiers the caller's resolver can place inside the project. A recipe outside the
|
|
608
|
+
* project's `include` is never parsed, so it has no rules in the stylesheet and could not be
|
|
609
|
+
* folded against even if the binding resolved.
|
|
610
|
+
*/
|
|
611
|
+
const importedRecipeBindings = (sourceFile, imports, resolveModule) => walkImports(sourceFile, imports, resolveModule, new Set([sourceFile])).bindings;
|
|
612
|
+
//#endregion
|
|
420
613
|
//#region src/get-import-declarations.ts
|
|
421
614
|
function getImportDeclarations(context, sourceFile) {
|
|
422
615
|
const { imports, tsOptions } = context;
|
|
@@ -768,8 +961,14 @@ var ParserResult = class {
|
|
|
768
961
|
const unresolved = findUnresolvedRecipeStyles(result);
|
|
769
962
|
if (unresolved.length) this.unresolved.push(...unresolved);
|
|
770
963
|
}
|
|
771
|
-
|
|
772
|
-
|
|
964
|
+
/**
|
|
965
|
+
* `kind` separates `token()` from `token.var()`, which resolve to the value and to the
|
|
966
|
+
* variable reference respectively. They share this set deliberately: everything that reads
|
|
967
|
+
* a result for the token *path* — `collectTokenReferences`, keeping a declaration alive
|
|
968
|
+
* through pruning — wants both, and only the fold cares which half was asked for.
|
|
969
|
+
*/
|
|
970
|
+
setToken(result, kind = "token") {
|
|
971
|
+
this.token.add(this.append(Object.assign({ type: kind }, result)));
|
|
773
972
|
}
|
|
774
973
|
setViewTransition(result) {
|
|
775
974
|
this.viewTransition.add(this.append(Object.assign({ type: "viewTransition" }, result)));
|
|
@@ -859,14 +1058,16 @@ const fallbackImpl = (...values) => values.some((value) => value === void 0) ? v
|
|
|
859
1058
|
const evaluateOptions = { environment: defaultEnv };
|
|
860
1059
|
function createParser(context) {
|
|
861
1060
|
const { jsx, imports, recipes } = context;
|
|
862
|
-
return function parse(sourceFile, encoder, options) {
|
|
1061
|
+
return function parse(sourceFile, encoder, options, resolveModule) {
|
|
863
1062
|
if (!sourceFile) return;
|
|
864
1063
|
const importDeclarations = getImportDeclarations(context, sourceFile);
|
|
865
1064
|
const file = imports.file(importDeclarations);
|
|
866
1065
|
const filePath = sourceFile.getFilePath();
|
|
867
1066
|
logger.debug("ast:import", !file.isEmpty() ? `Found import { ${file.toString()} } in ${filePath}` : `No import found in ${filePath}`);
|
|
868
1067
|
const parserResult = new ParserResult(context, encoder);
|
|
869
|
-
|
|
1068
|
+
const importedRecipes = resolveModule ? importedRecipeBindings(sourceFile, imports, resolveModule) : /* @__PURE__ */ new Map();
|
|
1069
|
+
if (file.isEmpty() && !jsx.isEnabled && importedRecipes.size === 0) return parserResult;
|
|
1070
|
+
for (const binding of importedRecipes.keys()) file.addLocalRecipe(binding);
|
|
870
1071
|
if (file.importsRecipeFactory()) for (const statement of sourceFile.compilerNode.statements) {
|
|
871
1072
|
if (!ts.isVariableStatement(statement)) continue;
|
|
872
1073
|
if (!(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
|
|
@@ -963,11 +1164,22 @@ function createParser(context) {
|
|
|
963
1164
|
if (query.kind === "call-expression") parserResult.setCvaCall(alias, {
|
|
964
1165
|
name: alias,
|
|
965
1166
|
box: query.box.value[0] ?? box.fallback(query.box),
|
|
966
|
-
data: combineResult(unbox(query.box.value[0]))
|
|
1167
|
+
data: combineResult(unbox(query.box.value[0])),
|
|
1168
|
+
origin: importedRecipes.get(alias)
|
|
967
1169
|
});
|
|
968
1170
|
});
|
|
969
1171
|
return;
|
|
970
1172
|
}
|
|
1173
|
+
if (file.isTokenVarFn(alias)) {
|
|
1174
|
+
result.queryList.forEach((query) => {
|
|
1175
|
+
if (query.kind === "call-expression") parserResult.setToken({
|
|
1176
|
+
name: "token.var",
|
|
1177
|
+
box: query.box.value[0] ?? box.fallback(query.box),
|
|
1178
|
+
data: combineResult(unbox(query.box.value[0]))
|
|
1179
|
+
}, "tokenVar");
|
|
1180
|
+
});
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
971
1183
|
match(name).when(imports.matchers.css.match, (name) => {
|
|
972
1184
|
result.queryList.forEach((query) => {
|
|
973
1185
|
if (query.kind === "call-expression") if (query.box.value.length > 1) parserResult.set(name, {
|
|
@@ -1035,6 +1247,17 @@ function createParser(context) {
|
|
|
1035
1247
|
}
|
|
1036
1248
|
//#endregion
|
|
1037
1249
|
//#region src/project.ts
|
|
1250
|
+
/**
|
|
1251
|
+
* Everything memoized against another file's contents.
|
|
1252
|
+
*
|
|
1253
|
+
* Both caches answer a question about a *different* module than the one being parsed — what
|
|
1254
|
+
* an identifier resolved to, and which recipes a module exports — so both go stale on exactly
|
|
1255
|
+
* the same events, and clearing one without the other leaves the pair disagreeing.
|
|
1256
|
+
*/
|
|
1257
|
+
const invalidateResolutions = () => {
|
|
1258
|
+
clearBoxNodeCache();
|
|
1259
|
+
clearImportedRecipeCache();
|
|
1260
|
+
};
|
|
1038
1261
|
const normalizeCompilerOptions = (raw) => {
|
|
1039
1262
|
if (!raw) return {};
|
|
1040
1263
|
const { options } = ts.convertCompilerOptionsFromJson(raw, process.cwd());
|
|
@@ -1118,6 +1341,18 @@ var Project = class {
|
|
|
1118
1341
|
* `.d.ts` into the project. The graph only tracks files bamboo already scans.
|
|
1119
1342
|
*/
|
|
1120
1343
|
moduleResolutionCache;
|
|
1344
|
+
/**
|
|
1345
|
+
* Everything memoized against the shape of the file tree, including the negative half.
|
|
1346
|
+
*
|
|
1347
|
+
* `resolveModuleName` caches failures too, so a specifier that resolved to nothing before
|
|
1348
|
+
* its target existed stays unresolved for the life of the process. That silently dropped a
|
|
1349
|
+
* dependency edge; now it would also leave a recipe permanently invisible to the module
|
|
1350
|
+
* importing it, since resolution is what finds one.
|
|
1351
|
+
*/
|
|
1352
|
+
invalidate = (fileTreeChanged = true) => {
|
|
1353
|
+
invalidateResolutions();
|
|
1354
|
+
if (fileTreeChanged) this.moduleResolutionCache = void 0;
|
|
1355
|
+
};
|
|
1121
1356
|
resolveImport = (decl) => {
|
|
1122
1357
|
const moduleName = decl.getModuleSpecifierValue();
|
|
1123
1358
|
if (!moduleName) return;
|
|
@@ -1126,6 +1361,16 @@ var Project = class {
|
|
|
1126
1361
|
const name = ts.resolveModuleName(moduleName, decl.getSourceFile().getFilePath(), compilerOptions, this.project.getModuleResolutionHost(), this.moduleResolutionCache).resolvedModule?.resolvedFileName;
|
|
1127
1362
|
return name ? this.project.getSourceFile(name) : void 0;
|
|
1128
1363
|
};
|
|
1364
|
+
/**
|
|
1365
|
+
* `resolveImport` in the shape a caller can use, for a specifier read off any declaration.
|
|
1366
|
+
*
|
|
1367
|
+
* Shares the module resolution cache above, so a barrel resolved while tracking
|
|
1368
|
+
* dependencies is not resolved again while looking for recipes.
|
|
1369
|
+
*/
|
|
1370
|
+
resolveModule = (specifier, from) => this.resolveImport({
|
|
1371
|
+
getModuleSpecifierValue: () => specifier,
|
|
1372
|
+
getSourceFile: () => from
|
|
1373
|
+
});
|
|
1129
1374
|
trackDependencies = (filePath, sourceFile) => {
|
|
1130
1375
|
const importer = this.normalizePath(sourceFile.getFilePath());
|
|
1131
1376
|
this.canonicalPaths.set(this.normalizePath(filePath), importer);
|
|
@@ -1172,7 +1417,7 @@ var Project = class {
|
|
|
1172
1417
|
};
|
|
1173
1418
|
createSourceFile = (filePath) => {
|
|
1174
1419
|
const { readFile } = this.options;
|
|
1175
|
-
|
|
1420
|
+
this.invalidate();
|
|
1176
1421
|
return this.project.createSourceFile(filePath, readFile(filePath), {
|
|
1177
1422
|
overwrite: true,
|
|
1178
1423
|
scriptKind: ScriptKind.TSX
|
|
@@ -1183,7 +1428,7 @@ var Project = class {
|
|
|
1183
1428
|
for (const file of files) this.createSourceFile(file);
|
|
1184
1429
|
};
|
|
1185
1430
|
addSourceFile = (filePath, content) => {
|
|
1186
|
-
|
|
1431
|
+
this.invalidate(!(filePath.includes("/") && this.project.getSourceFile(filePath)));
|
|
1187
1432
|
return this.project.createSourceFile(filePath, content, {
|
|
1188
1433
|
overwrite: true,
|
|
1189
1434
|
scriptKind: ScriptKind.TSX
|
|
@@ -1192,18 +1437,18 @@ var Project = class {
|
|
|
1192
1437
|
removeSourceFile = (filePath) => {
|
|
1193
1438
|
const sourceFile = this.project.getSourceFile(filePath);
|
|
1194
1439
|
if (sourceFile) {
|
|
1195
|
-
|
|
1440
|
+
this.invalidate();
|
|
1196
1441
|
return this.project.removeSourceFile(sourceFile);
|
|
1197
1442
|
}
|
|
1198
1443
|
return false;
|
|
1199
1444
|
};
|
|
1200
1445
|
reloadSourceFile = (filePath) => {
|
|
1201
|
-
|
|
1446
|
+
this.invalidate(false);
|
|
1202
1447
|
return this.getSourceFile(filePath)?.refreshFromFileSystemSync();
|
|
1203
1448
|
};
|
|
1204
1449
|
reloadSourceFiles = () => {
|
|
1205
1450
|
const files = this.getFiles();
|
|
1206
|
-
|
|
1451
|
+
this.invalidate();
|
|
1207
1452
|
for (const file of files) this.getSourceFile(file)?.refreshFromFileSystemSync() ?? this.project.addSourceFileAtPath(file);
|
|
1208
1453
|
};
|
|
1209
1454
|
get readFile() {
|
|
@@ -1237,7 +1482,7 @@ var Project = class {
|
|
|
1237
1482
|
}
|
|
1238
1483
|
}) ?? this.transformFile(filePath, original);
|
|
1239
1484
|
if (original !== transformed) sourceFile.replaceWithText(transformed);
|
|
1240
|
-
const result = this.parser(sourceFile, encoder, options)?.setFilePath(filePath);
|
|
1485
|
+
const result = this.parser(sourceFile, encoder, options, this.resolveModule)?.setFilePath(filePath);
|
|
1241
1486
|
hooks["parser:after"]?.({
|
|
1242
1487
|
filePath,
|
|
1243
1488
|
result
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/parser",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.1",
|
|
4
4
|
"description": "The static parser for bamboo css",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"ts-morph": "28.0.0",
|
|
36
36
|
"ts-pattern": "5.9.0",
|
|
37
|
-
"@bamboocss/
|
|
38
|
-
"@bamboocss/core": "^1.
|
|
39
|
-
"@bamboocss/
|
|
40
|
-
"@bamboocss/logger": "1.
|
|
41
|
-
"@bamboocss/shared": "1.
|
|
42
|
-
"@bamboocss/types": "1.
|
|
37
|
+
"@bamboocss/extractor": "1.28.1",
|
|
38
|
+
"@bamboocss/core": "^1.28.1",
|
|
39
|
+
"@bamboocss/config": "^1.28.1",
|
|
40
|
+
"@bamboocss/logger": "1.28.1",
|
|
41
|
+
"@bamboocss/shared": "1.28.1",
|
|
42
|
+
"@bamboocss/types": "1.28.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@bamboocss/generator": "1.
|
|
46
|
-
"@bamboocss/plugin-svelte": "1.
|
|
47
|
-
"@bamboocss/plugin-vue": "1.
|
|
45
|
+
"@bamboocss/generator": "1.28.1",
|
|
46
|
+
"@bamboocss/plugin-svelte": "1.28.1",
|
|
47
|
+
"@bamboocss/plugin-vue": "1.28.1"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsdown src/index.ts --format=esm,cjs --dts",
|