@bamboocss/parser 1.14.0 → 1.16.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
@@ -44,7 +44,7 @@ function classifyProject(ctx, resultMap) {
44
44
  const processPattern = (opts) => {
45
45
  const { boxNode, data, item, filepath, localMaps } = opts;
46
46
  const name = item.componentName;
47
- const pattern = ctx.patterns.details.find((p) => p.match.test(name) || p.baseName === name);
47
+ const pattern = ctx.patterns.details.find((p) => p.baseName === name);
48
48
  if (!pattern) return;
49
49
  const cssObj = pattern.config.transform?.(data || {}, _bamboocss_shared.patternFns) ?? {};
50
50
  const newItem = {
@@ -76,7 +76,7 @@ function classifyProject(ctx, resultMap) {
76
76
  contains: [],
77
77
  debug: Reflect.has(item, "debug")
78
78
  };
79
- if (item.type === "pattern" || item.type === "jsx-pattern") return processPattern({
79
+ if (item.type === "pattern") return processPattern({
80
80
  boxNode: item.box,
81
81
  data: item.data[0],
82
82
  item: componentReportItem,
@@ -201,14 +201,6 @@ function classifyProject(ctx, resultMap) {
201
201
  resultMap.forEach((parserResult, filepath) => {
202
202
  if (parserResult.isEmpty()) return;
203
203
  const localMaps = createReportMaps();
204
- const componentFn = (item) => {
205
- processResultItemFn({
206
- item,
207
- filepath,
208
- localMaps,
209
- type: "component"
210
- });
211
- };
212
204
  const functionFn = (item) => {
213
205
  processResultItemFn({
214
206
  item,
@@ -217,7 +209,6 @@ function classifyProject(ctx, resultMap) {
217
209
  type: "function"
218
210
  });
219
211
  };
220
- parserResult.jsx.forEach(componentFn);
221
212
  parserResult.css.forEach(functionFn);
222
213
  parserResult.cva.forEach(functionFn);
223
214
  parserResult.pattern.forEach((itemList) => {
@@ -466,6 +457,120 @@ function getImportDeclarations(context, sourceFile) {
466
457
  return importDeclarations;
467
458
  }
468
459
  //#endregion
460
+ //#region src/unresolved-styles.ts
461
+ /**
462
+ * Whether every box under this one carries a value the build can actually see.
463
+ *
464
+ * Mirrors `isStaticBox` in `@bamboocss/vite`, which asks the same question to decide
465
+ * whether a call is safe to fold. Duplicated rather than shared for now because the fold's
466
+ * copy also answers questions about ternaries that a diagnostic does not care about;
467
+ * unifying them is worth doing when the fold's detection moves out of the vite package.
468
+ */
469
+ const findUnresolvable = (node, path, out, seen = /* @__PURE__ */ new Set()) => {
470
+ if (!node || seen.has(node)) return;
471
+ seen.add(node);
472
+ if (_bamboocss_extractor.box.isUnresolvable(node)) {
473
+ out.push(path.join("."));
474
+ return;
475
+ }
476
+ if (_bamboocss_extractor.box.isConditional(node)) return;
477
+ if (_bamboocss_extractor.box.isLiteral(node) && node.value === void 0) {
478
+ if (ts_morph.Node.isTemplateExpression(node.getNode())) out.push(path.join("."));
479
+ return;
480
+ }
481
+ if (_bamboocss_extractor.box.isMap(node)) {
482
+ for (const [key, child] of node.value) findUnresolvable(child, [...path, key], out, seen);
483
+ return;
484
+ }
485
+ if (_bamboocss_extractor.box.isArray(node)) node.value.forEach((child, index) => findUnresolvable(child, [...path, String(index)], out, seen));
486
+ };
487
+ /**
488
+ * Property names written at the top level of the call's own object literal.
489
+ *
490
+ * A key whose value the extractor could not evaluate at all is not boxed as
491
+ * `unresolvable` — `maybeBoxNode` returns nothing and the pair is never recorded
492
+ * (`get-object-literal-expression-prop-pairs.ts` has no fallback), so the property
493
+ * disappears with no trace in the box tree. Reading the source back is the only way to
494
+ * notice, and it is why `css({ color: getColor() })` needs this and not just the walk above.
495
+ *
496
+ * Returns `undefined` only when the argument is not a single object literal at all, so the
497
+ * caller reports nothing rather than something wrong.
498
+ */
499
+ const writtenProps = (node) => {
500
+ let literal = node;
501
+ if (literal && ts_morph.Node.isCallExpression(literal)) {
502
+ const args = literal.getArguments();
503
+ if (args.length !== 1) return void 0;
504
+ literal = args[0];
505
+ }
506
+ if (!literal || !ts_morph.Node.isObjectLiteralExpression(literal)) return void 0;
507
+ const names = [];
508
+ let uncertain = false;
509
+ for (const property of literal.getProperties()) {
510
+ if (ts_morph.Node.isPropertyAssignment(property) || ts_morph.Node.isShorthandPropertyAssignment(property)) {
511
+ const name = property.getName();
512
+ if (name.startsWith("[")) {
513
+ uncertain = true;
514
+ continue;
515
+ }
516
+ names.push(name.replace(/^['"]|['"]$/g, ""));
517
+ continue;
518
+ }
519
+ uncertain = true;
520
+ }
521
+ return {
522
+ names,
523
+ uncertain
524
+ };
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
+ */
534
+ const findUnresolvedStyles = (item) => {
535
+ const boxNode = item.box;
536
+ if (!boxNode) return [];
537
+ const node = boxNode.getNode();
538
+ const sourceFile = node?.getSourceFile();
539
+ if (!node || !sourceFile) return [];
540
+ const found = [];
541
+ findUnresolvable(boxNode, [], found);
542
+ const losses = found.map((prop) => ({
543
+ prop: prop || void 0,
544
+ reason: "unresolvable-value"
545
+ }));
546
+ const written = writtenProps(node);
547
+ if (written) {
548
+ const resolved = /* @__PURE__ */ new Set();
549
+ for (const entry of item.data) if (entry && typeof entry === "object") for (const key of Object.keys(entry)) resolved.add(key);
550
+ if (_bamboocss_extractor.box.isMap(boxNode)) for (const key of boxNode.value.keys()) resolved.add(key);
551
+ for (const prop of written.names) if (!resolved.has(prop)) losses.push({
552
+ prop,
553
+ reason: "missing-property"
554
+ });
555
+ if (written.uncertain && !hasKeyOutside(resolved, written.names)) losses.push({ reason: "unenumerable-keys" });
556
+ }
557
+ if (!losses.length) return [];
558
+ const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
559
+ const at = {
560
+ filePath: sourceFile.getFilePath(),
561
+ line,
562
+ column
563
+ };
564
+ return losses.map((loss) => ({
565
+ ...at,
566
+ ...loss
567
+ }));
568
+ };
569
+ const hasKeyOutside = (resolved, names) => {
570
+ for (const key of resolved) if (!names.includes(key)) return true;
571
+ return false;
572
+ };
573
+ //#endregion
469
574
  //#region src/parser-result.ts
470
575
  function cartesian(arrays) {
471
576
  if (arrays.length === 0) return [[]];
@@ -477,19 +582,46 @@ var ParserResult = class {
477
582
  context;
478
583
  /** Ordered list of all ResultItem */
479
584
  all = [];
480
- jsx = /* @__PURE__ */ new Set();
481
585
  css = /* @__PURE__ */ new Set();
482
586
  cva = /* @__PURE__ */ new Set();
483
587
  sva = /* @__PURE__ */ new Set();
484
588
  token = /* @__PURE__ */ new Set();
589
+ viewTransition = /* @__PURE__ */ new Set();
485
590
  recipe = /* @__PURE__ */ new Map();
486
591
  pattern = /* @__PURE__ */ new Map();
487
592
  filePath;
488
593
  encoder;
594
+ /**
595
+ * `css()` calls whose styles the build could not fully see.
596
+ *
597
+ * Only collected under `cssMode: 'grouped'`, where one class names the whole call, so a
598
+ * property the build cannot resolve changes the class rather than dropping a declaration
599
+ * from it — and the element renders with no styles at all. Under `atomic` the same call
600
+ * keeps everything the build did resolve, which is not worth interrupting a build over.
601
+ */
602
+ unresolved = [];
489
603
  constructor(context, encoder) {
490
604
  this.context = context;
491
605
  this.encoder = encoder ?? context.encoder;
492
606
  }
607
+ /**
608
+ * Record a call whose styles the build could not fully see, at the call's own position.
609
+ *
610
+ * Used for losses the box tree cannot show — a ternary past the combination cap emits
611
+ * fragments rather than whole objects, and every individual box in it resolved fine.
612
+ */
613
+ reportUnresolved(result, reason) {
614
+ const node = result.box?.getNode();
615
+ const sourceFile = node?.getSourceFile();
616
+ if (!node || !sourceFile) return;
617
+ const { line, column } = sourceFile.getLineAndColumnAtPos(node.getStart());
618
+ this.unresolved.push({
619
+ filePath: sourceFile.getFilePath(),
620
+ line,
621
+ column,
622
+ reason
623
+ });
624
+ }
493
625
  append(result) {
494
626
  this.all.push(result);
495
627
  return result;
@@ -515,24 +647,36 @@ var ParserResult = class {
515
647
  this.css.add(this.append(Object.assign({ type: "css" }, result)));
516
648
  const encoder = this.encoder;
517
649
  const grouped = this.context.config.cssMode === "grouped";
518
- if (!grouped || result.data.length <= 1) {
519
- result.data.forEach((obj) => grouped ? encoder.processGrouped(obj) : encoder.processAtomic(obj));
650
+ const data = result.data.some(Array.isArray) ? result.data.flatMap((obj) => Array.isArray(obj) ? obj : [obj]) : result.data;
651
+ if (grouped) {
652
+ const unresolved = findUnresolvedStyles(result);
653
+ if (unresolved.length) {
654
+ this.unresolved.push(...unresolved);
655
+ data.forEach((obj) => encoder.processAtomic(obj));
656
+ }
657
+ }
658
+ if (!grouped || data.length <= 1) {
659
+ data.forEach((obj) => grouped ? encoder.processGrouped(obj) : encoder.processAtomic(obj));
520
660
  return;
521
661
  }
522
662
  const keyCounts = /* @__PURE__ */ new Map();
523
- for (const obj of result.data) for (const key of Object.keys(obj)) keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
663
+ for (const obj of data) for (const key of Object.keys(obj)) keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
524
664
  if (!Array.from(keyCounts.values()).some((c) => c > 1)) {
525
- encoder.processGrouped(Object.assign({}, ...result.data));
665
+ encoder.processGroupedMerge(data);
526
666
  return;
527
667
  }
668
+ if (this.callArgumentCount(result) > 1) {
669
+ this.reportUnresolved(result, "ambiguous-merge");
670
+ data.forEach((obj) => encoder.processAtomic(obj));
671
+ }
528
672
  const overlappingKeys = /* @__PURE__ */ new Set();
529
673
  keyCounts.forEach((count, key) => {
530
674
  if (count > 1) overlappingKeys.add(key);
531
675
  });
532
- const base = {};
676
+ const baseEntries = [];
533
677
  const branchEntries = [];
534
- for (const obj of result.data) if (Object.keys(obj).some((k) => overlappingKeys.has(k))) branchEntries.push(obj);
535
- else Object.assign(base, obj);
678
+ for (const obj of data) if (Object.keys(obj).some((k) => overlappingKeys.has(k))) branchEntries.push(obj);
679
+ else baseEntries.push(obj);
536
680
  const branchGroups = /* @__PURE__ */ new Map();
537
681
  for (const entry of branchEntries) {
538
682
  const keySet = Object.keys(entry).sort().join("\0");
@@ -542,10 +686,24 @@ var ParserResult = class {
542
686
  }
543
687
  const groupArrays = Array.from(branchGroups.values());
544
688
  if (groupArrays.reduce((acc, g) => acc * g.length, 1) > 32) {
545
- result.data.forEach((obj) => encoder.processGrouped(obj));
689
+ this.reportUnresolved(result, "too-many-combinations");
690
+ data.forEach((obj) => {
691
+ encoder.processGrouped(obj);
692
+ encoder.processAtomic(obj);
693
+ });
546
694
  return;
547
695
  }
548
- for (const combo of cartesian(groupArrays)) encoder.processGrouped(Object.assign({}, base, ...combo));
696
+ for (const combo of cartesian(groupArrays)) encoder.processGroupedMerge([...baseEntries, ...combo]);
697
+ }
698
+ /**
699
+ * How many arguments the call this result came from was written with.
700
+ *
701
+ * Returns 1 for anything that is not a call — a JSX element, or a box that lost its node —
702
+ * since the question only separates operands from branches and neither has operands.
703
+ */
704
+ callArgumentCount(result) {
705
+ const node = result.box?.getNode();
706
+ return node && ts_morph.Node.isCallExpression(node) ? node.getArguments().length : 1;
549
707
  }
550
708
  setCva(result) {
551
709
  this.cva.add(this.append(Object.assign({ type: "cva" }, result)));
@@ -560,11 +718,10 @@ var ParserResult = class {
560
718
  setToken(result) {
561
719
  this.token.add(this.append(Object.assign({ type: "token" }, result)));
562
720
  }
563
- setJsx(result) {
564
- this.jsx.add(this.append(Object.assign({ type: "jsx" }, result)));
721
+ setViewTransition(result) {
722
+ this.viewTransition.add(this.append(Object.assign({ type: "viewTransition" }, result)));
565
723
  const encoder = this.encoder;
566
- const grouped = this.context.config.cssMode === "grouped";
567
- result.data.forEach((obj) => encoder.processStyleProps(obj, grouped));
724
+ result.data.forEach((obj) => encoder.processViewTransition(obj));
568
725
  }
569
726
  setPattern(name, result) {
570
727
  (0, _bamboocss_shared.getOrCreateSet)(this.pattern, name).add(this.append(Object.assign({
@@ -572,7 +729,25 @@ var ParserResult = class {
572
729
  name
573
730
  }, result)));
574
731
  const encoder = this.encoder;
575
- result.data.forEach((obj) => encoder.processPattern(name, obj, result.type ?? "pattern", result.name));
732
+ const grouped = this.context.config.cssMode === "grouped";
733
+ result.data.forEach((obj) => encoder.processPattern(name, obj, grouped));
734
+ if (grouped && !this.groupIsExact(result)) result.data.forEach((obj) => encoder.processPattern(name, obj, false));
735
+ }
736
+ /**
737
+ * Whether the group encoded for this result is the one the runtime will ask for.
738
+ *
739
+ * True only when the build saw the whole thing at once: one style object, with every
740
+ * value in it resolved. Several objects means the runtime merges them into a call this
741
+ * never encoded — `setCss` reconstructs those combinations, and nothing else does — and
742
+ * an unresolved value means the merge would not have matched anyway.
743
+ *
744
+ * Answering "no" costs a call site its atomic rules, which is CSS that duplicates the
745
+ * group. Answering a wrong "yes" costs the element every style it has, so this is
746
+ * deliberately conservative.
747
+ */
748
+ groupIsExact(result) {
749
+ if (result.data.length !== 1) return false;
750
+ return findUnresolvedStyles(result).length === 0;
576
751
  }
577
752
  setRecipe(recipeName, result) {
578
753
  (0, _bamboocss_shared.getOrCreateSet)(this.recipe, recipeName).add(this.append(Object.assign({ type: "recipe" }, result)));
@@ -601,7 +776,7 @@ var ParserResult = class {
601
776
  result.cva.forEach((item) => this.cva.add(this.append(item)));
602
777
  result.sva.forEach((item) => this.sva.add(this.append(item)));
603
778
  result.token.forEach((item) => this.token.add(this.append(item)));
604
- result.jsx.forEach((item) => this.jsx.add(this.append(item)));
779
+ result.viewTransition.forEach((item) => this.viewTransition.add(this.append(item)));
605
780
  result.recipe.forEach((items, name) => {
606
781
  const set = (0, _bamboocss_shared.getOrCreateSet)(this.recipe, name);
607
782
  items.forEach((item) => set.add(this.append(item)));
@@ -610,6 +785,7 @@ var ParserResult = class {
610
785
  const set = (0, _bamboocss_shared.getOrCreateSet)(this.pattern, name);
611
786
  items.forEach((item) => set.add(this.append(item)));
612
787
  });
788
+ if (result.unresolved.length) this.unresolved.push(...result.unresolved);
613
789
  return this;
614
790
  }
615
791
  toArray() {
@@ -621,7 +797,7 @@ var ParserResult = class {
621
797
  cva: Array.from(this.cva),
622
798
  sva: Array.from(this.sva),
623
799
  token: Array.from(this.token),
624
- jsx: Array.from(this.jsx),
800
+ viewTransition: Array.from(this.viewTransition),
625
801
  recipe: Object.fromEntries(Array.from(this.recipe.entries()).map(([key, value]) => [key, Array.from(value)])),
626
802
  pattern: Object.fromEntries(Array.from(this.pattern.entries()).map(([key, value]) => [key, Array.from(value)]))
627
803
  };
@@ -646,8 +822,7 @@ const defaultEnv = { preset: "ECMA" };
646
822
  const fallbackImpl = (...values) => values.some((value) => value === void 0) ? void 0 : `fallback(${values.join(", ")})`;
647
823
  const evaluateOptions = { environment: defaultEnv };
648
824
  function createParser(context) {
649
- const { jsx, imports, recipes, config } = context;
650
- const syntax = config.syntax;
825
+ const { jsx, imports, recipes } = context;
651
826
  return function parse(sourceFile, encoder, options) {
652
827
  if (!sourceFile) return;
653
828
  const importDeclarations = getImportDeclarations(context, sourceFile);
@@ -682,12 +857,8 @@ function createParser(context) {
682
857
  functions: {
683
858
  matchFn: (prop) => file.matchFn(prop.fnName),
684
859
  matchProp: () => true,
685
- matchArg: (prop) => {
686
- if (file.isJsxFactory(prop.fnName) && prop.index === 1 && ts_morph.Node.isIdentifier(prop.argNode)) return false;
687
- return true;
688
- }
860
+ matchArg: () => true
689
861
  },
690
- taggedTemplates: syntax === "template-literal" ? { matchTaggedTemplate: (tag) => file.matchFn(tag.fnName) } : void 0,
691
862
  getEvaluateOptions: (node) => {
692
863
  if (!ts_morph.Node.isCallExpression(node)) return evaluateOptions;
693
864
  const propAccessExpr = node.getExpression();
@@ -721,14 +892,6 @@ function createParser(context) {
721
892
  box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
722
893
  data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
723
894
  });
724
- else if (query.kind === "tagged-template") {
725
- const obj = (0, _bamboocss_shared.astish)(query.box.value);
726
- parserResult.set(name, {
727
- name,
728
- box: query.box ?? _bamboocss_extractor.box.fallback(query.box),
729
- data: [obj]
730
- });
731
- }
732
895
  });
733
896
  }).when(imports.matchers.tokens.match, (name) => {
734
897
  result.queryList.forEach((query) => {
@@ -754,116 +917,28 @@ function createParser(context) {
754
917
  data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
755
918
  });
756
919
  });
757
- }).when(jsx.isJsxFactory, () => {
758
- result.queryList.forEach((query) => {
759
- if (query.kind === "call-expression" && query.box.value[1]) {
760
- const map = query.box.value[1];
761
- const boxNode = _bamboocss_extractor.box.isMap(map) ? map : _bamboocss_extractor.box.fallback(query.box);
762
- const combined = combineResult((0, _bamboocss_extractor.unbox)(boxNode));
763
- const result = {
764
- name,
765
- box: boxNode,
766
- data: options?.transform?.({
767
- type: "jsx-factory",
768
- data: combined
769
- }) ?? combined
770
- };
771
- if (_bamboocss_extractor.box.isRecipe(map)) parserResult.setCva(result);
772
- else parserResult.set("css", result);
773
- const recipeOptions = query.box.value[2];
774
- if (_bamboocss_extractor.box.isUnresolvable(map) && recipeOptions && _bamboocss_extractor.box.isMap(recipeOptions) && recipeOptions.value.has("defaultProps")) {
775
- const maybeIdentifier = map.getNode();
776
- if (ts_morph.Node.isIdentifier(maybeIdentifier)) {
777
- const name = maybeIdentifier.getText();
778
- const recipeName = file.getName(name);
779
- parserResult.setRecipe(recipeName, {
780
- type: "jsx-recipe",
781
- name: recipeName,
782
- box: recipeOptions,
783
- data: combineResult((0, _bamboocss_extractor.unbox)(recipeOptions.value.get("defaultProps")))
784
- });
785
- }
786
- }
787
- } else if (query.kind === "tagged-template") {
788
- const obj = (0, _bamboocss_shared.astish)(query.box.value);
789
- parserResult.set("css", {
790
- name,
791
- box: query.box ?? _bamboocss_extractor.box.fallback(query.box),
792
- data: [obj]
793
- });
794
- }
795
- });
796
- }).when(file.isJsxFactory, (name) => {
920
+ }).when(file.isViewTransitionFn, (name) => {
797
921
  result.queryList.forEach((query) => {
798
- if (query.kind === "call-expression") {
799
- const map = query.box.value[0];
800
- const boxNode = _bamboocss_extractor.box.isMap(map) ? map : _bamboocss_extractor.box.fallback(query.box);
801
- const combined = combineResult((0, _bamboocss_extractor.unbox)(boxNode));
802
- const result = {
803
- name,
804
- box: boxNode,
805
- data: options?.transform?.({
806
- type: "jsx-factory",
807
- data: combined
808
- }) ?? combined
809
- };
810
- if (_bamboocss_extractor.box.isRecipe(map)) parserResult.setCva(result);
811
- else parserResult.set("css", result);
812
- } else if (query.kind === "tagged-template") {
813
- const obj = (0, _bamboocss_shared.astish)(query.box.value);
814
- parserResult.set("css", {
815
- name,
816
- box: query.box ?? _bamboocss_extractor.box.fallback(query.box),
817
- data: [obj]
818
- });
819
- }
922
+ if (query.kind === "call-expression") parserResult.setViewTransition({
923
+ name,
924
+ box: query.box.value[0] ?? _bamboocss_extractor.box.fallback(query.box),
925
+ data: combineResult((0, _bamboocss_extractor.unbox)(query.box.value[0]))
926
+ });
820
927
  });
821
928
  }).otherwise(() => {});
822
929
  else if (jsx.isEnabled && result.kind === "component") result.queryList.forEach((query) => {
823
930
  const data = combineResult((0, _bamboocss_extractor.unbox)(query.box));
824
- switch (true) {
825
- case file.isJsxFactory(name) || file.isJsxFactory(alias):
826
- parserResult.setJsx({
827
- type: "jsx-factory",
828
- name,
829
- box: query.box,
830
- data
831
- });
832
- break;
833
- case jsx.isJsxTagPattern(name) || jsx.isJsxTagPattern(alias):
834
- parserResult.setPattern(name, {
835
- type: "jsx-pattern",
836
- name,
931
+ for (const tag of [name, alias]) {
932
+ if (!jsx.isJsxTagRecipe(tag)) continue;
933
+ recipes.filter(tag).forEach((recipe) => {
934
+ parserResult.setRecipe(recipe.baseName, {
935
+ type: "jsx-recipe",
936
+ name: tag,
837
937
  box: query.box,
838
938
  data
839
939
  });
840
- break;
841
- case jsx.isJsxTagRecipe(name):
842
- recipes.filter(name).map((recipe) => {
843
- parserResult.setRecipe(recipe.baseName, {
844
- type: "jsx-recipe",
845
- name,
846
- box: query.box,
847
- data
848
- });
849
- });
850
- break;
851
- case jsx.isJsxTagRecipe(alias):
852
- recipes.filter(alias).map((recipe) => {
853
- parserResult.setRecipe(recipe.baseName, {
854
- type: "jsx-recipe",
855
- name: alias,
856
- box: query.box,
857
- data
858
- });
859
- });
860
- break;
861
- default: parserResult.setJsx({
862
- type: "jsx",
863
- name,
864
- box: query.box,
865
- data
866
940
  });
941
+ break;
867
942
  }
868
943
  });
869
944
  });
@@ -1074,7 +1149,6 @@ var Project = class {
1074
1149
  }
1075
1150
  }) ?? this.transformFile(filePath, original);
1076
1151
  if (original !== transformed) sourceFile.replaceWithText(transformed);
1077
- if (hooks["parser:preprocess"]) options.transform = hooks["parser:preprocess"];
1078
1152
  const result = this.parser(sourceFile, encoder, options)?.setFilePath(filePath);
1079
1153
  hooks["parser:after"]?.({
1080
1154
  filePath,
@@ -1093,3 +1167,4 @@ var Project = class {
1093
1167
  //#endregion
1094
1168
  exports.ParserResult = ParserResult;
1095
1169
  exports.Project = Project;
1170
+ exports.findUnresolvedStyles = findUnresolvedStyles;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Context, ParserOptions, StyleDecoder, Stylesheet } from "@bamboocss/core";
2
- import { ArtifactId, BambooHooks, ConfigTsOptions, CssArtifactType, JsxFactoryResultTransform, LoadConfigResult, ParserResultConfigureOptions, ParserResultInterface, ResultItem, Runtime, SpecFile, SpecType, SpecTypeMap } from "@bamboocss/types";
2
+ import { ArtifactId, BambooHooks, ConfigTsOptions, CssArtifactType, LoadConfigResult, ParserResultConfigureOptions, ParserResultInterface, ResultItem, Runtime, SpecFile, SpecType, SpecTypeMap } from "@bamboocss/types";
3
3
  import { FileSystemRefreshResult, Project as Project$1, ProjectOptions as ProjectOptions$1, SourceFile } from "ts-morph";
4
4
 
5
5
  //#region ../generator/dist/index.d.cts
@@ -93,6 +93,20 @@ declare class Generator extends Context {
93
93
  */
94
94
  private getAlwaysKeptTokenVars;
95
95
  getParserCss: (decoder: StyleDecoder) => string;
96
+ /**
97
+ * The grouped class names this build emitted a rule for.
98
+ *
99
+ * Derived from the encoder rather than the decoder, so it is available as soon as
100
+ * extraction finishes and before a stylesheet exists. Both sides go through
101
+ * `groupClassName`, which is the same function the browser runtime calls — a registry
102
+ * built any other way would be a third spelling of a name that already has two.
103
+ *
104
+ * Unescaped, unlike `StyleDecoder`'s class names: this is compared against what `css()`
105
+ * returns into a `class` attribute, not against a selector. A grouped class is an opaque
106
+ * hash, so the two only differ in principle, but the principle is the one that matters
107
+ * here — the registry is only useful if it holds exactly what the runtime will ask about.
108
+ */
109
+ getGroupRegistry: () => string[];
96
110
  getCss: (stylesheet?: Stylesheet) => string;
97
111
  /**
98
112
  * Get CSS for a specific layer from the stylesheet
@@ -119,29 +133,94 @@ declare class Generator extends Context {
119
133
  * Get CSS for a specific theme
120
134
  */
121
135
  //#endregion
136
+ //#region src/unresolved-styles.d.ts
137
+ interface UnresolvedStyle {
138
+ /** The property the build could not resolve, or `undefined` when only the count differs. */
139
+ prop?: string;
140
+ filePath: string;
141
+ line: number;
142
+ column: number;
143
+ /**
144
+ * How the build lost the call:
145
+ *
146
+ * - `unresolvable-value` — a value it could not evaluate.
147
+ * - `missing-property` — a key that never arrived in the box tree at all.
148
+ * - `unenumerable-keys` — a spread or computed key, so it cannot say what the call sets.
149
+ * - `ambiguous-merge` — two arguments setting one property, which it cannot tell from a
150
+ * pair of alternatives.
151
+ * - `too-many-combinations` — more ternary branches than it will enumerate.
152
+ */
153
+ reason: 'unresolvable-value' | 'missing-property' | 'unenumerable-keys' | 'ambiguous-merge' | 'too-many-combinations';
154
+ }
155
+ /**
156
+ * Every property of a `css()` call that will not reach the stylesheet.
157
+ *
158
+ * Only meaningful under `cssMode: 'grouped'`, where one class names the whole call: a
159
+ * property the build cannot resolve does not merely go missing, it changes the class, and
160
+ * the element renders with no styles at all. Under `atomic` the same call loses one
161
+ * declaration and keeps the rest, which is why this is not reported there.
162
+ */
163
+ declare const findUnresolvedStyles: (item: ResultItem) => UnresolvedStyle[];
164
+ //#endregion
122
165
  //#region src/parser-result.d.ts
123
166
  declare class ParserResult implements ParserResultInterface {
124
167
  private context;
125
168
  /** Ordered list of all ResultItem */
126
169
  all: ResultItem[];
127
- jsx: Set<ResultItem>;
128
170
  css: Set<ResultItem>;
129
171
  cva: Set<ResultItem>;
130
172
  sva: Set<ResultItem>;
131
173
  token: Set<ResultItem>;
174
+ viewTransition: Set<ResultItem>;
132
175
  recipe: Map<string, Set<ResultItem>>;
133
176
  pattern: Map<string, Set<ResultItem>>;
134
177
  filePath: string | undefined;
135
178
  encoder: ParserOptions['encoder'];
179
+ /**
180
+ * `css()` calls whose styles the build could not fully see.
181
+ *
182
+ * Only collected under `cssMode: 'grouped'`, where one class names the whole call, so a
183
+ * property the build cannot resolve changes the class rather than dropping a declaration
184
+ * from it — and the element renders with no styles at all. Under `atomic` the same call
185
+ * keeps everything the build did resolve, which is not worth interrupting a build over.
186
+ */
187
+ unresolved: UnresolvedStyle[];
136
188
  constructor(context: ParserOptions, encoder?: ParserOptions['encoder']);
189
+ /**
190
+ * Record a call whose styles the build could not fully see, at the call's own position.
191
+ *
192
+ * Used for losses the box tree cannot show — a ternary past the combination cap emits
193
+ * fragments rather than whole objects, and every individual box in it resolved fine.
194
+ */
195
+ private reportUnresolved;
137
196
  append(result: ResultItem): ResultItem;
138
197
  set(name: 'cva' | 'css' | 'sva' | 'token', result: ResultItem): void;
139
198
  setCss(result: ResultItem): void;
199
+ /**
200
+ * How many arguments the call this result came from was written with.
201
+ *
202
+ * Returns 1 for anything that is not a call — a JSX element, or a box that lost its node —
203
+ * since the question only separates operands from branches and neither has operands.
204
+ */
205
+ private callArgumentCount;
140
206
  setCva(result: ResultItem): void;
141
207
  setSva(result: ResultItem): void;
142
208
  setToken(result: ResultItem): void;
143
- setJsx(result: ResultItem): void;
209
+ setViewTransition(result: ResultItem): void;
144
210
  setPattern(name: string, result: ResultItem): void;
211
+ /**
212
+ * Whether the group encoded for this result is the one the runtime will ask for.
213
+ *
214
+ * True only when the build saw the whole thing at once: one style object, with every
215
+ * value in it resolved. Several objects means the runtime merges them into a call this
216
+ * never encoded — `setCss` reconstructs those combinations, and nothing else does — and
217
+ * an unresolved value means the merge would not have matched anyway.
218
+ *
219
+ * Answering "no" costs a call site its atomic rules, which is CSS that duplicates the
220
+ * group. Answering a wrong "yes" costs the element every style it has, so this is
221
+ * deliberately conservative.
222
+ */
223
+ private groupIsExact;
145
224
  setRecipe(recipeName: string, result: ResultItem): void;
146
225
  isEmpty(): boolean;
147
226
  setFilePath(filePath: string): this;
@@ -152,7 +231,7 @@ declare class ParserResult implements ParserResultInterface {
152
231
  cva: ResultItem[];
153
232
  sva: ResultItem[];
154
233
  token: ResultItem[];
155
- jsx: ResultItem[];
234
+ viewTransition: ResultItem[];
156
235
  recipe: {
157
236
  [k: string]: ResultItem[];
158
237
  };
@@ -163,7 +242,7 @@ declare class ParserResult implements ParserResultInterface {
163
242
  }
164
243
  //#endregion
165
244
  //#region src/parser.d.ts
166
- declare function createParser(context: ParserOptions): (sourceFile: SourceFile | undefined, encoder?: Generator["encoder"], options?: ParserResultConfigureOptions & Partial<JsxFactoryResultTransform>) => ParserResult | undefined;
245
+ declare function createParser(context: ParserOptions): (sourceFile: SourceFile | undefined, encoder?: Generator["encoder"], options?: ParserResultConfigureOptions) => ParserResult | undefined;
167
246
  //#endregion
168
247
  //#region src/project.d.ts
169
248
  interface ProjectOptions extends ProjectOptions$1 {
@@ -248,4 +327,4 @@ declare class Project {
248
327
  classify: (fileMap: Map<string, ParserResultInterface>) => import("@bamboocss/types").ClassifyReport;
249
328
  }
250
329
  //#endregion
251
- export { ParserResult, Project, ProjectOptions };
330
+ export { ParserResult, Project, ProjectOptions, type UnresolvedStyle, findUnresolvedStyles };