@bamboocss/extractor 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
@@ -116,6 +116,29 @@ const recipeProps = [
116
116
  var BoxNodeMap = class extends BoxNodeType {
117
117
  value;
118
118
  spreadConditions;
119
+ /**
120
+ * Spreads the extractor walked structurally, paired with what it walked them into.
121
+ *
122
+ * The map records what a spread *contributed*, so a spread that flattened and one that
123
+ * was silently skipped look identical once folded in — both simply add keys, or fail to.
124
+ * That ambiguity is why a consumer rewriting source would otherwise have to decline every
125
+ * spread rather than only the ones it cannot account for.
126
+ *
127
+ * The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
128
+ * "may I trust this spread", and a list of failures answers that only while it is
129
+ * exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
130
+ * is a fold that does not happen.
131
+ *
132
+ * `box` is the map the spread flattened, and being here is **not** a promise that every
133
+ * one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
134
+ * is the handle a consumer needs to go and check for itself, which it cannot do from the
135
+ * flattened result. `node` is the spread's own expression, so the pair can be matched
136
+ * against the source being inspected.
137
+ *
138
+ * Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
139
+ * extraction, not the styles, and nothing that generates CSS should see it.
140
+ */
141
+ resolvedSpreads;
119
142
  constructor(definition) {
120
143
  super(definition);
121
144
  this.value = definition.value;
@@ -440,6 +463,8 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
440
463
  if (properties.length === 0) return box.emptyObject(expression, expressionStack);
441
464
  const extractedPropValues = [];
442
465
  const spreadConditions = [];
466
+ /** Spreads the extractor walked structurally — see `BoxNodeMap.resolvedSpreads`. */
467
+ const resolvedSpreads = [];
443
468
  properties.forEach((property) => {
444
469
  const stack = [...expressionStack];
445
470
  stack.push(property);
@@ -492,6 +517,10 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
492
517
  maybeObject.value.forEach((nested, propName) => {
493
518
  extractedPropValues.push([propName, nested]);
494
519
  });
520
+ resolvedSpreads.push({
521
+ node: initializer,
522
+ box: maybeObject
523
+ });
495
524
  return;
496
525
  }
497
526
  if (box.isConditional(maybeObject)) spreadConditions.push(maybeObject);
@@ -503,6 +532,7 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
503
532
  orderedMapValue.set(propName, value);
504
533
  });
505
534
  const map = box.map(orderedMapValue, expression, expressionStack);
535
+ if (resolvedSpreads.length > 0) map.resolvedSpreads = resolvedSpreads;
506
536
  if (spreadConditions.length > 0) map.spreadConditions = spreadConditions;
507
537
  return map;
508
538
  };
@@ -631,6 +661,7 @@ function maybeBoxNode(node, stack, ctx, matchProp) {
631
661
  if (isPlusSyntax(operatorKind)) {
632
662
  const value = tryComputingPlusTokenBinaryExpressionToString(node, stack, ctx) ?? safeEvaluateNode(node, stack, ctx);
633
663
  if (!value) return;
664
+ if (typeof value === "string" && value.includes("undefined")) return;
634
665
  return cache(box.from(value, node, stack));
635
666
  }
636
667
  if (isLogicalSyntax(operatorKind)) return cache(maybeResolveConditionalExpression({
@@ -1492,7 +1523,7 @@ const objectLikeToMap = (maybeObject, node) => {
1492
1523
  const isImportOrExport = (node) => ts_morph.Node.isImportDeclaration(node) || ts_morph.Node.isExportDeclaration(node);
1493
1524
  const isJsxElement = (node) => ts_morph.Node.isJsxOpeningElement(node) || ts_morph.Node.isJsxSelfClosingElement(node);
1494
1525
  const extract = ({ ast, ...ctx }) => {
1495
- const { components, functions, taggedTemplates } = ctx;
1526
+ const { components, functions } = ctx;
1496
1527
  const compiledJsx = createCompiledJsxContext(ast);
1497
1528
  /** contains all the extracted nodes from this ast parsing */
1498
1529
  const byName = /* @__PURE__ */ new Map();
@@ -1662,6 +1693,7 @@ const extract = ({ ast, ...ctx }) => {
1662
1693
  });
1663
1694
  const boxMap = box.map(mapValue, node, boxNode.getStack());
1664
1695
  if (box.isMap(boxNode) && boxNode.spreadConditions?.length) boxMap.spreadConditions = boxNode.spreadConditions;
1696
+ if (box.isMap(boxNode) && boxNode.resolvedSpreads?.length) boxMap.resolvedSpreads = boxNode.resolvedSpreads;
1665
1697
  return boxMap;
1666
1698
  }
1667
1699
  return boxNode;
@@ -1673,26 +1705,6 @@ const extract = ({ ast, ...ctx }) => {
1673
1705
  };
1674
1706
  fnResultMap.queryList.push(query);
1675
1707
  }
1676
- if (taggedTemplates && ts_morph.Node.isTaggedTemplateExpression(node)) {
1677
- const tag = node.getTag();
1678
- const fnName = ts_morph.Node.isCallExpression(tag) ? tag.getExpression().getText() : tag.getText();
1679
- if (!taggedTemplates.matchTaggedTemplate({
1680
- taggedTemplateNode: node,
1681
- fnName
1682
- })) return;
1683
- if (!byName.has(fnName)) byName.set(fnName, {
1684
- kind: "function",
1685
- nodesByProp: /* @__PURE__ */ new Map(),
1686
- queryList: []
1687
- });
1688
- const fnResultMap = byName.get(fnName);
1689
- const query = {
1690
- kind: "tagged-template",
1691
- name: fnName,
1692
- box: maybeBoxNode(node, [], ctx)
1693
- };
1694
- fnResultMap.queryList.push(query);
1695
- }
1696
1708
  });
1697
1709
  componentByNode.forEach((parentRef, componentNode) => {
1698
1710
  const component = componentByNode.get(componentNode);
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile, TaggedTemplateExpression, VariableDeclaration } from "ts-morph";
1
+ import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile, VariableDeclaration } from "ts-morph";
2
2
  import { EvaluateOptions as EvaluateOptions$1 } from "ts-evaluator";
3
3
 
4
4
  //#region src/box-factory.d.ts
@@ -79,6 +79,32 @@ declare class BoxNodeLiteral extends BoxNodeType$1<LiteralType> {
79
79
  declare class BoxNodeMap extends BoxNodeType$1<MapType> {
80
80
  value: MapType['value'];
81
81
  spreadConditions?: BoxNodeConditional[];
82
+ /**
83
+ * Spreads the extractor walked structurally, paired with what it walked them into.
84
+ *
85
+ * The map records what a spread *contributed*, so a spread that flattened and one that
86
+ * was silently skipped look identical once folded in — both simply add keys, or fail to.
87
+ * That ambiguity is why a consumer rewriting source would otherwise have to decline every
88
+ * spread rather than only the ones it cannot account for.
89
+ *
90
+ * The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
91
+ * "may I trust this spread", and a list of failures answers that only while it is
92
+ * exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
93
+ * is a fold that does not happen.
94
+ *
95
+ * `box` is the map the spread flattened, and being here is **not** a promise that every
96
+ * one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
97
+ * is the handle a consumer needs to go and check for itself, which it cannot do from the
98
+ * flattened result. `node` is the spread's own expression, so the pair can be matched
99
+ * against the source being inspected.
100
+ *
101
+ * Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
102
+ * extraction, not the styles, and nothing that generates CSS should see it.
103
+ */
104
+ resolvedSpreads?: Array<{
105
+ node: Node;
106
+ box: BoxNodeMap;
107
+ }>;
82
108
  constructor(definition: MapType);
83
109
  isRecipe: () => boolean;
84
110
  }
@@ -111,16 +137,10 @@ interface ExtractedFunctionInstance {
111
137
  fromNode: () => CallExpression;
112
138
  box: BoxNodeArray;
113
139
  }
114
- interface ExtractedTaggedTemplateInstance {
115
- name: string;
116
- kind: 'tagged-template';
117
- fromNode: () => TaggedTemplateExpression;
118
- box: BoxNodeLiteral;
119
- }
120
140
  interface ExtractedFunctionResult {
121
141
  kind: 'function';
122
142
  nodesByProp: Map<string, BoxNode[]>;
123
- queryList: Array<ExtractedFunctionInstance | ExtractedTaggedTemplateInstance>;
143
+ queryList: ExtractedFunctionInstance[];
124
144
  }
125
145
  interface ExtractedComponentInstance {
126
146
  name: string;
@@ -164,11 +184,6 @@ interface ComponentMatchers {
164
184
  matchTag: (element: MatchTagArgs) => boolean;
165
185
  matchProp: (prop: Pick<MatchTagArgs, 'tagName' | 'tagNode'> & MatchPropArgs) => boolean;
166
186
  }
167
- interface MatchTaggedTemplateArgs {
168
- fnName: string;
169
- taggedTemplateNode: TaggedTemplateExpression;
170
- }
171
- type MatchTaggedTemplate = (tag: MatchTaggedTemplateArgs) => boolean;
172
187
  interface BoxContext {
173
188
  getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
174
189
  canEval?: (node: Expression, stack: Node[]) => boolean;
@@ -190,9 +205,6 @@ type ExtractOptions = BoxContext & {
190
205
  ast: SourceFile;
191
206
  components?: ComponentMatchers;
192
207
  functions?: FunctionMatchers;
193
- taggedTemplates?: {
194
- matchTaggedTemplate: MatchTaggedTemplate;
195
- };
196
208
  };
197
209
  //#endregion
198
210
  //#region src/to-box-node.d.ts
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile, TaggedTemplateExpression, VariableDeclaration } from "ts-morph";
1
+ import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile, VariableDeclaration } from "ts-morph";
2
2
  import { EvaluateOptions as EvaluateOptions$1 } from "ts-evaluator";
3
3
 
4
4
  //#region src/box-factory.d.ts
@@ -79,6 +79,32 @@ declare class BoxNodeLiteral extends BoxNodeType$1<LiteralType> {
79
79
  declare class BoxNodeMap extends BoxNodeType$1<MapType> {
80
80
  value: MapType['value'];
81
81
  spreadConditions?: BoxNodeConditional[];
82
+ /**
83
+ * Spreads the extractor walked structurally, paired with what it walked them into.
84
+ *
85
+ * The map records what a spread *contributed*, so a spread that flattened and one that
86
+ * was silently skipped look identical once folded in — both simply add keys, or fail to.
87
+ * That ambiguity is why a consumer rewriting source would otherwise have to decline every
88
+ * spread rather than only the ones it cannot account for.
89
+ *
90
+ * The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
91
+ * "may I trust this spread", and a list of failures answers that only while it is
92
+ * exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
93
+ * is a fold that does not happen.
94
+ *
95
+ * `box` is the map the spread flattened, and being here is **not** a promise that every
96
+ * one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
97
+ * is the handle a consumer needs to go and check for itself, which it cannot do from the
98
+ * flattened result. `node` is the spread's own expression, so the pair can be matched
99
+ * against the source being inspected.
100
+ *
101
+ * Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
102
+ * extraction, not the styles, and nothing that generates CSS should see it.
103
+ */
104
+ resolvedSpreads?: Array<{
105
+ node: Node;
106
+ box: BoxNodeMap;
107
+ }>;
82
108
  constructor(definition: MapType);
83
109
  isRecipe: () => boolean;
84
110
  }
@@ -111,16 +137,10 @@ interface ExtractedFunctionInstance {
111
137
  fromNode: () => CallExpression;
112
138
  box: BoxNodeArray;
113
139
  }
114
- interface ExtractedTaggedTemplateInstance {
115
- name: string;
116
- kind: 'tagged-template';
117
- fromNode: () => TaggedTemplateExpression;
118
- box: BoxNodeLiteral;
119
- }
120
140
  interface ExtractedFunctionResult {
121
141
  kind: 'function';
122
142
  nodesByProp: Map<string, BoxNode[]>;
123
- queryList: Array<ExtractedFunctionInstance | ExtractedTaggedTemplateInstance>;
143
+ queryList: ExtractedFunctionInstance[];
124
144
  }
125
145
  interface ExtractedComponentInstance {
126
146
  name: string;
@@ -164,11 +184,6 @@ interface ComponentMatchers {
164
184
  matchTag: (element: MatchTagArgs) => boolean;
165
185
  matchProp: (prop: Pick<MatchTagArgs, 'tagName' | 'tagNode'> & MatchPropArgs) => boolean;
166
186
  }
167
- interface MatchTaggedTemplateArgs {
168
- fnName: string;
169
- taggedTemplateNode: TaggedTemplateExpression;
170
- }
171
- type MatchTaggedTemplate = (tag: MatchTaggedTemplateArgs) => boolean;
172
187
  interface BoxContext {
173
188
  getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
174
189
  canEval?: (node: Expression, stack: Node[]) => boolean;
@@ -190,9 +205,6 @@ type ExtractOptions = BoxContext & {
190
205
  ast: SourceFile;
191
206
  components?: ComponentMatchers;
192
207
  functions?: FunctionMatchers;
193
- taggedTemplates?: {
194
- matchTaggedTemplate: MatchTaggedTemplate;
195
- };
196
208
  };
197
209
  //#endregion
198
210
  //#region src/to-box-node.d.ts
package/dist/index.mjs CHANGED
@@ -115,6 +115,29 @@ const recipeProps = [
115
115
  var BoxNodeMap = class extends BoxNodeType {
116
116
  value;
117
117
  spreadConditions;
118
+ /**
119
+ * Spreads the extractor walked structurally, paired with what it walked them into.
120
+ *
121
+ * The map records what a spread *contributed*, so a spread that flattened and one that
122
+ * was silently skipped look identical once folded in — both simply add keys, or fail to.
123
+ * That ambiguity is why a consumer rewriting source would otherwise have to decline every
124
+ * spread rather than only the ones it cannot account for.
125
+ *
126
+ * The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
127
+ * "may I trust this spread", and a list of failures answers that only while it is
128
+ * exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
129
+ * is a fold that does not happen.
130
+ *
131
+ * `box` is the map the spread flattened, and being here is **not** a promise that every
132
+ * one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
133
+ * is the handle a consumer needs to go and check for itself, which it cannot do from the
134
+ * flattened result. `node` is the spread's own expression, so the pair can be matched
135
+ * against the source being inspected.
136
+ *
137
+ * Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
138
+ * extraction, not the styles, and nothing that generates CSS should see it.
139
+ */
140
+ resolvedSpreads;
118
141
  constructor(definition) {
119
142
  super(definition);
120
143
  this.value = definition.value;
@@ -439,6 +462,8 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
439
462
  if (properties.length === 0) return box.emptyObject(expression, expressionStack);
440
463
  const extractedPropValues = [];
441
464
  const spreadConditions = [];
465
+ /** Spreads the extractor walked structurally — see `BoxNodeMap.resolvedSpreads`. */
466
+ const resolvedSpreads = [];
442
467
  properties.forEach((property) => {
443
468
  const stack = [...expressionStack];
444
469
  stack.push(property);
@@ -491,6 +516,10 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
491
516
  maybeObject.value.forEach((nested, propName) => {
492
517
  extractedPropValues.push([propName, nested]);
493
518
  });
519
+ resolvedSpreads.push({
520
+ node: initializer,
521
+ box: maybeObject
522
+ });
494
523
  return;
495
524
  }
496
525
  if (box.isConditional(maybeObject)) spreadConditions.push(maybeObject);
@@ -502,6 +531,7 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
502
531
  orderedMapValue.set(propName, value);
503
532
  });
504
533
  const map = box.map(orderedMapValue, expression, expressionStack);
534
+ if (resolvedSpreads.length > 0) map.resolvedSpreads = resolvedSpreads;
505
535
  if (spreadConditions.length > 0) map.spreadConditions = spreadConditions;
506
536
  return map;
507
537
  };
@@ -630,6 +660,7 @@ function maybeBoxNode(node, stack, ctx, matchProp) {
630
660
  if (isPlusSyntax(operatorKind)) {
631
661
  const value = tryComputingPlusTokenBinaryExpressionToString(node, stack, ctx) ?? safeEvaluateNode(node, stack, ctx);
632
662
  if (!value) return;
663
+ if (typeof value === "string" && value.includes("undefined")) return;
633
664
  return cache(box.from(value, node, stack));
634
665
  }
635
666
  if (isLogicalSyntax(operatorKind)) return cache(maybeResolveConditionalExpression({
@@ -1491,7 +1522,7 @@ const objectLikeToMap = (maybeObject, node) => {
1491
1522
  const isImportOrExport = (node) => Node.isImportDeclaration(node) || Node.isExportDeclaration(node);
1492
1523
  const isJsxElement = (node) => Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node);
1493
1524
  const extract = ({ ast, ...ctx }) => {
1494
- const { components, functions, taggedTemplates } = ctx;
1525
+ const { components, functions } = ctx;
1495
1526
  const compiledJsx = createCompiledJsxContext(ast);
1496
1527
  /** contains all the extracted nodes from this ast parsing */
1497
1528
  const byName = /* @__PURE__ */ new Map();
@@ -1661,6 +1692,7 @@ const extract = ({ ast, ...ctx }) => {
1661
1692
  });
1662
1693
  const boxMap = box.map(mapValue, node, boxNode.getStack());
1663
1694
  if (box.isMap(boxNode) && boxNode.spreadConditions?.length) boxMap.spreadConditions = boxNode.spreadConditions;
1695
+ if (box.isMap(boxNode) && boxNode.resolvedSpreads?.length) boxMap.resolvedSpreads = boxNode.resolvedSpreads;
1664
1696
  return boxMap;
1665
1697
  }
1666
1698
  return boxNode;
@@ -1672,26 +1704,6 @@ const extract = ({ ast, ...ctx }) => {
1672
1704
  };
1673
1705
  fnResultMap.queryList.push(query);
1674
1706
  }
1675
- if (taggedTemplates && Node.isTaggedTemplateExpression(node)) {
1676
- const tag = node.getTag();
1677
- const fnName = Node.isCallExpression(tag) ? tag.getExpression().getText() : tag.getText();
1678
- if (!taggedTemplates.matchTaggedTemplate({
1679
- taggedTemplateNode: node,
1680
- fnName
1681
- })) return;
1682
- if (!byName.has(fnName)) byName.set(fnName, {
1683
- kind: "function",
1684
- nodesByProp: /* @__PURE__ */ new Map(),
1685
- queryList: []
1686
- });
1687
- const fnResultMap = byName.get(fnName);
1688
- const query = {
1689
- kind: "tagged-template",
1690
- name: fnName,
1691
- box: maybeBoxNode(node, [], ctx)
1692
- };
1693
- fnResultMap.queryList.push(query);
1694
- }
1695
1707
  });
1696
1708
  componentByNode.forEach((parentRef, componentNode) => {
1697
1709
  const component = componentByNode.get(componentNode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/extractor",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "The css extractor for css bamboo",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "ts-evaluator": "1.2.0",
37
37
  "ts-morph": "28.0.0",
38
- "@bamboocss/shared": "1.14.0"
38
+ "@bamboocss/shared": "1.16.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsdown src/index.ts --format=cjs,esm --shims --dts",