@bamboocss/parser 1.20.4 → 1.22.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
@@ -523,14 +523,7 @@ const writtenProps = (node) => {
523
523
  uncertain
524
524
  };
525
525
  };
526
- /**
527
- * Every property of a `css()` call that will not reach the stylesheet.
528
- *
529
- * Only meaningful under `cssMode: 'grouped'`, where one class names the whole call: a
530
- * property the build cannot resolve does not merely go missing, it changes the class, and
531
- * the element renders with no styles at all. Under `atomic` the same call loses one
532
- * declaration and keeps the rest, which is why this is not reported there.
533
- */
526
+ /** Every property of a `css()` call that will not reach the stylesheet. */
534
527
  /**
535
528
  * A recipe config the build could not fully read, level by level.
536
529
  *
@@ -675,12 +668,6 @@ const hasKeyOutside = (resolved, names) => {
675
668
  };
676
669
  //#endregion
677
670
  //#region src/parser-result.ts
678
- function cartesian(arrays) {
679
- if (arrays.length === 0) return [[]];
680
- const [first, ...rest] = arrays;
681
- const restProduct = cartesian(rest);
682
- return first.flatMap((item) => restProduct.map((combo) => [item, ...combo]));
683
- }
684
671
  var ParserResult = class {
685
672
  context;
686
673
  /** Ordered list of all ResultItem */
@@ -697,35 +684,14 @@ var ParserResult = class {
697
684
  /**
698
685
  * `css()` calls whose styles the build could not fully see.
699
686
  *
700
- * Only collected under `cssMode: 'grouped'`, where one class names the whole call, so a
701
- * property the build cannot resolve changes the class rather than dropping a declaration
702
- * from it — and the element renders with no styles at all. Under `atomic` the same call
703
- * keeps everything the build did resolve, which is not worth interrupting a build over.
687
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
688
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
704
689
  */
705
690
  unresolved = [];
706
691
  constructor(context, encoder) {
707
692
  this.context = context;
708
693
  this.encoder = encoder ?? context.encoder;
709
694
  }
710
- /**
711
- * Record a call whose styles the build could not fully see, at the call's own position.
712
- *
713
- * Used for losses the box tree cannot show — a ternary past the combination cap emits
714
- * fragments rather than whole objects, and every individual box in it resolved fine.
715
- */
716
- reportUnresolved(result, reason) {
717
- const node = result.box?.getNode();
718
- const sourceFile = node?.getSourceFile();
719
- if (!node || !sourceFile) return;
720
- const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
721
- this.unresolved.push({
722
- filePath: sourceFile.getFilePath(),
723
- kind: "grouped",
724
- line,
725
- column,
726
- reason
727
- });
728
- }
729
695
  append(result) {
730
696
  this.all.push(result);
731
697
  return result;
@@ -750,62 +716,10 @@ var ParserResult = class {
750
716
  setCss(result) {
751
717
  this.css.add(this.append(Object.assign({ type: "css" }, result)));
752
718
  const encoder = this.encoder;
753
- const grouped = this.context.config.cssMode === "grouped";
754
719
  const data = result.data.some(Array.isArray) ? result.data.flatMap((obj) => Array.isArray(obj) ? obj : [obj]) : result.data;
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));
759
- }
760
- if (!grouped || data.length <= 1) {
761
- data.forEach((obj) => grouped ? encoder.processGrouped(obj) : encoder.processAtomic(obj));
762
- return;
763
- }
764
- const keyCounts = /* @__PURE__ */ new Map();
765
- for (const obj of data) for (const key of Object.keys(obj)) keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
766
- if (!Array.from(keyCounts.values()).some((c) => c > 1)) {
767
- encoder.processGroupedMerge(data);
768
- return;
769
- }
770
- if (this.callArgumentCount(result) > 1) {
771
- this.reportUnresolved(result, "ambiguous-merge");
772
- data.forEach((obj) => encoder.processAtomic(obj));
773
- }
774
- const overlappingKeys = /* @__PURE__ */ new Set();
775
- keyCounts.forEach((count, key) => {
776
- if (count > 1) overlappingKeys.add(key);
777
- });
778
- const baseEntries = [];
779
- const branchEntries = [];
780
- for (const obj of data) if (Object.keys(obj).some((k) => overlappingKeys.has(k))) branchEntries.push(obj);
781
- else baseEntries.push(obj);
782
- const branchGroups = /* @__PURE__ */ new Map();
783
- for (const entry of branchEntries) {
784
- const keySet = Object.keys(entry).sort().join("\0");
785
- const group = branchGroups.get(keySet) || [];
786
- group.push(entry);
787
- branchGroups.set(keySet, group);
788
- }
789
- const groupArrays = Array.from(branchGroups.values());
790
- if (groupArrays.reduce((acc, g) => acc * g.length, 1) > 32) {
791
- this.reportUnresolved(result, "too-many-combinations");
792
- data.forEach((obj) => {
793
- encoder.processGrouped(obj);
794
- encoder.processAtomic(obj);
795
- });
796
- return;
797
- }
798
- for (const combo of cartesian(groupArrays)) encoder.processGroupedMerge([...baseEntries, ...combo]);
799
- }
800
- /**
801
- * How many arguments the call this result came from was written with.
802
- *
803
- * Returns 1 for anything that is not a call — a JSX element, or a box that lost its node —
804
- * since the question only separates operands from branches and neither has operands.
805
- */
806
- callArgumentCount(result) {
807
- const node = result.box?.getNode();
808
- return node && ts_morph.Node.isCallExpression(node) ? node.getArguments().length : 1;
720
+ const unresolved = findUnresolvedStyles(result, "atomic").filter((entry) => entry.reason === "unenumerable-keys");
721
+ if (unresolved.length) this.unresolved.push(...unresolved);
722
+ data.forEach((obj) => encoder.processAtomic(obj));
809
723
  }
