@bamboocss/parser 1.21.0 → 1.23.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
@@ -65,6 +65,7 @@ function classifyProject(ctx, resultMap) {
65
65
  const { item, kind, filepath, localMaps } = opts;
66
66
  if (!item.box || _bamboocss_extractor.box.isUnresolvable(item.box)) return;
67
67
  if (!item.data) return;
68
+ if (item.type === "cva-call") return;
68
69
  const componentReportItem = {
69
70
  componentIndex: String(componentIndex++),
70
71
  componentName: item.name,
@@ -523,14 +524,7 @@ const writtenProps = (node) => {
523
524
  uncertain
524
525
  };
525
526
  };
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
- */
527
+ /** Every property of a `css()` call that will not reach the stylesheet. */
534
528
  /**
535
529
  * A recipe config the build could not fully read, level by level.
536
530
  *
@@ -675,18 +669,13 @@ const hasKeyOutside = (resolved, names) => {
675
669
  };
676
670
  //#endregion
677
671
  //#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
672
  var ParserResult = class {
685
673
  context;
686
674
  /** Ordered list of all ResultItem */
687
675
  all = [];
688
676
  css = /* @__PURE__ */ new Set();
689
677
  cva = /* @__PURE__ */ new Set();
678
+ cvaCall = /* @__PURE__ */ new Set();
690
679
  sva = /* @__PURE__ */ new Set();
691
680
  token = /* @__PURE__ */ new Set();
692
681
  viewTransition = /* @__PURE__ */ new Set();
@@ -697,35 +686,14 @@ var ParserResult = class {
697
686
  /**
698
687
  * `css()` calls whose styles the build could not fully see.
699
688
  *
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.
689
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
690
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
704
691
  */
705
692
  unresolved = [];
706
693
  constructor(context, encoder) {
707
694
  this.context = context;
708
695
  this.encoder = encoder ?? context.encoder;
709
696
  }
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
697
  append(result) {
730
698
  this.all.push(result);
731
699
  return result;
@@ -750,62 +718,10 @@ var ParserResult = class {
750
718
  setCss(result) {
751
719
  this.css.add(this.append(Object.assign({ type: "css" }, result)));
752
720
  const encoder = this.encoder;
753
- const grouped = this.context.config.cssMode === "grouped";
754
721
  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;
722
+ const unresolved = findUnresolvedStyles(result, "atomic").filter((entry) => entry.reason === "unenumerable-keys");
723
+ if (unresolved.length) this.unresolved.push(...unresolved);
724
+ data.forEach((obj) => encoder.processAtomic(obj));
809
725
  }
810
726
  setCva(result) {
811
727
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
@@ -813,6 +729,20 @@ var ParserResult = class {
813
729
  const encoder = this.encoder;
814
730
  result.data.forEach((data) => encoder.processAtomicRecipe(data));
815
731
  }
732
+ /**
733
+ * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
734
+ *
735
+ * Recorded, not encoded. The rules already exist: `setCva` emitted them from the config,
736
+ * and a recipe's classes are named semantically from that config rather than from this
737
+ * call. What this adds is *visibility* -- the call site becomes something the fold can see
738
+ * and report on, where before it was indistinguishable from code nobody had parsed.
739
+ */
740
+ setCvaCall(name, result) {
741
+ this.cvaCall.add(this.append(Object.assign({
742
+ type: "cva-call",
743
+ name
744
+ }, result)));
745
+ }
816
746
  setSva(result) {
817
747
  this.sva.add(this.append(Object.assign({ type: "sva" }, result)));
818
748
  this.reportUnresolvedRecipe(result);
@@ -822,10 +752,9 @@ var ParserResult = class {
822
752
  /**
823
753
  * Record a recipe config the build could not fully read.
824
754
  *
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
755
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
756
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
757
+ * changes the hash, so the build emits rules under one name and the browser asks for
829
758
  * another, and the element renders with no styles at all.
830
759
  *
831
760
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -853,26 +782,7 @@ var ParserResult = class {
853
782
  type: "pattern",
854
783
  name
855
784
  }, 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;
785
+ result.data.forEach((obj) => this.encoder.processPattern(name, obj));
876
786
  }
877
787
  setRecipe(recipeName, result) {
878
788
  (0, _bamboocss_shared.getOrCreateSet)(this.recipe, recipeName).add(this.append(Object.assign({ type: "recipe" }, result)));
@@ -900,6 +810,7 @@ var ParserResult = class {
900
810
  result.css.forEach((item) => this.css.add(this.append(item)));
901
811
  result.cva.forEach((item) => this.cva.add(this.append(item)));
902
812
  result.sva.forEach((item) => this.sva.add(this.append(item)));
813
+ result.cvaCall.forEach((item) => this.cvaCall.add(this.append(item)));
903
814
  result.token.forEach((item) => this.token.add(this.append(item)));
904
815
  result.viewTransition.forEach((item) => this.viewTransition.add(this.append(item)));
905
816
  result.recipe.forEach((items, name) => {
@@ -921,6 +832,7 @@ var ParserResult = class {
921
832
  css: Array.from(this.css),
922
833
  cva: Array.from(this.cva),
923
834
  sva: Array.from(this.sva),
835
+ cvaCall: Array.from(this.cvaCall),
924
836
  token: Array.from(this.token),
925
837
  viewTransition: Array.from(this.viewTransition),
926
838
  recipe: Object.fromEntries(Array.from(this.recipe.entries()).map(([key, value]) => [key, Array.from(value)])),
@@ -956,6 +868,20 @@ function createParser(context) {
956
868
  _bamboocss_logger.logger.debug("ast:import", !file.isEmpty() ? `Found import { ${file.toString()} } in ${filePath}` : `No import found in ${filePath}`);
957
869
  const parserResult = new ParserResult(context, encoder);
958
870
  if (file.isEmpty() && !jsx.isEnabled) return parserResult;
871
+ if (file.importsRecipeFactory()) for (const statement of sourceFile.compilerNode.statements) {
872
+ if (!ts_morph.ts.isVariableStatement(statement)) continue;
873
+ if (!(statement.declarationList.flags & ts_morph.ts.NodeFlags.Const)) continue;
874
+ for (const declaration of statement.declarationList.declarations) {
875
+ const initializer = declaration.initializer;
876
+ if (!initializer || !ts_morph.ts.isCallExpression(initializer)) continue;
877
+ const callee = initializer.expression;
878
+ if (!ts_morph.ts.isIdentifier(callee) || !ts_morph.ts.isIdentifier(declaration.name)) continue;
879
+ const imported = file.getName(callee.text);
880
+ if (imported !== "cva" && imported !== "sva") continue;
881
+ if (!file.matchFn(callee.text)) continue;
882
+ file.addLocalRecipe(declaration.name.text);
883
+ }
884
+ }
959
885
  (0, _bamboocss_extractor.extract)({
960
886
  ast: sourceFile,
961
887
  tokens: context.tokens ? {
@@ -1005,53 +931,64 @@ function createParser(context) {
1005
931
  kind: result.kind,
1006
932
  alias
1007
933
  } : { kind: result.kind });
1008
- if (result.kind === "function") (0, ts_pattern.match)(name).when(imports.matchers.css.match, (name) => {
1009
- result.queryList.forEach((query) => {
1010
- if (query.kind === "call-expression") if (query.box.value.length > 1) parserResult.set(name, {
1011
- name,
1012
- box: query.box,
1013
- data: query.box.value.reduce((acc, value) => [...acc, ...combineResult((0, _bamboocss_extractor.unbox)(value))], [])
934
+ if (result.kind === "function") {
935
+ if (file.isLocalRecipe(alias) && !file.match(alias)) {
936
+ result.queryList.forEach((query) => {
937
+ if (query.kind === "call-expression") parserResult.setCvaCall(alias, {
938
+ name: alias,
939
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
940
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
941
+ });
1014
942
  });
1015
- else parserResult.set(name, {
1016
- name,
1017
- box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
1018
- data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
943
+ return;
944
+ }
945
+ (0, ts_pattern.match)(name).when(imports.matchers.css.match, (name) => {
946
+ result.queryList.forEach((query) => {
947
+ if (query.kind === "call-expression") if (query.box.value.length > 1) parserResult.set(name, {
948
+ name,
949
+ box: query.box,
950
+ data: query.box.value.reduce((acc, value) => [...acc, ...combineResult((0, _bamboocss_extractor.unbox)(value))], [])
951
+ });
952
+ else parserResult.set(name, {
953
+ name,
954
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
955
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
956
+ });
1019
957
  });
1020
- });
1021
- }).when(imports.matchers.tokens.match, (name) => {
1022
- result.queryList.forEach((query) => {
1023
- if (query.kind === "call-expression") parserResult.setToken({
1024
- name,
1025
- box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
1026
- data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
958
+ }).when(imports.matchers.tokens.match, (name) => {
959
+ result.queryList.forEach((query) => {
960
+ if (query.kind === "call-expression") parserResult.setToken({
961
+ name,
962
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
963
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
964
+ });
1027
965
  });
1028
- });
1029
- }).when(file.isValidPattern, (name) => {
1030
- result.queryList.forEach((query) => {
1031
- if (query.kind === "call-expression") parserResult.setPattern(name, {
1032
- name,
1033
- box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
1034
- data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
966
+ }).when(file.isValidPattern, (name) => {
967
+ result.queryList.forEach((query) => {
968
+ if (query.kind === "call-expression") parserResult.setPattern(name, {
969
+ name,
970
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
971
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
972
+ });
1035
973
  });
1036
- });
1037
- }).when(file.isValidRecipe, (name) => {
1038
- result.queryList.forEach((query) => {
1039
- if (query.kind === "call-expression") parserResult.setRecipe(name, {
1040
- name,
1041
- box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
1042
- data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
974
+ }).when(file.isValidRecipe, (name) => {
975
+ result.queryList.forEach((query) => {
976
+ if (query.kind === "call-expression") parserResult.setRecipe(name, {
977
+ name,
978
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
979
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
980
+ });
1043
981
  });
1044
- });
1045
- }).when(file.isViewTransitionFn, (name) => {
1046
- result.queryList.forEach((query) => {
1047
- if (query.kind === "call-expression") parserResult.setViewTransition({
1048
- name,
1049
- box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
1050
- data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
982
+ }).when(file.isViewTransitionFn, (name) => {
983
+ result.queryList.forEach((query) => {
984
+ if (query.kind === "call-expression") parserResult.setViewTransition({
985
+ name,
986
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
987
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
988
+ });
1051
989
  });
1052
- });
1053
- }).otherwise(() => {});
1054
- else if (jsx.isEnabled && result.kind === "component") result.queryList.forEach((query) => {
990
+ }).otherwise(() => {});
991
+ } else if (jsx.isEnabled && result.kind === "component") result.queryList.forEach((query) => {
1055
992
  const data = combineResult((0, _bamboocss_extractor.unbox)(query.box));
1056
993
  for (const tag of [name, alias]) {
1057
994
  if (!jsx.isJsxTagRecipe(tag)) continue;
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 {
@@ -179,6 +173,7 @@ declare class ParserResult implements ParserResultInterface {
179
173
  all: ResultItem[];
180
174
  css: Set<ResultItem>;
181
175
  cva: Set<ResultItem>;
176
+ cvaCall: Set<ResultItem>;
182
177
  sva: Set<ResultItem>;
183
178
  token: Set<ResultItem>;
184
179
  viewTransition: Set<ResultItem>;
@@ -189,39 +184,31 @@ declare class ParserResult implements ParserResultInterface {
189
184
  /**
190
185
  * `css()` calls whose styles the build could not fully see.
191
186
  *
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.
187
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
188
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
196
189
  */
197
190
  unresolved: UnresolvedStyle[];
198
191
  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
192
  append(result: ResultItem): ResultItem;
207
193
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
208
194
  setCss(result: ResultItem): void;
195
+ setCva(result: ResultItem): void;
209
196
  /**
210
- * How many arguments the call this result came from was written with.
197
+ * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
211
198
  *
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.
199
+ * Recorded, not encoded. The rules already exist: `setCva` emitted them from the config,
200
+ * and a recipe's classes are named semantically from that config rather than from this
201
+ * call. What this adds is *visibility* -- the call site becomes something the fold can see
202
+ * and report on, where before it was indistinguishable from code nobody had parsed.
214
203
  */
215
- private callArgumentCount;
216
- setCva(result: ResultItem): void;
204
+ setCvaCall(name: string, result: ResultItem): void;
217
205
  setSva(result: ResultItem): void;
218
206
  /**
219
207
  * Record a recipe config the build could not fully read.
220
208
  *
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
209
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
210
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
211
+ * changes the hash, so the build emits rules under one name and the browser asks for
225
212
  * another, and the element renders with no styles at all.
226
213
  *
227
214
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -232,19 +219,6 @@ declare class ParserResult implements ParserResultInterface {
232
219
  setToken(result: ResultItem): void;
233
220
  setViewTransition(result: ResultItem): void;
234
221
  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
222
  setRecipe(recipeName: string, result: ResultItem): void;
249
223
  isEmpty(): boolean;
250
224
  setFilePath(filePath: string): this;
@@ -254,6 +228,7 @@ declare class ParserResult implements ParserResultInterface {
254
228
  css: ResultItem[];
255
229
  cva: ResultItem[];
256
230
  sva: ResultItem[];
231
+ cvaCall: ResultItem[];
257
232
  token: ResultItem[];
258
233
  viewTransition: ResultItem[];
259
234
  recipe: {
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 {
@@ -179,6 +173,7 @@ declare class ParserResult implements ParserResultInterface {
179
173
  all: ResultItem[];
180
174
  css: Set<ResultItem>;
181
175
  cva: Set<ResultItem>;
176
+ cvaCall: Set<ResultItem>;
182
177
  sva: Set<ResultItem>;
183
178
  token: Set<ResultItem>;
184
179
  viewTransition: Set<ResultItem>;
@@ -189,39 +184,31 @@ declare class ParserResult implements ParserResultInterface {
189
184
  /**
190
185
  * `css()` calls whose styles the build could not fully see.
191
186
  *
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.
187
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
188
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
196
189
  */
197
190
  unresolved: UnresolvedStyle[];
198
191
  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
192
  append(result: ResultItem): ResultItem;
207
193
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
208
194
  setCss(result: ResultItem): void;
195
+ setCva(result: ResultItem): void;
209
196
  /**
210
- * How many arguments the call this result came from was written with.
197
+ * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
211
198
  *
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.
199
+ * Recorded, not encoded. The rules already exist: `setCva` emitted them from the config,
200
+ * and a recipe's classes are named semantically from that config rather than from this
201
+ * call. What this adds is *visibility* -- the call site becomes something the fold can see
202
+ * and report on, where before it was indistinguishable from code nobody had parsed.
214
203
  */
215
- private callArgumentCount;
216
- setCva(result: ResultItem): void;
204
+ setCvaCall(name: string, result: ResultItem): void;
217
205
  setSva(result: ResultItem): void;
218
206
  /**
219
207
  * Record a recipe config the build could not fully read.
220
208
  *
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
209
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
210
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
211
+ * changes the hash, so the build emits rules under one name and the browser asks for
225
212
  * another, and the element renders with no styles at all.
226
213
  *
227
214
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -232,19 +219,6 @@ declare class ParserResult implements ParserResultInterface {
232
219
  setToken(result: ResultItem): void;
233
220
  setViewTransition(result: ResultItem): void;
234
221
  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
222
  setRecipe(recipeName: string, result: ResultItem): void;
249
223
  isEmpty(): boolean;
250
224
  setFilePath(filePath: string): this;
@@ -254,6 +228,7 @@ declare class ParserResult implements ParserResultInterface {
254
228
  css: ResultItem[];
255
229
  cva: ResultItem[];
256
230
  sva: ResultItem[];
231
+ cvaCall: ResultItem[];
257
232
  token: ResultItem[];
258
233
  viewTransition: ResultItem[];
259
234
  recipe: {
package/dist/index.mjs CHANGED
@@ -64,6 +64,7 @@ function classifyProject(ctx, resultMap) {
64
64
  const { item, kind, filepath, localMaps } = opts;
65
65
  if (!item.box || box.isUnresolvable(item.box)) return;
66
66
  if (!item.data) return;
67
+ if (item.type === "cva-call") return;
67
68
  const componentReportItem = {
68
69
  componentIndex: String(componentIndex++),
69
70
  componentName: item.name,
@@ -522,14 +523,7 @@ const writtenProps = (node) => {
522
523
  uncertain
523
524
  };
524
525
  };
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
- */
526
+ /** Every property of a `css()` call that will not reach the stylesheet. */
533
527
  /**
534
528
  * A recipe config the build could not fully read, level by level.
535
529
  *
@@ -674,18 +668,13 @@ const hasKeyOutside = (resolved, names) => {
674
668
  };
675
669
  //#endregion
676
670
  //#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
671
  var ParserResult = class {
684
672
  context;
685
673
  /** Ordered list of all ResultItem */
686
674
  all = [];
687
675
  css = /* @__PURE__ */ new Set();
688
676
  cva = /* @__PURE__ */ new Set();
677
+ cvaCall = /* @__PURE__ */ new Set();
689
678
  sva = /* @__PURE__ */ new Set();
690
679
  token = /* @__PURE__ */ new Set();
691
680
  viewTransition = /* @__PURE__ */ new Set();
@@ -696,35 +685,14 @@ var ParserResult = class {
696
685
  /**
697
686
  * `css()` calls whose styles the build could not fully see.
698
687
  *
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.
688
+ * A property the build cannot resolve has no rule behind it, so the declaration is simply
689
+ * absent from the element — silently. Only the surprising half is collected; see `setCss`.
703
690
  */
704
691
  unresolved = [];
705
692
  constructor(context, encoder) {
706
693
  this.context = context;
707
694
  this.encoder = encoder ?? context.encoder;
708
695
  }
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
696
  append(result) {
729
697
  this.all.push(result);
730
698
  return result;
@@ -749,62 +717,10 @@ var ParserResult = class {
749
717
  setCss(result) {
750
718
  this.css.add(this.append(Object.assign({ type: "css" }, result)));
751
719
  const encoder = this.encoder;
752
- const grouped = this.context.config.cssMode === "grouped";
753
720
  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;
721
+ const unresolved = findUnresolvedStyles(result, "atomic").filter((entry) => entry.reason === "unenumerable-keys");
722
+ if (unresolved.length) this.unresolved.push(...unresolved);
723
+ data.forEach((obj) => encoder.processAtomic(obj));
808
724
  }
809
725
  setCva(result) {
810
726
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
@@ -812,6 +728,20 @@ var ParserResult = class {
812
728
  const encoder = this.encoder;
813
729
  result.data.forEach((data) => encoder.processAtomicRecipe(data));
814
730
  }
731
+ /**
732
+ * A call of a locally-bound inline recipe -- `const badge = cva(...)`, then `badge({...})`.
733
+ *
734
+ * Recorded, not encoded. The rules already exist: `setCva` emitted them from the config,
735
+ * and a recipe's classes are named semantically from that config rather than from this
736
+ * call. What this adds is *visibility* -- the call site becomes something the fold can see
737
+ * and report on, where before it was indistinguishable from code nobody had parsed.
738
+ */
739
+ setCvaCall(name, result) {
740
+ this.cvaCall.add(this.append(Object.assign({
741
+ type: "cva-call",
742
+ name
743
+ }, result)));
744
+ }
815
745
  setSva(result) {
816
746
  this.sva.add(this.append(Object.assign({ type: "sva" }, result)));
817
747
  this.reportUnresolvedRecipe(result);
@@ -821,10 +751,9 @@ var ParserResult = class {
821
751
  /**
822
752
  * Record a recipe config the build could not fully read.
823
753
  *
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
754
+ * Reported in full, unlike the `css()` check in `setCss`, which keeps only the surprising
755
+ * half. A recipe is named from a *hash of its config*: a declaration the build cannot see
756
+ * changes the hash, so the build emits rules under one name and the browser asks for
828
757
  * another, and the element renders with no styles at all.
829
758
  *
830
759
  * There is no fallback to pair with it either. Grouped can emit atomic rules alongside the
@@ -852,26 +781,7 @@ var ParserResult = class {
852
781
  type: "pattern",
853
782
  name
854
783
  }, 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;
784
+ result.data.forEach((obj) => this.encoder.processPattern(name, obj));
875
785
  }
876
786
  setRecipe(recipeName, result) {
877
787
  getOrCreateSet(this.recipe, recipeName).add(this.append(Object.assign({ type: "recipe" }, result)));
@@ -899,6 +809,7 @@ var ParserResult = class {
899
809
  result.css.forEach((item) => this.css.add(this.append(item)));
900
810
  result.cva.forEach((item) => this.cva.add(this.append(item)));
901
811
  result.sva.forEach((item) => this.sva.add(this.append(item)));
812
+ result.cvaCall.forEach((item) => this.cvaCall.add(this.append(item)));
902
813
  result.token.forEach((item) => this.token.add(this.append(item)));
903
814
  result.viewTransition.forEach((item) => this.viewTransition.add(this.append(item)));
904
815
  result.recipe.forEach((items, name) => {
@@ -920,6 +831,7 @@ var ParserResult = class {
920
831
  css: Array.from(this.css),
921
832
  cva: Array.from(this.cva),
922
833
  sva: Array.from(this.sva),
834
+ cvaCall: Array.from(this.cvaCall),
923
835
  token: Array.from(this.token),
924
836
  viewTransition: Array.from(this.viewTransition),
925
837
  recipe: Object.fromEntries(Array.from(this.recipe.entries()).map(([key, value]) => [key, Array.from(value)])),
@@ -955,6 +867,20 @@ function createParser(context) {
955
867
  logger.debug("ast:import", !file.isEmpty() ? `Found import { ${file.toString()} } in ${filePath}` : `No import found in ${filePath}`);
956
868
  const parserResult = new ParserResult(context, encoder);
957
869
  if (file.isEmpty() && !jsx.isEnabled) return parserResult;
870
+ if (file.importsRecipeFactory()) for (const statement of sourceFile.compilerNode.statements) {
871
+ if (!ts.isVariableStatement(statement)) continue;
872
+ if (!(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
873
+ for (const declaration of statement.declarationList.declarations) {
874
+ const initializer = declaration.initializer;
875
+ if (!initializer || !ts.isCallExpression(initializer)) continue;
876
+ const callee = initializer.expression;
877
+ if (!ts.isIdentifier(callee) || !ts.isIdentifier(declaration.name)) continue;
878
+ const imported = file.getName(callee.text);
879
+ if (imported !== "cva" && imported !== "sva") continue;
880
+ if (!file.matchFn(callee.text)) continue;
881
+ file.addLocalRecipe(declaration.name.text);
882
+ }
883
+ }
958
884
  extract({
959
885
  ast: sourceFile,
960
886
  tokens: context.tokens ? {
@@ -1004,53 +930,64 @@ function createParser(context) {
1004
930
  kind: result.kind,
1005
931
  alias
1006
932
  } : { kind: result.kind });
1007
- if (result.kind === "function") match(name).when(imports.matchers.css.match, (name) => {
1008
- result.queryList.forEach((query) => {
1009
- if (query.kind === "call-expression") if (query.box.value.length > 1) parserResult.set(name, {
1010
- name,
1011
- box: query.box,
1012
- data: query.box.value.reduce((acc, value) => [...acc, ...combineResult(unbox(value))], [])
933
+ if (result.kind === "function") {
934
+ if (file.isLocalRecipe(alias) && !file.match(alias)) {
935
+ result.queryList.forEach((query) => {
936
+ if (query.kind === "call-expression") parserResult.setCvaCall(alias, {
937
+ name: alias,
938
+ box: query.box.value[0] ?? box.fallback(query.box),
939
+ data: combineResult(unbox(query.box.value[0]))
940
+ });
1013
941
  });
1014
- else parserResult.set(name, {
1015
- name,
1016
- box: query.box.value[0] ?? box.fallback(query.box),
1017
- data: combineResult(unbox(query.box.value[0]))
942
+ return;
943
+ }
944
+ match(name).when(imports.matchers.css.match, (name) => {
945
+ result.queryList.forEach((query) => {
946
+ if (query.kind === "call-expression") if (query.box.value.length > 1) parserResult.set(name, {
947
+ name,
948
+ box: query.box,
949
+ data: query.box.value.reduce((acc, value) => [...acc, ...combineResult(unbox(value))], [])
950
+ });
951
+ else parserResult.set(name, {
952
+ name,
953
+ box: query.box.value[0] ?? box.fallback(query.box),
954
+ data: combineResult(unbox(query.box.value[0]))
955
+ });
1018
956
  });
1019
- });
1020
- }).when(imports.matchers.tokens.match, (name) => {
1021
- result.queryList.forEach((query) => {
1022
- if (query.kind === "call-expression") parserResult.setToken({
1023
- name,
1024
- box: query.box.value[0] ?? box.fallback(query.box),
1025
- data: combineResult(unbox(query.box.value[0]))
957
+ }).when(imports.matchers.tokens.match, (name) => {
958
+ result.queryList.forEach((query) => {
959
+ if (query.kind === "call-expression") parserResult.setToken({
960
+ name,
961
+ box: query.box.value[0] ?? box.fallback(query.box),
962
+ data: combineResult(unbox(query.box.value[0]))
963
+ });
1026
964
  });
1027
- });
1028
- }).when(file.isValidPattern, (name) => {
1029
- result.queryList.forEach((query) => {
1030
- if (query.kind === "call-expression") parserResult.setPattern(name, {
1031
- name,
1032
- box: query.box.value[0] ?? box.fallback(query.box),
1033
- data: combineResult(unbox(query.box.value[0]))
965
+ }).when(file.isValidPattern, (name) => {
966
+ result.queryList.forEach((query) => {
967
+ if (query.kind === "call-expression") parserResult.setPattern(name, {
968
+ name,
969
+ box: query.box.value[0] ?? box.fallback(query.box),
970
+ data: combineResult(unbox(query.box.value[0]))
971
+ });
1034
972
  });
1035
- });
1036
- }).when(file.isValidRecipe, (name) => {
1037
- result.queryList.forEach((query) => {
1038
- if (query.kind === "call-expression") parserResult.setRecipe(name, {
1039
- name,
1040
- box: query.box.value[0] ?? box.fallback(query.box),
1041
- data: combineResult(unbox(query.box.value[0]))
973
+ }).when(file.isValidRecipe, (name) => {
974
+ result.queryList.forEach((query) => {
975
+ if (query.kind === "call-expression") parserResult.setRecipe(name, {
976
+ name,
977
+ box: query.box.value[0] ?? box.fallback(query.box),
978
+ data: combineResult(unbox(query.box.value[0]))
979
+ });
1042
980
  });
1043
- });
1044
- }).when(file.isViewTransitionFn, (name) => {
1045
- result.queryList.forEach((query) => {
1046
- if (query.kind === "call-expression") parserResult.setViewTransition({
1047
- name,
1048
- box: query.box.value[0] ?? box.fallback(query.box),
1049
- data: combineResult(unbox(query.box.value[0]))
981
+ }).when(file.isViewTransitionFn, (name) => {
982
+ result.queryList.forEach((query) => {
983
+ if (query.kind === "call-expression") parserResult.setViewTransition({
984
+ name,
985
+ box: query.box.value[0] ?? box.fallback(query.box),
986
+ data: combineResult(unbox(query.box.value[0]))
987
+ });
1050
988
  });
1051
- });
1052
- }).otherwise(() => {});
1053
- else if (jsx.isEnabled && result.kind === "component") result.queryList.forEach((query) => {
989
+ }).otherwise(() => {});
990
+ } else if (jsx.isEnabled && result.kind === "component") result.queryList.forEach((query) => {
1054
991
  const data = combineResult(unbox(query.box));
1055
992
  for (const tag of [name, alias]) {
1056
993
  if (!jsx.isJsxTagRecipe(tag)) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/parser",
3
- "version": "1.21.0",
3
+ "version": "1.23.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.21.0",
38
- "@bamboocss/core": "^1.21.0",
39
- "@bamboocss/extractor": "1.21.0",
40
- "@bamboocss/logger": "1.21.0",
41
- "@bamboocss/shared": "1.21.0",
42
- "@bamboocss/types": "1.21.0"
37
+ "@bamboocss/config": "^1.23.0",
38
+ "@bamboocss/core": "^1.23.0",
39
+ "@bamboocss/extractor": "1.23.0",
40
+ "@bamboocss/logger": "1.23.0",
41
+ "@bamboocss/shared": "1.23.0",
42
+ "@bamboocss/types": "1.23.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@bamboocss/generator": "1.21.0",
46
- "@bamboocss/plugin-svelte": "1.21.0",
47
- "@bamboocss/plugin-vue": "1.21.0"
45
+ "@bamboocss/generator": "1.23.0",
46
+ "@bamboocss/plugin-svelte": "1.23.0",
47
+ "@bamboocss/plugin-vue": "1.23.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsdown src/index.ts --format=esm,cjs --dts",