@bamboocss/parser 1.53.1 → 1.54.0

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 CHANGED
@@ -939,6 +939,13 @@ var ParserResult = class {
939
939
  * `assertNoDeadCalls` rather than warned about, for that reason.
940
940
  */
941
941
  deadCalls = [];
942
+ /**
943
+ * Whether each result item is attributed to its call site, for a source map.
944
+ *
945
+ * Off for a source a `parser:before` hook rewrote: its positions are the hook's output's,
946
+ * not the file's, and a wrong line is worse than none.
947
+ */
948
+ origins = true;
942
949
  constructor(context, encoder) {
943
950
  this.context = context;
944
951
  this.encoder = encoder ?? context.encoder;
@@ -971,13 +978,26 @@ var ParserResult = class {
971
978
  const data = result.data;
972
979
  const unresolved = findUnresolvedStyles(result, "atomic").filter((entry) => entry.reason === "unenumerable-keys");
973
980
  if (unresolved.length) this.unresolved.push(...unresolved);
974
- data.forEach((obj) => encoder.processAtomic(obj));
981
+ encoder.withOrigin(this.originOf(result), () => data.forEach((obj) => encoder.processAtomic(obj)));
975
982
  }
983
+ /** The call site of `result`, when the encoder is recording them. */
984
+ originOf = (result) => {
985
+ if (!this.origins || !this.encoder.recordOrigins) return void 0;
986
+ const node = result.box?.getNode();
987
+ const sourceFile = node?.getSourceFile();
988
+ if (!node || !sourceFile) return void 0;
989
+ const { line, column } = (0, _bamboocss_ts_ast.getLineAndColumnAtPos)(sourceFile, node.getStart());
990
+ return {
991
+ filePath: (0, _bamboocss_ts_ast.pathOf)(sourceFile),
992
+ line,
993
+ column
994
+ };
995
+ };
976
996
  setCva(result) {
977
997
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
978
998
  this.reportUnresolvedRecipe(result);
979
999
  const encoder = this.encoder;
980
- result.data.forEach((data) => encoder.processAtomicRecipe(data));
1000
+ encoder.withOrigin(this.originOf(result), () => result.data.forEach((data) => encoder.processAtomicRecipe(data)));
981
1001
  }
982
1002
  /**
983
1003
  * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
@@ -997,7 +1017,7 @@ var ParserResult = class {
997
1017
  this.sva.add(this.append(Object.assign({ type: "sva" }, result)));
998
1018
  this.reportUnresolvedRecipe(result);
999
1019
  const encoder = this.encoder;
1000
- result.data.forEach((data) => encoder.processAtomicSlotRecipe(data));
1020
+ encoder.withOrigin(this.originOf(result), () => result.data.forEach((data) => encoder.processAtomicSlotRecipe(data)));
1001
1021
  }
1002
1022
  /**
1003
1023
  * Record a recipe config the build could not fully read.
@@ -1038,7 +1058,7 @@ var ParserResult = class {
1038
1058
  type: "pattern",
1039
1059
  name
1040
1060
  }, result)));
1041
- result.data.forEach((obj) => this.encoder.processPattern(name, obj));
1061
+ this.encoder.withOrigin(this.originOf(result), () => result.data.forEach((obj) => this.encoder.processPattern(name, obj)));
1042
1062
  }
1043
1063
  /**
1044
1064
  * The variant axes a call site passed and the build could not read.
@@ -1181,13 +1201,14 @@ const fallbackImpl = (...values) => values.some((value) => value === void 0) ? v
1181
1201
  const evaluateOptions = { environment: defaultEnv };
1182
1202
  function createParser(context) {
1183
1203
  const { jsx, imports, recipes } = context;
1184
- return function parse(sourceFile, encoder, options, resolveModule) {
1204
+ return function parse(sourceFile, encoder, options, resolveModule, origins = true) {
1185
1205
  if (!sourceFile) return;
1186
1206
  const importDeclarations = getImportDeclarations$1(context, sourceFile);
1187
1207
  const file = imports.file(importDeclarations);
1188
1208
  const filePath = (0, _bamboocss_ts_ast.pathOf)(sourceFile);
1189
1209
  _bamboocss_logger.logger.debug("ast:import", !file.isEmpty() ? `Found import { ${file.toString()} } in ${filePath}` : `No import found in ${filePath}`);
1190
1210
  const parserResult = new ParserResult(context, encoder);
1211
+ parserResult.origins = origins;
1191
1212
  const importedRecipes = resolveModule ? importedRecipeBindings(sourceFile, imports, resolveModule) : /* @__PURE__ */ new Map();
1192
1213
  if (file.isEmpty() && !jsx.isEnabled && importedRecipes.size === 0) return parserResult;
1193
1214
  parserResult.importedRecipes = importedRecipes;
@@ -2123,9 +2144,11 @@ var Project = class {
2123
2144
  for (const { kind, ordinal, plan, specifier } of planned) {
2124
2145
  const resolved = this.#completeResolution(plan);
2125
2146
  if (!resolved.local) continue;
2147
+ const target = resolved.sourceFile ? this.normalizePath((0, _bamboocss_ts_ast.pathOf)(resolved.sourceFile)) : null;
2148
+ if (target && !specifier.startsWith(".") && !specifier.startsWith("/")) this.#bareSpecifierTargets.set(specifier, target);
2126
2149
  facts.push(Object.freeze({
2127
2150
  importer,
2128
- target: resolved.sourceFile ? this.normalizePath((0, _bamboocss_ts_ast.pathOf)(resolved.sourceFile)) : null,
2151
+ target,
2129
2152
  specifier,
2130
2153
  kind,
2131
2154
  ordinal
@@ -2453,6 +2476,29 @@ var Project = class {
2453
2476
  this.project.addSourceFiles(installed);
2454
2477
  this.invalidate(true);
2455
2478
  };
2479
+ /**
2480
+ * Whether the project holds this exact path, without reading anything from the compiler.
2481
+ *
2482
+ * Membership is answered from what was installed, so it costs no round trip — which is what
2483
+ * lets the Vite compiler ask it once per module it is handed.
2484
+ */
2485
+ hasSourceFile = (filePath) => {
2486
+ this.#ensureSourceFiles();
2487
+ return this.project.has(filePath);
2488
+ };
2489
+ /**
2490
+ * Where a bare specifier — a package name — resolved to, the last time any importer asked.
2491
+ *
2492
+ * One answer per specifier rather than per importer, which is an approximation a nested
2493
+ * `node_modules` could defeat; it exists for the Vite compiler to ask, of a module it has
2494
+ * not parsed, whether a package it imports is one the project holds a source of.
2495
+ */
2496
+ bareSpecifierTarget = (specifier) => this.#bareSpecifierTargets.get(specifier);
2497
+ /** @internal The revision every importer's cached resolution facts are checked against. */
2498
+ get fileTreeRevision() {
2499
+ return this.#fileTreeRevision;
2500
+ }
2501
+ #bareSpecifierTargets = /* @__PURE__ */ new Map();
2456
2502
  addSourceFile = (filePath, content, options = {}) => {
2457
2503
  this.#assertNotLoading();
2458
2504
  this.#ensureSourceFiles();
@@ -2485,7 +2531,7 @@ var Project = class {
2485
2531
  }
2486
2532
  this.invalidateSourcePreparation(filePath, existing);
2487
2533
  this.removedSourcePaths.delete(this.normalizePath(filePath));
2488
- this.invalidate(!existing, (0, _bamboocss_ts_ast.pathOf)(existing));
2534
+ this.invalidate(!existing && !options.auxiliary, existing ? (0, _bamboocss_ts_ast.pathOf)(existing) : options.auxiliary ? filePath : void 0);
2489
2535
  const sourceFile = this.project.createSourceFile(filePath, content, { scriptKind: scriptKindFor(filePath) });
2490
2536
  if (!sourceFile) throw new Error(`bamboo: could not add ${filePath} to the project`);
2491
2537
  this.markAuxiliary((0, _bamboocss_ts_ast.pathOf)(sourceFile), options.auxiliary);
@@ -2587,7 +2633,7 @@ var Project = class {
2587
2633
  const prepared = this.prepareEffectiveSource(filePath, sourceFile, hookFilePath);
2588
2634
  const parserOptions = prepared.options;
2589
2635
  sourceFile = prepared.sourceFile ?? sourceFile;
2590
- const result = (encoder ?? this.options.parserOptions.encoder).withOwner("parse", (0, _bamboocss_ts_ast.pathOf)(sourceFile), () => this.parser(sourceFile, encoder, parserOptions, this.resolveModule))?.setFilePath(filePath);
2636
+ const result = (encoder ?? this.options.parserOptions.encoder).withOwner("parse", (0, _bamboocss_ts_ast.pathOf)(sourceFile), () => this.parser(sourceFile, encoder, parserOptions, this.resolveModule, prepared.effectiveText === prepared.inputText))?.setFilePath(filePath);
2591
2637
  hooks["parser:after"]?.({
2592
2638
  filePath: hookFilePath,
2593
2639
  result
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, DeadImport, ParserOptions, StyleDecoder, Stylesheet } from "@bamboocss/core";
1
+ import { AtomOrigin, Context, DeadImport, ParserOptions, StyleDecoder, Stylesheet } from "@bamboocss/core";
2
2
  import { ArtifactId, BambooHooks, ConfigTsOptions, CssArtifactType, LoadConfigResult, ParserResultConfigureOptions, ParserResultInterface, ResultItem, Runtime, SpecFile, SpecType, SpecTypeMap } from "@bamboocss/types";
3
3
  import { CompilerOptions, Project as Project$1, ProjectOptions as ProjectOptions$1, SourceFile } from "@bamboocss/ts-ast";
4
4
  import { ResolveModule } from "@bamboocss/extractor";
@@ -134,6 +134,13 @@ declare class Generator extends Context {
134
134
  */
135
135
  private getAlwaysKeptTokenVars;
136
136
  getParserCss: (decoder: StyleDecoder) => string;
137
+ /**
138
+ * Each atom's first call site, by the class name the sheet writes it under.
139
+ *
140
+ * Only what the encoder recorded — see `StyleEncoder.recordOrigins` — so empty unless an
141
+ * integration asked for origins. Several hashes can decode to one class; the first wins.
142
+ */
143
+ getAtomOrigins: () => Map<string, AtomOrigin>;
137
144
  getCss: (stylesheet?: Stylesheet) => string;
138
145
  /**
139
146
  * Fail on a style value shaped like a token path that names no token.
@@ -195,6 +202,34 @@ declare class Generator extends Context {
195
202
  */
196
203
  reportRawValues: () => void;
197
204
  assertNoUnresolvedTokens: () => void;
205
+ /**
206
+ * The findings `warn` has already printed, keyed `property:value`.
207
+ *
208
+ * A dev server emits the sheet on every edit, and extraction is additive within a watch, so
209
+ * a finding that warned on every rebuild would bury the edit that introduced the next one.
210
+ * Per process, like the sheet it describes.
211
+ */
212
+ private reportedInvalidDeclarations;
213
+ /**
214
+ * Report every declaration in the sheet just emitted that its property's grammar rejects.
215
+ *
216
+ * The other side of `assertNoUnresolvedTokens`. That one reads the *values* the source asked
217
+ * for; this reads what the sheet *contains*, after every utility transform, mixin and recipe
218
+ * has had its say — which is the only place a transform that handed a value through
219
+ * unchanged, or a `[…]` literal that was never valid CSS, can be seen at all. Collected by
220
+ * `Stylesheet.toCss`, where the finished tree exists; graded here, where the sheet is
221
+ * emitted.
222
+ *
223
+ * A finding the unresolved-token pass owns is left to it, whatever that pass is set to, so
224
+ * one mistake is one report and `unresolvedToken: 'off'` means silence rather than the same
225
+ * value reported in this check's voice: `display: 'flexx'` is that pass's grammar half, and
226
+ * would otherwise be rejected again here as the declaration it became. Keyed on the
227
+ * property the utility *emits*, because that is how the sheet spells it.
228
+ *
229
+ * `warn` reports each distinct declaration once per process. `error` lists everything the
230
+ * sheet holds each time, because each time the build is failing on it.
231
+ */
232
+ reportInvalidDeclarations: (sheet: Stylesheet) => void;
198
233
  /**
199
234
  * Get CSS for a specific layer from the stylesheet
200
235
  */
@@ -298,10 +333,19 @@ declare class ParserResult implements ParserResultInterface {
298
333
  * `assertNoDeadCalls` rather than warned about, for that reason.
299
334
  */
300
335
  deadCalls: DeadImport[];
336
+ /**
337
+ * Whether each result item is attributed to its call site, for a source map.
338
+ *
339
+ * Off for a source a `parser:before` hook rewrote: its positions are the hook's output's,
340
+ * not the file's, and a wrong line is worse than none.
341
+ */
342
+ origins: boolean;
301
343
  constructor(context: ParserOptions, encoder?: ParserOptions['encoder']);
302
344
  append(result: ResultItem): ResultItem;
303
345
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
304
346
  setCss(result: ResultItem): void;
347
+ /** The call site of `result`, when the encoder is recording them. */
348
+ private originOf;
305
349
  setCva(result: ResultItem): void;
306
350
  /**
307
351
  * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
@@ -400,7 +444,9 @@ declare function createParser(context: ParserOptions): (sourceFile: SourceFile |
400
444
  * recipe declared in another module stays invisible exactly as before.
401
445
  */
402
446
 
403
- resolveModule?: ResolveModule) => ParserResult | undefined;
447
+ resolveModule?: ResolveModule, /** Whether result items may be attributed to their call site. @see `ParserResult.origins` */
448
+
449
+ origins?: boolean) => ParserResult | undefined;
404
450
  //#endregion
405
451
  //#region src/project.d.ts
406
452
  interface ProjectOptions extends ProjectOptions$1 {
@@ -654,6 +700,23 @@ declare class Project {
654
700
  * that, and a caller that wants a file *extracted* puts it in `include`.
655
701
  */
656
702
  addSourceFiles: (entries: Iterable<readonly [string, string]>) => void;
703
+ /**
704
+ * Whether the project holds this exact path, without reading anything from the compiler.
705
+ *
706
+ * Membership is answered from what was installed, so it costs no round trip — which is what
707
+ * lets the Vite compiler ask it once per module it is handed.
708
+ */
709
+ hasSourceFile: (filePath: string) => boolean;
710
+ /**
711
+ * Where a bare specifier — a package name — resolved to, the last time any importer asked.
712
+ *
713
+ * One answer per specifier rather than per importer, which is an approximation a nested
714
+ * `node_modules` could defeat; it exists for the Vite compiler to ask, of a module it has
715
+ * not parsed, whether a package it imports is one the project holds a source of.
716
+ */
717
+ bareSpecifierTarget: (specifier: string) => string | undefined;
718
+ /** @internal The revision every importer's cached resolution facts are checked against. */
719
+ get fileTreeRevision(): number;
657
720
  addSourceFile: (filePath: string, content: string, options?: AddSourceFileOptions) => SourceFile;
658
721
  /** Claim or release compiler ownership of one source, in the ledger's own spelling. */
659
722
  private markAuxiliary;
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CompilerOptions, Project as Project$1, ProjectOptions as ProjectOptions$1, SourceFile } from "@bamboocss/ts-ast";
2
2
  import { ResolveModule } from "@bamboocss/extractor";
3
- import { Context, DeadImport, ParserOptions, StyleDecoder, Stylesheet } from "@bamboocss/core";
3
+ import { AtomOrigin, Context, DeadImport, ParserOptions, StyleDecoder, Stylesheet } from "@bamboocss/core";
4
4
  import { ArtifactId, BambooHooks, ConfigTsOptions, CssArtifactType, LoadConfigResult, ParserResultConfigureOptions, ParserResultInterface, ResultItem, Runtime, SpecFile, SpecType, SpecTypeMap } from "@bamboocss/types";
5
5
 
6
6
  //#region ../generator/dist/index.d.cts
@@ -134,6 +134,13 @@ declare class Generator extends Context {
134
134
  */
135
135
  private getAlwaysKeptTokenVars;
136
136
  getParserCss: (decoder: StyleDecoder) => string;
137
+ /**
138
+ * Each atom's first call site, by the class name the sheet writes it under.
139
+ *
140
+ * Only what the encoder recorded — see `StyleEncoder.recordOrigins` — so empty unless an
141
+ * integration asked for origins. Several hashes can decode to one class; the first wins.
142
+ */
143
+ getAtomOrigins: () => Map<string, AtomOrigin>;
137
144
  getCss: (stylesheet?: Stylesheet) => string;
138
145
  /**
139
146
  * Fail on a style value shaped like a token path that names no token.
@@ -195,6 +202,34 @@ declare class Generator extends Context {
195
202
  */
196
203
  reportRawValues: () => void;
197
204
  assertNoUnresolvedTokens: () => void;
205
+ /**
206
+ * The findings `warn` has already printed, keyed `property:value`.
207
+ *
208
+ * A dev server emits the sheet on every edit, and extraction is additive within a watch, so
209
+ * a finding that warned on every rebuild would bury the edit that introduced the next one.
210
+ * Per process, like the sheet it describes.
211
+ */
212
+ private reportedInvalidDeclarations;
213
+ /**
214
+ * Report every declaration in the sheet just emitted that its property's grammar rejects.
215
+ *
216
+ * The other side of `assertNoUnresolvedTokens`. That one reads the *values* the source asked
217
+ * for; this reads what the sheet *contains*, after every utility transform, mixin and recipe
218
+ * has had its say — which is the only place a transform that handed a value through
219
+ * unchanged, or a `[…]` literal that was never valid CSS, can be seen at all. Collected by
220
+ * `Stylesheet.toCss`, where the finished tree exists; graded here, where the sheet is
221
+ * emitted.
222
+ *
223
+ * A finding the unresolved-token pass owns is left to it, whatever that pass is set to, so
224
+ * one mistake is one report and `unresolvedToken: 'off'` means silence rather than the same
225
+ * value reported in this check's voice: `display: 'flexx'` is that pass's grammar half, and
226
+ * would otherwise be rejected again here as the declaration it became. Keyed on the
227
+ * property the utility *emits*, because that is how the sheet spells it.
228
+ *
229
+ * `warn` reports each distinct declaration once per process. `error` lists everything the
230
+ * sheet holds each time, because each time the build is failing on it.
231
+ */
232
+ reportInvalidDeclarations: (sheet: Stylesheet) => void;
198
233
  /**
199
234
  * Get CSS for a specific layer from the stylesheet
200
235
  */
@@ -298,10 +333,19 @@ declare class ParserResult implements ParserResultInterface {
298
333
  * `assertNoDeadCalls` rather than warned about, for that reason.
299
334
  */
300
335
  deadCalls: DeadImport[];
336
+ /**
337
+ * Whether each result item is attributed to its call site, for a source map.
338
+ *
339
+ * Off for a source a `parser:before` hook rewrote: its positions are the hook's output's,
340
+ * not the file's, and a wrong line is worse than none.
341
+ */
342
+ origins: boolean;
301
343
  constructor(context: ParserOptions, encoder?: ParserOptions['encoder']);
302
344
  append(result: ResultItem): ResultItem;
303
345
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
304
346
  setCss(result: ResultItem): void;
347
+ /** The call site of `result`, when the encoder is recording them. */
348
+ private originOf;
305
349
  setCva(result: ResultItem): void;
306
350
  /**
307
351
  * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
@@ -400,7 +444,9 @@ declare function createParser(context: ParserOptions): (sourceFile: SourceFile |
400
444
  * recipe declared in another module stays invisible exactly as before.
401
445
  */
402
446
 
403
- resolveModule?: ResolveModule) => ParserResult | undefined;
447
+ resolveModule?: ResolveModule, /** Whether result items may be attributed to their call site. @see `ParserResult.origins` */
448
+
449
+ origins?: boolean) => ParserResult | undefined;
404
450
  //#endregion
405
451
  //#region src/project.d.ts
406
452
  interface ProjectOptions extends ProjectOptions$1 {
@@ -654,6 +700,23 @@ declare class Project {
654
700
  * that, and a caller that wants a file *extracted* puts it in `include`.
655
701
  */
656
702
  addSourceFiles: (entries: Iterable<readonly [string, string]>) => void;
703
+ /**
704
+ * Whether the project holds this exact path, without reading anything from the compiler.
705
+ *
706
+ * Membership is answered from what was installed, so it costs no round trip — which is what
707
+ * lets the Vite compiler ask it once per module it is handed.
708
+ */
709
+ hasSourceFile: (filePath: string) => boolean;
710
+ /**
711
+ * Where a bare specifier — a package name — resolved to, the last time any importer asked.
712
+ *
713
+ * One answer per specifier rather than per importer, which is an approximation a nested
714
+ * `node_modules` could defeat; it exists for the Vite compiler to ask, of a module it has
715
+ * not parsed, whether a package it imports is one the project holds a source of.
716
+ */
717
+ bareSpecifierTarget: (specifier: string) => string | undefined;
718
+ /** @internal The revision every importer's cached resolution facts are checked against. */
719
+ get fileTreeRevision(): number;
657
720
  addSourceFile: (filePath: string, content: string, options?: AddSourceFileOptions) => SourceFile;
658
721
  /** Claim or release compiler ownership of one source, in the ledger's own spelling. */
659
722
  private markAuxiliary;
package/dist/index.mjs CHANGED
@@ -938,6 +938,13 @@ var ParserResult = class {
938
938
  * `assertNoDeadCalls` rather than warned about, for that reason.
939
939
  */
940
940
  deadCalls = [];
941
+ /**
942
+ * Whether each result item is attributed to its call site, for a source map.
943
+ *
944
+ * Off for a source a `parser:before` hook rewrote: its positions are the hook's output's,
945
+ * not the file's, and a wrong line is worse than none.
946
+ */
947
+ origins = true;
941
948
  constructor(context, encoder) {
942
949
  this.context = context;
943
950
  this.encoder = encoder ?? context.encoder;
@@ -970,13 +977,26 @@ var ParserResult = class {
970
977
  const data = result.data;
971
978
  const unresolved = findUnresolvedStyles(result, "atomic").filter((entry) => entry.reason === "unenumerable-keys");
972
979
  if (unresolved.length) this.unresolved.push(...unresolved);
973
- data.forEach((obj) => encoder.processAtomic(obj));
980
+ encoder.withOrigin(this.originOf(result), () => data.forEach((obj) => encoder.processAtomic(obj)));
974
981
  }
982
+ /** The call site of `result`, when the encoder is recording them. */
983
+ originOf = (result) => {
984
+ if (!this.origins || !this.encoder.recordOrigins) return void 0;
985
+ const node = result.box?.getNode();
986
+ const sourceFile = node?.getSourceFile();
987
+ if (!node || !sourceFile) return void 0;
988
+ const { line, column } = getLineAndColumnAtPos(sourceFile, node.getStart());
989
+ return {
990
+ filePath: pathOf(sourceFile),
991
+ line,
992
+ column
993
+ };
994
+ };
975
995
  setCva(result) {
976
996
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
977
997
  this.reportUnresolvedRecipe(result);
978
998
  const encoder = this.encoder;
979
- result.data.forEach((data) => encoder.processAtomicRecipe(data));
999
+ encoder.withOrigin(this.originOf(result), () => result.data.forEach((data) => encoder.processAtomicRecipe(data)));
980
1000
  }
981
1001
  /**
982
1002
  * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
@@ -996,7 +1016,7 @@ var ParserResult = class {
996
1016
  this.sva.add(this.append(Object.assign({ type: "sva" }, result)));
997
1017
  this.reportUnresolvedRecipe(result);
998
1018
  const encoder = this.encoder;
999
- result.data.forEach((data) => encoder.processAtomicSlotRecipe(data));
1019
+ encoder.withOrigin(this.originOf(result), () => result.data.forEach((data) => encoder.processAtomicSlotRecipe(data)));
1000
1020
  }
1001
1021
  /**
1002
1022
  * Record a recipe config the build could not fully read.
@@ -1037,7 +1057,7 @@ var ParserResult = class {
1037
1057
  type: "pattern",
1038
1058
  name
1039
1059
  }, result)));
1040
- result.data.forEach((obj) => this.encoder.processPattern(name, obj));
1060
+ this.encoder.withOrigin(this.originOf(result), () => result.data.forEach((obj) => this.encoder.processPattern(name, obj)));
1041
1061
  }
1042
1062
  /**
1043
1063
  * The variant axes a call site passed and the build could not read.
@@ -1180,13 +1200,14 @@ const fallbackImpl = (...values) => values.some((value) => value === void 0) ? v
1180
1200
  const evaluateOptions = { environment: defaultEnv };
1181
1201
  function createParser(context) {
1182
1202
  const { jsx, imports, recipes } = context;
1183
- return function parse(sourceFile, encoder, options, resolveModule) {
1203
+ return function parse(sourceFile, encoder, options, resolveModule, origins = true) {
1184
1204
  if (!sourceFile) return;
1185
1205
  const importDeclarations = getImportDeclarations$1(context, sourceFile);
1186
1206
  const file = imports.file(importDeclarations);
1187
1207
  const filePath = pathOf(sourceFile);
1188
1208
  logger.debug("ast:import", !file.isEmpty() ? `Found import { ${file.toString()} } in ${filePath}` : `No import found in ${filePath}`);
1189
1209
  const parserResult = new ParserResult(context, encoder);
1210
+ parserResult.origins = origins;
1190
1211
  const importedRecipes = resolveModule ? importedRecipeBindings(sourceFile, imports, resolveModule) : /* @__PURE__ */ new Map();
1191
1212
  if (file.isEmpty() && !jsx.isEnabled && importedRecipes.size === 0) return parserResult;
1192
1213
  parserResult.importedRecipes = importedRecipes;
@@ -2122,9 +2143,11 @@ var Project = class {
2122
2143
  for (const { kind, ordinal, plan, specifier } of planned) {
2123
2144
  const resolved = this.#completeResolution(plan);
2124
2145
  if (!resolved.local) continue;
2146
+ const target = resolved.sourceFile ? this.normalizePath(pathOf(resolved.sourceFile)) : null;
2147
+ if (target && !specifier.startsWith(".") && !specifier.startsWith("/")) this.#bareSpecifierTargets.set(specifier, target);
2125
2148
  facts.push(Object.freeze({
2126
2149
  importer,
2127
- target: resolved.sourceFile ? this.normalizePath(pathOf(resolved.sourceFile)) : null,
2150
+ target,
2128
2151
  specifier,
2129
2152
  kind,
2130
2153
  ordinal
@@ -2452,6 +2475,29 @@ var Project = class {
2452
2475
  this.project.addSourceFiles(installed);
2453
2476
  this.invalidate(true);
2454
2477
  };
2478
+ /**
2479
+ * Whether the project holds this exact path, without reading anything from the compiler.
2480
+ *
2481
+ * Membership is answered from what was installed, so it costs no round trip — which is what
2482
+ * lets the Vite compiler ask it once per module it is handed.
2483
+ */
2484
+ hasSourceFile = (filePath) => {
2485
+ this.#ensureSourceFiles();
2486
+ return this.project.has(filePath);
2487
+ };
2488
+ /**
2489
+ * Where a bare specifier — a package name — resolved to, the last time any importer asked.
2490
+ *
2491
+ * One answer per specifier rather than per importer, which is an approximation a nested
2492
+ * `node_modules` could defeat; it exists for the Vite compiler to ask, of a module it has
2493
+ * not parsed, whether a package it imports is one the project holds a source of.
2494
+ */
2495
+ bareSpecifierTarget = (specifier) => this.#bareSpecifierTargets.get(specifier);
2496
+ /** @internal The revision every importer's cached resolution facts are checked against. */
2497
+ get fileTreeRevision() {
2498
+ return this.#fileTreeRevision;
2499
+ }
2500
+ #bareSpecifierTargets = /* @__PURE__ */ new Map();
2455
2501
  addSourceFile = (filePath, content, options = {}) => {
2456
2502
  this.#assertNotLoading();
2457
2503
  this.#ensureSourceFiles();
@@ -2484,7 +2530,7 @@ var Project = class {
2484
2530
  }
2485
2531
  this.invalidateSourcePreparation(filePath, existing);
2486
2532
  this.removedSourcePaths.delete(this.normalizePath(filePath));
2487
- this.invalidate(!existing, pathOf(existing));
2533
+ this.invalidate(!existing && !options.auxiliary, existing ? pathOf(existing) : options.auxiliary ? filePath : void 0);
2488
2534
  const sourceFile = this.project.createSourceFile(filePath, content, { scriptKind: scriptKindFor(filePath) });
2489
2535
  if (!sourceFile) throw new Error(`bamboo: could not add ${filePath} to the project`);
2490
2536
  this.markAuxiliary(pathOf(sourceFile), options.auxiliary);
@@ -2586,7 +2632,7 @@ var Project = class {
2586
2632
  const prepared = this.prepareEffectiveSource(filePath, sourceFile, hookFilePath);
2587
2633
  const parserOptions = prepared.options;
2588
2634
  sourceFile = prepared.sourceFile ?? sourceFile;
2589
- const result = (encoder ?? this.options.parserOptions.encoder).withOwner("parse", pathOf(sourceFile), () => this.parser(sourceFile, encoder, parserOptions, this.resolveModule))?.setFilePath(filePath);
2635
+ const result = (encoder ?? this.options.parserOptions.encoder).withOwner("parse", pathOf(sourceFile), () => this.parser(sourceFile, encoder, parserOptions, this.resolveModule, prepared.effectiveText === prepared.inputText))?.setFilePath(filePath);
2590
2636
  hooks["parser:after"]?.({
2591
2637
  filePath: hookFilePath,
2592
2638
  result
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/parser",
3
- "version": "1.53.1",
3
+ "version": "1.54.0",
4
4
  "description": "The static parser for bamboo css",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -33,20 +33,20 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "ts-pattern": "5.9.0",
36
- "@bamboocss/config": "^1.53.1",
37
- "@bamboocss/core": "^1.53.1",
38
- "@bamboocss/extractor": "1.53.1",
39
- "@bamboocss/logger": "1.53.1",
40
- "@bamboocss/shared": "1.53.1",
41
- "@bamboocss/ts-ast": "1.53.1",
42
- "@bamboocss/types": "1.53.1"
36
+ "@bamboocss/config": "^1.54.0",
37
+ "@bamboocss/core": "^1.54.0",
38
+ "@bamboocss/extractor": "1.54.0",
39
+ "@bamboocss/logger": "1.54.0",
40
+ "@bamboocss/shared": "1.54.0",
41
+ "@bamboocss/ts-ast": "1.54.0",
42
+ "@bamboocss/types": "1.54.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@typescript/api": "npm:typescript@7.1.0-dev.20260826.1",
46
46
  "ts-morph": "28.0.0",
47
- "@bamboocss/generator": "1.53.1",
48
- "@bamboocss/plugin-svelte": "1.53.1",
49
- "@bamboocss/plugin-vue": "1.53.1"
47
+ "@bamboocss/generator": "1.54.0",
48
+ "@bamboocss/plugin-svelte": "1.54.0",
49
+ "@bamboocss/plugin-vue": "1.54.0"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsdown src/index.ts --format=esm,cjs --dts",