810
724
  setCva(result) {
811
725
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
@@ -822,10 +736,9 @@ var ParserResult = class {
822
736
  /**
823
737
  * Record a recipe config the build could not fully read.
824
738
  *
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
739
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
740
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
741
+ * changes the hash, so the build emits rules under one name and the browser asks for
829
742
  * another, and the element renders with no styles at all.
830
743
  *
831
744
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -853,26 +766,7 @@ var ParserResult = class {
853
766
  type: "pattern",
854
767
  name
855
768
  }, result)));
856
- const encoder = this.encoder;
857
- const grouped = this.context.config.cssMode === "grouped";
858
- result.data.forEach((obj) => encoder.processPattern(name, obj, grouped));
859
- if (grouped && !this.groupIsExact(result)) result.data.forEach((obj) => encoder.processPattern(name, obj, false));
860
- }
861
- /**
862
- * Whether the group encoded for this result is the one the runtime will ask for.
863
- *
864
- * True only when the build saw the whole thing at once: one style object, with every
865
- * value in it resolved. Several objects means the runtime merges them into a call this
866
- * never encoded — `setCss` reconstructs those combinations, and nothing else does — and
867
- * an unresolved value means the merge would not have matched anyway.
868
- *
869
- * Answering "no" costs a call site its atomic rules, which is CSS that duplicates the
870
- * group. Answering a wrong "yes" costs the element every style it has, so this is
871
- * deliberately conservative.
872
- */
873
- groupIsExact(result) {
874
- if (result.data.length !== 1) return false;
875
- return findUnresolvedStyles(result, "grouped").length === 0;
769
+ result.data.forEach((obj) => this.encoder.processPattern(name, obj));
876
770
  }
877
771
  setRecipe(recipeName, result) {
878
772
  (0, _bamboocss_shared.getOrCreateSet)(this.recipe, recipeName).add(this.append(Object.assign({ type: "recipe" }, result)));
package/dist/index.d.cts CHANGED
@@ -38,7 +38,7 @@ declare class Generator extends Context {
38
38
  *
39
39
  * `keep` carries references this cannot see for itself; see `collectTokenReferences`.
40
40
  */
41
- pruneTokens: (sheet: Stylesheet, keep?: Set<string>) => {
41
+ pruneTokens: (sheet: Stylesheet, keep?: Set<string>, tokensReachableFromJs?: boolean) => {
42
42
  removed: number;
43
43
  kept: number;
44
44
  removedProperties?: undefined;
@@ -47,6 +47,18 @@ declare class Generator extends Context {
47
47
  removedProperties: number;
48
48
  kept: number;
49
49
  };
50
+ /**
51
+ * Drop the parts of the reset that style elements the source never renders.
52
+ *
53
+ * Off unless asked for. Unlike the token and keyframe passes there is no way to prove this
54
+ * from the build: an element rendered by a dependency, by `dangerouslySetInnerHTML` or by
55
+ * markdown is invisible to a scan of your own source, and the failure is an element quietly
56
+ * losing its reset rather than anything that reports itself.
57
+ */
58
+ prunePreflight: (sheet: Stylesheet, rendered: Set<string>) => {
59
+ removedRules: number;
60
+ removedParts: number;
61
+ } | undefined;
50
62
  /**
51
63
  * Drop `@keyframes` nothing can reach. Same completeness requirement as
52
64
  * `pruneTokens`: the sheet has to hold the whole stylesheet, or every keyframe looks
@@ -98,20 +110,6 @@ declare class Generator extends Context {
98
110
  */
99
111
  private getAlwaysKeptTokenVars;
100
112
  getParserCss: (decoder: StyleDecoder) => string;
101
- /**
102
- * The grouped class names this build emitted a rule for.
103
- *
104
- * Derived from the encoder rather than the decoder, so it is available as soon as
105
- * extraction finishes and before a stylesheet exists. Both sides go through
106
- * `groupClassName`, which is the same function the browser runtime calls — a registry
107
- * built any other way would be a third spelling of a name that already has two.
108
- *
109
- * Unescaped, unlike `StyleDecoder`'s class names: this is compared against what `css()`
110
- * returns into a `class` attribute, not against a selector. A grouped class is an opaque
111
- * hash, so the two only differ in principle, but the principle is the one that matters
112
- * here — the registry is only useful if it holds exactly what the runtime will ask about.
113
- */
114
- getGroupRegistry: () => string[];
115
113
  getCss: (stylesheet?: Stylesheet) => string;
116
114
  /**
117
115
  * Get CSS for a specific layer from the stylesheet
@@ -143,16 +141,13 @@ interface UnresolvedStyle {
143
141
  /**
144
142
  * What the loss costs, which decides how it is explained.
145
143
  *
146
- * - `grouped` — a `css()` call under `cssMode: 'grouped'`. It degrades: the runtime falls
147
- * back to naming each declaration, and the build emits atomic rules alongside the group
148
- * so the ones it resolved still apply.
149
- * - `atomic` — a `css()` call under `cssMode: 'atomic'`. The declarations the build saw
150
- * still apply; the ones it did not have no rule behind them, so they are simply absent.
144
+ * - `atomic` — a `css()` call. The declarations the build saw still apply; the ones it
145
+ * did not have no rule behind them, so they are simply absent.
151
146
  * - `recipe` — a `cva`/`sva` config. There is no degrading. A recipe's classes are named
152
147
  * from a hash of its config, so a declaration the build cannot see gives the two sides
153
148
  * different names and *every* rule misses.
154
149
  */
155
- kind: 'grouped' | 'atomic' | 'recipe';
150
+ kind: 'atomic' | 'recipe';
156
151
  /** The property the build could not resolve, or `undefined` when only the count differs. */
157
152
  prop?: string;
158
153
  filePath: string;
@@ -164,13 +159,12 @@ interface UnresolvedStyle {
164
159
  * - `unresolvable-value` — a value it could not evaluate.
165
160
  * - `missing-property` — a key that never arrived in the box tree at all.
166
161
  * - `unenumerable-keys` — a spread or computed key, so it cannot say what the call sets.
167
- * - `ambiguous-merge` — two arguments setting one property, which it cannot tell from a
168
162
  * pair of alternatives.
169
163
  * - `too-many-combinations` — more ternary branches than it will enumerate.
170
164
  */
171
- reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys' | 'ambiguous-merge' | 'too-many-combinations';
165
+ reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys';
172
166
  }
173
- declare const findUnresolvedStyles: (item: ResultItem, kind: "grouped" | "atomic") => UnresolvedStyle[];
167
+ declare const findUnresolvedStyles: (item: ResultItem, kind: "atomic") => UnresolvedStyle[];
174
168
  //#endregion
175
169
  //#region src/parser-result.d.ts
176
170
  declare class ParserResult implements ParserResultInterface {
@@ -189,39 +183,22 @@ declare class ParserResult implements ParserResultInterface {
189
183
  /**
190
184
  * `css()` calls whose styles the build could not fully see.
191
185
  *
192
- * Only collected under `cssMode: 'grouped'`, where one class names the whole call, so a
193
- * property the build cannot resolve changes the class rather than dropping a declaration
194
- * from it — and the element renders with no styles at all. Under `atomic` the same call
195
- * keeps everything the build did resolve, which is not worth interrupting a build over.
186
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
187
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
196
188
  */
197
189
  unresolved: UnresolvedStyle[];
198
190
  constructor(context: ParserOptions, encoder?: ParserOptions['encoder']);
199
- /**
200
- * Record a call whose styles the build could not fully see, at the call's own position.
201
- *
202
- * Used for losses the box tree cannot show — a ternary past the combination cap emits
203
- * fragments rather than whole objects, and every individual box in it resolved fine.
204
- */
205
- private reportUnresolved;
206
191
  append(result: ResultItem): ResultItem;
207
192
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
208
193
  setCss(result: ResultItem): void;
209
- /**
210
- * How many arguments the call this result came from was written with.
211
- *
212
- * Returns 1 for anything that is not a call — a JSX element, or a box that lost its node —
213
- * since the question only separates operands from branches and neither has operands.
214
- */
215
- private callArgumentCount;
216
194
  setCva(result: ResultItem): void;
217
195
  setSva(result: ResultItem): void;
218
196
  /**
219
197
  * Record a recipe config the build could not fully read.
220
198
  *
221
- * Not gated on `cssMode`, unlike the `css()` check in `setCss`. That one exists because
222
- * grouping names a whole call with one class; this one exists because a recipe is named
223
- * from a *hash of its config*, which is true in every mode. A declaration the build cannot
224
- * see changes the hash, so the build emits rules under one name and the browser asks for
199
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
200
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
201
+ * changes the hash, so the build emits rules under one name and the browser asks for
225
202
  * another, and the element renders with no styles at all.
226
203
  *
227
204
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -232,19 +209,6 @@ declare class ParserResult implements ParserResultInterface {
232
209
  setToken(result: ResultItem): void;
233
210
  setViewTransition(result: ResultItem): void;
234
211
  setPattern(name: string, result: ResultItem): void;
235
- /**
236
- * Whether the group encoded for this result is the one the runtime will ask for.
237
- *
238
- * True only when the build saw the whole thing at once: one style object, with every
239
- * value in it resolved. Several objects means the runtime merges them into a call this
240
- * never encoded — `setCss` reconstructs those combinations, and nothing else does — and
241
- * an unresolved value means the merge would not have matched anyway.
242
- *
243
- * Answering "no" costs a call site its atomic rules, which is CSS that duplicates the
244
- * group. Answering a wrong "yes" costs the element every style it has, so this is
245
- * deliberately conservative.
246
- */
247
- private groupIsExact;
248
212
  setRecipe(recipeName: string, result: ResultItem): void;
249
213
  isEmpty(): boolean;
250
214
  setFilePath(filePath: string): this;
package/dist/index.d.mts CHANGED
@@ -38,7 +38,7 @@ declare class Generator extends Context {
38
38
  *
39
39
  * `keep` carries references this cannot see for itself; see `collectTokenReferences`.
40
40
  */
41
- pruneTokens: (sheet: Stylesheet, keep?: Set<string>) => {
41
+ pruneTokens: (sheet: Stylesheet, keep?: Set<string>, tokensReachableFromJs?: boolean) => {
42
42
  removed: number;
43
43
  kept: number;
44
44
  removedProperties?: undefined;
@@ -47,6 +47,18 @@ declare class Generator extends Context {
47
47
  removedProperties: number;
48
48
  kept: number;
49
49
  };
50
+ /**
51
+ * Drop the parts of the reset that style elements the source never renders.
52
+ *
53
+ * Off unless asked for. Unlike the token and keyframe passes there is no way to prove this
54
+ * from the build: an element rendered by a dependency, by `dangerouslySetInnerHTML` or by
55
+ * markdown is invisible to a scan of your own source, and the failure is an element quietly
56
+ * losing its reset rather than anything that reports itself.
57
+ */
58
+ prunePreflight: (sheet: Stylesheet, rendered: Set<string>) => {
59
+ removedRules: number;
60
+ removedParts: number;
61
+ } | undefined;
50
62
  /**
51
63
  * Drop `@keyframes` nothing can reach. Same completeness requirement as
52
64
  * `pruneTokens`: the sheet has to hold the whole stylesheet, or every keyframe looks
@@ -98,20 +110,6 @@ declare class Generator extends Context {
98
110
  */
99
111
  private getAlwaysKeptTokenVars;
100
112
  getParserCss: (decoder: StyleDecoder) => string;
101
- /**
102
- * The grouped class names this build emitted a rule for.
103
- *
104
- * Derived from the encoder rather than the decoder, so it is available as soon as
105
- * extraction finishes and before a stylesheet exists. Both sides go through
106
- * `groupClassName`, which is the same function the browser runtime calls — a registry
107
- * built any other way would be a third spelling of a name that already has two.
108
- *
109
- * Unescaped, unlike `StyleDecoder`'s class names: this is compared against what `css()`
110
- * returns into a `class` attribute, not against a selector. A grouped class is an opaque
111
- * hash, so the two only differ in principle, but the principle is the one that matters
112
- * here — the registry is only useful if it holds exactly what the runtime will ask about.
113
- */
114
- getGroupRegistry: () => string[];
115
113
  getCss: (stylesheet?: Stylesheet) => string;
116
114
  /**
117
115
  * Get CSS for a specific layer from the stylesheet
@@ -143,16 +141,13 @@ interface UnresolvedStyle {
143
141
  /**
144
142
  * What the loss costs, which decides how it is explained.
145
143
  *
146
- * - `grouped` — a `css()` call under `cssMode: 'grouped'`. It degrades: the runtime falls
147
- * back to naming each declaration, and the build emits atomic rules alongside the group
148
- * so the ones it resolved still apply.
149
- * - `atomic` — a `css()` call under `cssMode: 'atomic'`. The declarations the build saw
150
- * still apply; the ones it did not have no rule behind them, so they are simply absent.
144
+ * - `atomic` — a `css()` call. The declarations the build saw still apply; the ones it
145
+ * did not have no rule behind them, so they are simply absent.
151
146
  * - `recipe` — a `cva`/`sva` config. There is no degrading. A recipe's classes are named
152
147
  * from a hash of its config, so a declaration the build cannot see gives the two sides
153
148
  * different names and *every* rule misses.
154
149
  */
155
- kind: 'grouped' | 'atomic' | 'recipe';
150
+ kind: 'atomic' | 'recipe';
156
151
  /** The property the build could not resolve, or `undefined` when only the count differs. */
157
152
  prop?: string;
158
153
  filePath: string;
@@ -164,13 +159,12 @@ interface UnresolvedStyle {
164
159
  * - `unresolvable-value` — a value it could not evaluate.
165
160
  * - `missing-property` — a key that never arrived in the box tree at all.
166
161
  * - `unenumerable-keys` — a spread or computed key, so it cannot say what the call sets.
167
- * - `ambiguous-merge` — two arguments setting one property, which it cannot tell from a
168
162
  * pair of alternatives.
169
163
  * - `too-many-combinations` — more ternary branches than it will enumerate.
170
164
  */
171
- reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys' | 'ambiguous-merge' | 'too-many-combinations';
165
+ reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys';
172
166
  }
173
- declare const findUnresolvedStyles: (item: ResultItem, kind: "grouped" | "atomic") => UnresolvedStyle[];
167
+ declare const findUnresolvedStyles: (item: ResultItem, kind: "atomic") => UnresolvedStyle[];
174
168
  //#endregion
175
169
  //#region src/parser-result.d.ts
176
170
  declare class ParserResult implements ParserResultInterface {
@@ -189,39 +183,22 @@ declare class ParserResult implements ParserResultInterface {
189
183
  /**
190
184
  * `css()` calls whose styles the build could not fully see.
191
185
  *
192
- * Only collected under `cssMode: 'grouped'`, where one class names the whole call, so a
193
- * property the build cannot resolve changes the class rather than dropping a declaration
194
- * from it — and the element renders with no styles at all. Under `atomic` the same call
195
- * keeps everything the build did resolve, which is not worth interrupting a build over.
186
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
187
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
196
188
  */
197
189
  unresolved: UnresolvedStyle[];
198
190
  constructor(context: ParserOptions, encoder?: ParserOptions['encoder']);
199
- /**
200
- * Record a call whose styles the build could not fully see, at the call's own position.
201
- *
202
- * Used for losses the box tree cannot show — a ternary past the combination cap emits
203
- * fragments rather than whole objects, and every individual box in it resolved fine.
204
- */
205
- private reportUnresolved;
206
191
  append(result: ResultItem): ResultItem;
207
192
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
208
193
  setCss(result: ResultItem): void;
209
- /**
210
- * How many arguments the call this result came from was written with.
211
- *
212
- * Returns 1 for anything that is not a call — a JSX element, or a box that lost its node —
213
- * since the question only separates operands from branches and neither has operands.
214
- */
215
- private callArgumentCount;
216
194
  setCva(result: ResultItem): void;
217
195
  setSva(result: ResultItem): void;
218
196
  /**
219
197
  * Record a recipe config the build could not fully read.
220
198
  *
221
- * Not gated on `cssMode`, unlike the `css()` check in `setCss`. That one exists because
222
- * grouping names a whole call with one class; this one exists because a recipe is named
223
- * from a *hash of its config*, which is true in every mode. A declaration the build cannot
224
- * see changes the hash, so the build emits rules under one name and the browser asks for
199
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
200
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
201
+ * changes the hash, so the build emits rules under one name and the browser asks for
225
202
  * another, and the element renders with no styles at all.
226
203
  *
227
204
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -232,19 +209,6 @@ declare class ParserResult implements ParserResultInterface {
232
209
  setToken(result: ResultItem): void;
233
210
  setViewTransition(result: ResultItem): void;
234
211
  setPattern(name: string, result: ResultItem): void;
235
- /**
236
- * Whether the group encoded for this result is the one the runtime will ask for.
237
- *
238
- * True only when the build saw the whole thing at once: one style object, with every
239
- * value in it resolved. Several objects means the runtime merges them into a call this
240
- * never encoded — `setCss` reconstructs those combinations, and nothing else does — and
241
- * an unresolved value means the merge would not have matched anyway.
242
- *
243
- * Answering "no" costs a call site its atomic rules, which is CSS that duplicates the
244
- * group. Answering a wrong "yes" costs the element every style it has, so this is
245
- * deliberately conservative.
246
- */
247
- private groupIsExact;
248
212
  setRecipe(recipeName: string, result: ResultItem): void;
249
213
  isEmpty(): boolean;
250
214
  setFilePath(filePath: string): this;
package/dist/index.mjs CHANGED
@@ -522,14 +522,7 @@ const writtenProps = (node) => {
522
522
  uncertain
523
523
  };
524
524
  };
525
- /**
526
- * Every property of a `css()` call that will not reach the stylesheet.
527
- *
528
- * Only meaningful under `cssMode: 'grouped'`, where one class names the whole call: a
529
- * property the build cannot resolve does not merely go missing, it changes the class, and
530
- * the element renders with no styles at all. Under `atomic` the same call loses one
531
- * declaration and keeps the rest, which is why this is not reported there.
532
- */
525
+ /** Every property of a `css()` call that will not reach the stylesheet. */
533
526
  /**
534
527
  * A recipe config the build could not fully read, level by level.
535
528
  *
@@ -674,12 +667,6 @@ const hasKeyOutside = (resolved, names) => {
674
667
  };
675
668
  //#endregion
676
669
  //#region src/parser-result.ts
677
- function cartesian(arrays) {
678
- if (arrays.length === 0) return [[]];
679
- const [first, ...rest] = arrays;
680
- const restProduct = cartesian(rest);
681
- return first.flatMap((item) => restProduct.map((combo) => [item, ...combo]));
682
- }
683
670
  var ParserResult = class {
684
671
  context;
685
672
  /** Ordered list of all ResultItem */
@@ -696,35 +683,14 @@ var ParserResult = class {
696
683
  /**
697
684
  * `css()` calls whose styles the build could not fully see.
698
685
  *
699
- * Only collected under `cssMode: 'grouped'`, where one class names the whole call, so a
700
- * property the build cannot resolve changes the class rather than dropping a declaration
701
- * from it — and the element renders with no styles at all. Under `atomic` the same call
702
- * keeps everything the build did resolve, which is not worth interrupting a build over.
686
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
687
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
703
688
  */
704
689
  unresolved = [];
705
690
  constructor(context, encoder) {
706
691
  this.context = context;
707
692
  this.encoder = encoder ?? context.encoder;
708
693
  }
709
- /**
710
- * Record a call whose styles the build could not fully see, at the call's own position.
711
- *
712
- * Used for losses the box tree cannot show — a ternary past the combination cap emits
713
- * fragments rather than whole objects, and every individual box in it resolved fine.
714
- */
715
- reportUnresolved(result, reason) {
716
- const node = result.box?.getNode();
717
- const sourceFile = node?.getSourceFile();
718
- if (!node || !sourceFile) return;
719
- const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
720
- this.unresolved.push({
721
- filePath: sourceFile.getFilePath(),
722
- kind: "grouped",
723
- line,
724
- column,
725
- reason
726
- });
727
- }
728
694
  append(result) {
729
695
  this.all.push(result);
730
696
  return result;
@@ -749,62 +715,10 @@ var ParserResult = class {
749
715
  setCss(result) {
750
716
  this.css.add(this.append(Object.assign({ type: "css" }, result)));
751
717
  const encoder = this.encoder;
752
- const grouped = this.context.config.cssMode === "grouped";
753
718
  const data = result.data.some(Array.isArray) ? result.data.flatMap((obj) => Array.isArray(obj) ? obj : [obj]) : result.data;
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));
758
- }
759
- if (!grouped || data.length <= 1) {
760
- data.forEach((obj) => grouped ? encoder.processGrouped(obj) : encoder.processAtomic(obj));
761
- return;
762
- }
763
- const keyCounts = /* @__PURE__ */ new Map();
764
- for (const obj of data) for (const key of Object.keys(obj)) keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
765
- if (!Array.from(keyCounts.values()).some((c) => c > 1)) {
766
- encoder.processGroupedMerge(data);
767
- return;
768
- }
769
- if (this.callArgumentCount(result) > 1) {
770
- this.reportUnresolved(result, "ambiguous-merge");
771
- data.forEach((obj) => encoder.processAtomic(obj));
772
- }
773
- const overlappingKeys = /* @__PURE__ */ new Set();
774
- keyCounts.forEach((count, key) => {
775
- if (count > 1) overlappingKeys.add(key);
776
- });
777
- const baseEntries = [];
778
- const branchEntries = [];
779
- for (const obj of data) if (Object.keys(obj).some((k) => overlappingKeys.has(k))) branchEntries.push(obj);
780
- else baseEntries.push(obj);
781
- const branchGroups = /* @__PURE__ */ new Map();
782
- for (const entry of branchEntries) {
783
- const keySet = Object.keys(entry).sort().join("\0");
784
- const group = branchGroups.get(keySet) || [];
785
- group.push(entry);
786
- branchGroups.set(keySet, group);
787
- }
788
- const groupArrays = Array.from(branchGroups.values());
789
- if (groupArrays.reduce((acc, g) => acc * g.length, 1) > 32) {
790
- this.reportUnresolved(result, "too-many-combinations");
791
- data.forEach((obj) => {
792
- encoder.processGrouped(obj);
793
- encoder.processAtomic(obj);
794
- });
795
- return;
796
- }
797
- for (const combo of cartesian(groupArrays)) encoder.processGroupedMerge([...baseEntries, ...combo]);
798
- }
799
- /**
800
- * How many arguments the call this result came from was written with.
801
- *
802
- * Returns 1 for anything that is not a call — a JSX element, or a box that lost its node —
803
- * since the question only separates operands from branches and neither has operands.
804
- */
805
- callArgumentCount(result) {
806
- const node = result.box?.getNode();
807
- return node && Node.isCallExpression(node) ? node.getArguments().length : 1;
719
+ const unresolved = findUnresolvedStyles(result, "atomic").filter((entry) => entry.reason === "unenumerable-keys");
720
+ if (unresolved.length) this.unresolved.push(...unresolved);
721
+ data.forEach((obj) => encoder.processAtomic(obj));
808
722
  }
809
723
  setCva(result) {
810
724
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
@@ -821,10 +735,9 @@ var ParserResult = class {
821
735
  /**
822
736
  * Record a recipe config the build could not fully read.
823
737
  *
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
738
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
739
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
740
+ * changes the hash, so the build emits rules under one name and the browser asks for
828
741
  * another, and the element renders with no styles at all.
829
742
  *
830
743
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -852,26 +765,7 @@ var ParserResult = class {
852
765
  type: "pattern",
853
766
  name
854
767
  }, result)));
855
- const encoder = this.encoder;
856
- const grouped = this.context.config.cssMode === "grouped";
857
- result.data.forEach((obj) => encoder.processPattern(name, obj, grouped));
858
- if (grouped && !this.groupIsExact(result)) result.data.forEach((obj) => encoder.processPattern(name, obj, false));
859
- }
860
- /**
861
- * Whether the group encoded for this result is the one the runtime will ask for.
862
- *
863
- * True only when the build saw the whole thing at once: one style object, with every
864
- * value in it resolved. Several objects means the runtime merges them into a call this
865
- * never encoded — `setCss` reconstructs those combinations, and nothing else does — and
866
- * an unresolved value means the merge would not have matched anyway.
867
- *
868
- * Answering "no" costs a call site its atomic rules, which is CSS that duplicates the
869
- * group. Answering a wrong "yes" costs the element every style it has, so this is
870
- * deliberately conservative.
871
- */
872
- groupIsExact(result) {
873
- if (result.data.length !== 1) return false;
874
- return findUnresolvedStyles(result, "grouped").length === 0;
768
+ result.data.forEach((obj) => this.encoder.processPattern(name, obj));
875
769
  }
876
770
  setRecipe(recipeName, result) {
877
771
  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.20.4",
3
+ "version": "1.22.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.20.4",
38
- "@bamboocss/core": "^1.20.4",
39
- "@bamboocss/extractor": "1.20.4",
40
- "@bamboocss/logger": "1.20.4",
41
- "@bamboocss/shared": "1.20.4",
42
- "@bamboocss/types": "1.20.4"
37
+ "@bamboocss/config": "^1.22.0",
38
+ "@bamboocss/core": "^1.22.0",
39
+ "@bamboocss/extractor": "1.22.0",
40
+ "@bamboocss/logger": "1.22.0",
41
+ "@bamboocss/shared": "1.22.0",
42
+ "@bamboocss/types": "1.22.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@bamboocss/generator": "1.20.4",
46
- "@bamboocss/plugin-svelte": "1.20.4",
47
- "@bamboocss/plugin-vue": "1.20.4"
45
+ "@bamboocss/generator": "1.22.0",
46
+ "@bamboocss/plugin-svelte": "1.22.0",
47
+ "@bamboocss/plugin-vue": "1.22.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsdown src/index.ts --format=esm,cjs --dts",