@bamboocss/parser 1.16.0 → 1.17.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
@@ -531,7 +531,109 @@ const writtenProps = (node) => {
531
531
  * the element renders with no styles at all. Under `atomic` the same call loses one
532
532
  * declaration and keeps the rest, which is why this is not reported there.
533
533
  */
534
- const findUnresolvedStyles = (item) => {
534
+ /**
535
+ * A recipe config the build could not fully read, level by level.
536
+ *
537
+ * `findUnresolvedStyles` reads the *call's* literal, which for a recipe holds `base` and
538
+ * `variants` rather than declarations — so a loss inside `base` is nested out of its reach.
539
+ * This walks the written source against the resolved data instead, and reports three ways a
540
+ * level can lose something:
541
+ *
542
+ * - a key written in the source that never arrived in the data — an unresolvable *value*,
543
+ * which leaves no trace in the box tree because the pair is never recorded at all;
544
+ * - a spread or computed key that contributed no keys beyond those written beside it;
545
+ * - the config not being an object literal at all, so nothing can be compared.
546
+ *
547
+ * Every level is unwrapped first. `as const` and `satisfies` are idiomatic on a recipe
548
+ * config, and reading through them is what the extractor does — a diagnostic that stops at
549
+ * the cast reports nothing for the exact configs most likely to be written that way.
550
+ */
551
+ const findUnresolvedInValue = (node, resolved, path, out) => {
552
+ const value = node ? (0, _bamboocss_extractor.unwrapExpression)(node) : void 0;
553
+ if (!value) return;
554
+ if (ts_morph.Node.isArrayLiteralExpression(value)) {
555
+ value.getElements().forEach((element, index) => {
556
+ const at = path ? `${path}.${index}` : String(index);
557
+ const element_ = resolved?.[index];
558
+ if (ts_morph.Node.isSpreadElement(element) && element_ == null) {
559
+ out.push({
560
+ prop: at || void 0,
561
+ reason: "unenumerable-keys"
562
+ });
563
+ return;
564
+ }
565
+ findUnresolvedInValue(element, element_, at, out);
566
+ });
567
+ return;
568
+ }
569
+ if (!ts_morph.Node.isObjectLiteralExpression(value)) return;
570
+ const properties = value.getProperties();
571
+ const written = [];
572
+ let uncertain = false;
573
+ for (const property of properties) {
574
+ if (ts_morph.Node.isSpreadAssignment(property)) {
575
+ const spread = (0, _bamboocss_extractor.unwrapExpression)(property.getExpression());
576
+ if (ts_morph.Node.isObjectLiteralExpression(spread)) {
577
+ for (const inner of spread.getProperties()) {
578
+ if (!ts_morph.Node.isPropertyAssignment(inner) && !ts_morph.Node.isShorthandPropertyAssignment(inner)) continue;
579
+ const innerName = inner.getName();
580
+ if (!innerName.startsWith("[")) written.push(innerName.replace(/^['"]|['"]$/g, ""));
581
+ }
582
+ continue;
583
+ }
584
+ uncertain = true;
585
+ continue;
586
+ }
587
+ if (!ts_morph.Node.isPropertyAssignment(property) && !ts_morph.Node.isShorthandPropertyAssignment(property)) continue;
588
+ const name = property.getName();
589
+ if (name.startsWith("[")) {
590
+ uncertain = true;
591
+ continue;
592
+ }
593
+ written.push(name.replace(/^['"]|['"]$/g, ""));
594
+ }
595
+ const resolvedKeys = Boolean(resolved) && typeof resolved === "object" ? Object.keys(resolved) : [];
596
+ for (const name of written) if (!resolvedKeys.includes(name)) out.push({
597
+ prop: path ? `${path}.${name}` : name,
598
+ reason: "missing-property"
599
+ });
600
+ if (uncertain && !resolvedKeys.some((key) => !written.includes(key))) out.push({
601
+ prop: path || void 0,
602
+ reason: "unenumerable-keys"
603
+ });
604
+ for (const property of properties) {
605
+ if (!ts_morph.Node.isPropertyAssignment(property)) continue;
606
+ const name = property.getName().replace(/^['"]|['"]$/g, "");
607
+ if (name.startsWith("[")) continue;
608
+ findUnresolvedInValue(property.getInitializer(), resolved?.[name], path ? `${path}.${name}` : name, out);
609
+ }
610
+ };
611
+ /** A recipe config the build could not fully read — see `findUnresolvedInValue`. */
612
+ const findUnresolvedRecipeStyles = (item) => {
613
+ const node = item.box?.getNode();
614
+ const sourceFile = node?.getSourceFile();
615
+ if (!node || !sourceFile) return [];
616
+ let argument = node;
617
+ if (ts_morph.Node.isCallExpression(argument)) {
618
+ const args = argument.getArguments();
619
+ if (args.length !== 1) return [];
620
+ argument = args[0];
621
+ }
622
+ const losses = [];
623
+ const config = argument ? (0, _bamboocss_extractor.unwrapExpression)(argument) : void 0;
624
+ if (!config || !ts_morph.Node.isObjectLiteralExpression(config)) losses.push({ reason: "unresolvable-value" });
625
+ else findUnresolvedInValue(config, item.data[0], "", losses);
626
+ if (!losses.length) return [];
627
+ const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
628
+ return losses.map((loss) => ({
629
+ column,
630
+ filePath: sourceFile.getFilePath(),
631
+ kind: "recipe",
632
+ line,
633
+ ...loss
634
+ }));
635
+ };
636
+ const findUnresolvedStyles = (item, kind) => {
535
637
  const boxNode = item.box;
536
638
  if (!boxNode) return [];
537
639
  const node = boxNode.getNode();
@@ -563,6 +665,7 @@ const findUnresolvedStyles = (item) => {
563
665
  };
564
666
  return losses.map((loss) => ({
565
667
  ...at,
668
+ kind,
566
669
  ...loss
567
670
  }));
568
671
  };
@@ -617,6 +720,7 @@ var ParserResult = class {
617
720
  const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
618
721
  this.unresolved.push({
619
722
  filePath: sourceFile.getFilePath(),
723
+ kind: "grouped",
620
724
  line,
621
725
  column,
622
726
  reason
@@ -648,12 +752,10 @@ var ParserResult = class {
648
752
  const encoder = this.encoder;
649
753
  const grouped = this.context.config.cssMode === "grouped";
650
754
  const data = result.data.some(Array.isArray) ? result.data.flatMap((obj) => Array.isArray(obj) ? obj : [obj]) : result.data;
651
- if (grouped) {
652
- const unresolved = findUnresolvedStyles(result);
653
- if (unresolved.length) {
654
- this.unresolved.push(...unresolved);
655
- data.forEach((obj) => encoder.processAtomic(obj));
656
- }
755
+ const unresolved = findUnresolvedStyles(result, grouped ? "grouped" : "atomic").filter((entry) => grouped || entry.reason === "unenumerable-keys");
756
+ if (unresolved.length) {
757
+ this.unresolved.push(...unresolved);
758
+ if (grouped) data.forEach((obj) => encoder.processAtomic(obj));
657
759
  }
658
760
  if (!grouped || data.length <= 1) {
659
761
  data.forEach((obj) => grouped ? encoder.processGrouped(obj) : encoder.processAtomic(obj));
@@ -707,14 +809,37 @@ var ParserResult = class {
707
809
  }
708
810
  setCva(result) {
709
811
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
812
+ this.reportUnresolvedRecipe(result);
710
813
  const encoder = this.encoder;
711
814
  result.data.forEach((data) => encoder.processAtomicRecipe(data));
712
815
  }
713
816
  setSva(result) {
714
817
  this.sva.add(this.append(Object.assign({ type: "sva" }, result)));
818
+ this.reportUnresolvedRecipe(result);
715
819
  const encoder = this.encoder;
716
820
  result.data.forEach((data) => encoder.processAtomicSlotRecipe(data));
717
821
  }
822
+ /**
823
+ * Record a recipe config the build could not fully read.
824
+ *
825
+ * Not gated on `cssMode`, unlike the `css()` check in `setCss`. That one exists because
826
+ * grouping names a whole call with one class; this one exists because a recipe is named
827
+ * from a *hash of its config*, which is true in every mode. A declaration the build cannot
828
+ * see changes the hash, so the build emits rules under one name and the browser asks for
829
+ * another, and the element renders with no styles at all.
830
+ *
831
+ * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
832
+ * group and let the runtime's degraded naming land on them; nothing can rescue a diverged
833
+ * hash except an explicit `className`, which is what the message says to reach for.
834
+ */
835
+ reportUnresolvedRecipe(result) {
836
+ if (result.data.every((data) => {
837
+ const className = data?.className;
838
+ return typeof className === "string" && className !== "";
839
+ })) return;
840
+ const unresolved = findUnresolvedRecipeStyles(result);
841
+ if (unresolved.length) this.unresolved.push(...unresolved);
842
+ }
718
843
  setToken(result) {
719
844
  this.token.add(this.append(Object.assign({ type: "token" }, result)));
720
845
  }
@@ -747,7 +872,7 @@ var ParserResult = class {
747
872
  */
748
873
  groupIsExact(result) {
749
874
  if (result.data.length !== 1) return false;
750
- return findUnresolvedStyles(result).length === 0;
875
+ return findUnresolvedStyles(result, "grouped").length === 0;
751
876
  }
752
877
  setRecipe(recipeName, result) {
753
878
  (0, _bamboocss_shared.getOrCreateSet)(this.recipe, recipeName).add(this.append(Object.assign({ type: "recipe" }, result)));
package/dist/index.d.cts CHANGED
@@ -135,6 +135,19 @@ declare class Generator extends Context {
135
135
  //#endregion
136
136
  //#region src/unresolved-styles.d.ts
137
137
  interface UnresolvedStyle {
138
+ /**
139
+ * What the loss costs, which decides how it is explained.
140
+ *
141
+ * - `grouped` — a `css()` call under `cssMode: 'grouped'`. It degrades: the runtime falls
142
+ * back to naming each declaration, and the build emits atomic rules alongside the group
143
+ * so the ones it resolved still apply.
144
+ * - `atomic` — a `css()` call under `cssMode: 'atomic'`. The declarations the build saw
145
+ * still apply; the ones it did not have no rule behind them, so they are simply absent.
146
+ * - `recipe` — a `cva`/`sva` config. There is no degrading. A recipe's classes are named
147
+ * from a hash of its config, so a declaration the build cannot see gives the two sides
148
+ * different names and *every* rule misses.
149
+ */
150
+ kind: 'grouped' | 'atomic' | 'recipe';
138
151
  /** The property the build could not resolve, or `undefined` when only the count differs. */
139
152
  prop?: string;
140
153
  filePath: string;
@@ -152,15 +165,7 @@ interface UnresolvedStyle {
152
165
  */
153
166
  reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys' | 'ambiguous-merge' | 'too-many-combinations';
154
167
  }
155
- /**
156
- * Every property of a `css()` call that will not reach the stylesheet.
157
- *
158
- * Only meaningful under `cssMode: 'grouped'`, where one class names the whole call: a
159
- * property the build cannot resolve does not merely go missing, it changes the class, and
160
- * the element renders with no styles at all. Under `atomic` the same call loses one
161
- * declaration and keeps the rest, which is why this is not reported there.
162
- */
163
- declare const findUnresolvedStyles: (item: ResultItem) => UnresolvedStyle[];
168
+ declare const findUnresolvedStyles: (item: ResultItem, kind: "grouped" | "atomic") => UnresolvedStyle[];
164
169
  //#endregion
165
170
  //#region src/parser-result.d.ts
166
171
  declare class ParserResult implements ParserResultInterface {
@@ -205,6 +210,20 @@ declare class ParserResult implements ParserResultInterface {
205
210
  private callArgumentCount;
206
211
  setCva(result: ResultItem): void;
207
212
  setSva(result: ResultItem): void;
213
+ /**
214
+ * Record a recipe config the build could not fully read.
215
+ *
216
+ * Not gated on `cssMode`, unlike the `css()` check in `setCss`. That one exists because
217
+ * grouping names a whole call with one class; this one exists because a recipe is named
218
+ * from a *hash of its config*, which is true in every mode. A declaration the build cannot
219
+ * see changes the hash, so the build emits rules under one name and the browser asks for
220
+ * another, and the element renders with no styles at all.
221
+ *
222
+ * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
223
+ * group and let the runtime's degraded naming land on them; nothing can rescue a diverged
224
+ * hash except an explicit `className`, which is what the message says to reach for.
225
+ */
226
+ private reportUnresolvedRecipe;
208
227
  setToken(result: ResultItem): void;
209
228
  setViewTransition(result: ResultItem): void;
210
229
  setPattern(name: string, result: ResultItem): void;
package/dist/index.d.mts CHANGED
@@ -135,6 +135,19 @@ declare class Generator extends Context {
135
135
  //#endregion
136
136
  //#region src/unresolved-styles.d.ts
137
137
  interface UnresolvedStyle {
138
+ /**
139
+ * What the loss costs, which decides how it is explained.
140
+ *
141
+ * - `grouped` — a `css()` call under `cssMode: 'grouped'`. It degrades: the runtime falls
142
+ * back to naming each declaration, and the build emits atomic rules alongside the group
143
+ * so the ones it resolved still apply.
144
+ * - `atomic` — a `css()` call under `cssMode: 'atomic'`. The declarations the build saw
145
+ * still apply; the ones it did not have no rule behind them, so they are simply absent.
146
+ * - `recipe` — a `cva`/`sva` config. There is no degrading. A recipe's classes are named
147
+ * from a hash of its config, so a declaration the build cannot see gives the two sides
148
+ * different names and *every* rule misses.
149
+ */
150
+ kind: 'grouped' | 'atomic' | 'recipe';
138
151
  /** The property the build could not resolve, or `undefined` when only the count differs. */
139
152
  prop?: string;
140
153
  filePath: string;
@@ -152,15 +165,7 @@ interface UnresolvedStyle {
152
165
  */
153
166
  reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys' | 'ambiguous-merge' | 'too-many-combinations';
154
167
  }
155
- /**
156
- * Every property of a `css()` call that will not reach the stylesheet.
157
- *
158
- * Only meaningful under `cssMode: 'grouped'`, where one class names the whole call: a
159
- * property the build cannot resolve does not merely go missing, it changes the class, and
160
- * the element renders with no styles at all. Under `atomic` the same call loses one
161
- * declaration and keeps the rest, which is why this is not reported there.
162
- */
163
- declare const findUnresolvedStyles: (item: ResultItem) => UnresolvedStyle[];
168
+ declare const findUnresolvedStyles: (item: ResultItem, kind: "grouped" | "atomic") => UnresolvedStyle[];
164
169
  //#endregion
165
170
  //#region src/parser-result.d.ts
166
171
  declare class ParserResult implements ParserResultInterface {
@@ -205,6 +210,20 @@ declare class ParserResult implements ParserResultInterface {
205
210
  private callArgumentCount;
206
211
  setCva(result: ResultItem): void;
207
212
  setSva(result: ResultItem): void;
213
+ /**
214
+ * Record a recipe config the build could not fully read.
215
+ *
216
+ * Not gated on `cssMode`, unlike the `css()` check in `setCss`. That one exists because
217
+ * grouping names a whole call with one class; this one exists because a recipe is named
218
+ * from a *hash of its config*, which is true in every mode. A declaration the build cannot
219
+ * see changes the hash, so the build emits rules under one name and the browser asks for
220
+ * another, and the element renders with no styles at all.
221
+ *
222
+ * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
223
+ * group and let the runtime's degraded naming land on them; nothing can rescue a diverged
224
+ * hash except an explicit `className`, which is what the message says to reach for.
225
+ */
226
+ private reportUnresolvedRecipe;
208
227
  setToken(result: ResultItem): void;
209
228
  setViewTransition(result: ResultItem): void;
210
229
  setPattern(name: string, result: ResultItem): void;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Node, Project as Project$1, ScriptKind, ts } from "ts-morph";
2
- import { box, clearBoxNodeCache, extract, unbox } from "@bamboocss/extractor";
2
+ import { box, clearBoxNodeCache, extract, unbox, unwrapExpression } from "@bamboocss/extractor";
3
3
  import { BambooError, compact, getOrCreateSet, patternFns } from "@bamboocss/shared";
4
4
  import { logger } from "@bamboocss/logger";
5
5
  import { match } from "ts-pattern";
@@ -530,7 +530,109 @@ const writtenProps = (node) => {
530
530
  * the element renders with no styles at all. Under `atomic` the same call loses one
531
531
  * declaration and keeps the rest, which is why this is not reported there.
532
532
  */
533
- const findUnresolvedStyles = (item) => {
533
+ /**
534
+ * A recipe config the build could not fully read, level by level.
535
+ *
536
+ * `findUnresolvedStyles` reads the *call's* literal, which for a recipe holds `base` and
537
+ * `variants` rather than declarations — so a loss inside `base` is nested out of its reach.
538
+ * This walks the written source against the resolved data instead, and reports three ways a
539
+ * level can lose something:
540
+ *
541
+ * - a key written in the source that never arrived in the data — an unresolvable *value*,
542
+ * which leaves no trace in the box tree because the pair is never recorded at all;
543
+ * - a spread or computed key that contributed no keys beyond those written beside it;
544
+ * - the config not being an object literal at all, so nothing can be compared.
545
+ *
546
+ * Every level is unwrapped first. `as const` and `satisfies` are idiomatic on a recipe
547
+ * config, and reading through them is what the extractor does — a diagnostic that stops at
548
+ * the cast reports nothing for the exact configs most likely to be written that way.
549
+ */
550
+ const findUnresolvedInValue = (node, resolved, path, out) => {
551
+ const value = node ? unwrapExpression(node) : void 0;
552
+ if (!value) return;
553
+ if (Node.isArrayLiteralExpression(value)) {
554
+ value.getElements().forEach((element, index) => {
555
+ const at = path ? `${path}.${index}` : String(index);
556
+ const element_ = resolved?.[index];
557
+ if (Node.isSpreadElement(element) && element_ == null) {
558
+ out.push({
559
+ prop: at || void 0,
560
+ reason: "unenumerable-keys"
561
+ });
562
+ return;
563
+ }
564
+ findUnresolvedInValue(element, element_, at, out);
565
+ });
566
+ return;
567
+ }
568
+ if (!Node.isObjectLiteralExpression(value)) return;
569
+ const properties = value.getProperties();
570
+ const written = [];
571
+ let uncertain = false;
572
+ for (const property of properties) {
573
+ if (Node.isSpreadAssignment(property)) {
574
+ const spread = unwrapExpression(property.getExpression());
575
+ if (Node.isObjectLiteralExpression(spread)) {
576
+ for (const inner of spread.getProperties()) {
577
+ if (!Node.isPropertyAssignment(inner) && !Node.isShorthandPropertyAssignment(inner)) continue;
578
+ const innerName = inner.getName();
579
+ if (!innerName.startsWith("[")) written.push(innerName.replace(/^['"]|['"]$/g, ""));
580
+ }
581
+ continue;
582
+ }
583
+ uncertain = true;
584
+ continue;
585
+ }
586
+ if (!Node.isPropertyAssignment(property) && !Node.isShorthandPropertyAssignment(property)) continue;
587
+ const name = property.getName();
588
+ if (name.startsWith("[")) {
589
+ uncertain = true;
590
+ continue;
591
+ }
592
+ written.push(name.replace(/^['"]|['"]$/g, ""));
593
+ }
594
+ const resolvedKeys = Boolean(resolved) && typeof resolved === "object" ? Object.keys(resolved) : [];
595
+ for (const name of written) if (!resolvedKeys.includes(name)) out.push({
596
+ prop: path ? `${path}.${name}` : name,
597
+ reason: "missing-property"
598
+ });
599
+ if (uncertain && !resolvedKeys.some((key) => !written.includes(key))) out.push({
600
+ prop: path || void 0,
601
+ reason: "unenumerable-keys"
602
+ });
603
+ for (const property of properties) {
604
+ if (!Node.isPropertyAssignment(property)) continue;
605
+ const name = property.getName().replace(/^['"]|['"]$/g, "");
606
+ if (name.startsWith("[")) continue;
607
+ findUnresolvedInValue(property.getInitializer(), resolved?.[name], path ? `${path}.${name}` : name, out);
608
+ }
609
+ };
610
+ /** A recipe config the build could not fully read — see `findUnresolvedInValue`. */
611
+ const findUnresolvedRecipeStyles = (item) => {
612
+ const node = item.box?.getNode();
613
+ const sourceFile = node?.getSourceFile();
614
+ if (!node || !sourceFile) return [];
615
+ let argument = node;
616
+ if (Node.isCallExpression(argument)) {
617
+ const args = argument.getArguments();
618
+ if (args.length !== 1) return [];
619
+ argument = args[0];
620
+ }
621
+ const losses = [];
622
+ const config = argument ? unwrapExpression(argument) : void 0;
623
+ if (!config || !Node.isObjectLiteralExpression(config)) losses.push({ reason: "unresolvable-value" });
624
+ else findUnresolvedInValue(config, item.data[0], "", losses);
625
+ if (!losses.length) return [];
626
+ const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
627
+ return losses.map((loss) => ({
628
+ column,
629
+ filePath: sourceFile.getFilePath(),
630
+ kind: "recipe",
631
+ line,
632
+ ...loss
633
+ }));
634
+ };
635
+ const findUnresolvedStyles = (item, kind) => {
534
636
  const boxNode = item.box;
535
637
  if (!boxNode) return [];
536
638
  const node = boxNode.getNode();
@@ -562,6 +664,7 @@ const findUnresolvedStyles = (item) => {
562
664
  };
563
665
  return losses.map((loss) => ({
564
666
  ...at,
667
+ kind,
565
668
  ...loss
566
669
  }));
567
670
  };
@@ -616,6 +719,7 @@ var ParserResult = class {
616
719
  const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
617
720
  this.unresolved.push({
618
721
  filePath: sourceFile.getFilePath(),
722
+ kind: "grouped",
619
723
  line,
620
724
  column,
621
725
  reason
@@ -647,12 +751,10 @@ var ParserResult = class {
647
751
  const encoder = this.encoder;
648
752
  const grouped = this.context.config.cssMode === "grouped";
649
753
  const data = result.data.some(Array.isArray) ? result.data.flatMap((obj) => Array.isArray(obj) ? obj : [obj]) : result.data;
650
- if (grouped) {
651
- const unresolved = findUnresolvedStyles(result);
652
- if (unresolved.length) {
653
- this.unresolved.push(...unresolved);
654
- data.forEach((obj) => encoder.processAtomic(obj));
655
- }
754
+ const unresolved = findUnresolvedStyles(result, grouped ? "grouped" : "atomic").filter((entry) => grouped || entry.reason === "unenumerable-keys");
755
+ if (unresolved.length) {
756
+ this.unresolved.push(...unresolved);
757
+ if (grouped) data.forEach((obj) => encoder.processAtomic(obj));
656
758
  }
657
759
  if (!grouped || data.length <= 1) {
658
760
  data.forEach((obj) => grouped ? encoder.processGrouped(obj) : encoder.processAtomic(obj));
@@ -706,14 +808,37 @@ var ParserResult = class {
706
808
  }
707
809
  setCva(result) {
708
810
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
811
+ this.reportUnresolvedRecipe(result);
709
812
  const encoder = this.encoder;
710
813
  result.data.forEach((data) => encoder.processAtomicRecipe(data));
711
814
  }
712
815
  setSva(result) {
713
816
  this.sva.add(this.append(Object.assign({ type: "sva" }, result)));
817
+ this.reportUnresolvedRecipe(result);
714
818
  const encoder = this.encoder;
715
819
  result.data.forEach((data) => encoder.processAtomicSlotRecipe(data));
716
820
  }
821
+ /**
822
+ * Record a recipe config the build could not fully read.
823
+ *
824
+ * Not gated on `cssMode`, unlike the `css()` check in `setCss`. That one exists because
825
+ * grouping names a whole call with one class; this one exists because a recipe is named
826
+ * from a *hash of its config*, which is true in every mode. A declaration the build cannot
827
+ * see changes the hash, so the build emits rules under one name and the browser asks for
828
+ * another, and the element renders with no styles at all.
829
+ *
830
+ * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
831
+ * group and let the runtime's degraded naming land on them; nothing can rescue a diverged
832
+ * hash except an explicit `className`, which is what the message says to reach for.
833
+ */
834
+ reportUnresolvedRecipe(result) {
835
+ if (result.data.every((data) => {
836
+ const className = data?.className;
837
+ return typeof className === "string" && className !== "";
838
+ })) return;
839
+ const unresolved = findUnresolvedRecipeStyles(result);
840
+ if (unresolved.length) this.unresolved.push(...unresolved);
841
+ }
717
842
  setToken(result) {
718
843
  this.token.add(this.append(Object.assign({ type: "token" }, result)));
719
844
  }
@@ -746,7 +871,7 @@ var ParserResult = class {
746
871
  */
747
872
  groupIsExact(result) {
748
873
  if (result.data.length !== 1) return false;
749
- return findUnresolvedStyles(result).length === 0;
874
+ return findUnresolvedStyles(result, "grouped").length === 0;
750
875
  }
751
876
  setRecipe(recipeName, result) {
752
877
  getOrCreateSet(this.recipe, recipeName).add(this.append(Object.assign({ type: "recipe" }, result)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/parser",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
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/config": "^1.16.0",
38
- "@bamboocss/core": "^1.16.0",
39
- "@bamboocss/extractor": "1.16.0",
40
- "@bamboocss/logger": "1.16.0",
41
- "@bamboocss/shared": "1.16.0",
42
- "@bamboocss/types": "1.16.0"
37
+ "@bamboocss/config": "^1.17.0",
38
+ "@bamboocss/core": "^1.17.0",
39
+ "@bamboocss/extractor": "1.17.0",
40
+ "@bamboocss/logger": "1.17.0",
41
+ "@bamboocss/shared": "1.17.0",
42
+ "@bamboocss/types": "1.17.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@bamboocss/generator": "1.16.0",
46
- "@bamboocss/plugin-svelte": "1.16.0",
47
- "@bamboocss/plugin-vue": "1.16.0"
45
+ "@bamboocss/generator": "1.17.0",
46
+ "@bamboocss/plugin-svelte": "1.17.0",
47
+ "@bamboocss/plugin-vue": "1.17.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsdown src/index.ts --format=esm,cjs --dts",