@bamboocss/extractor 1.15.0 → 1.16.1

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({
@@ -1348,6 +1379,34 @@ const runtimeProfiles = [
1348
1379
  getPropNodes: getArgsAt(1)
1349
1380
  }
1350
1381
  ];
1382
+ /**
1383
+ * The declaration `identifier` binds to, found by walking its enclosing scopes outward.
1384
+ *
1385
+ * Deliberately not `identifier.getDefinitions()`. That is a language-service query, and
1386
+ * the first one forces `synchronizeHostData` -> `createProgram`, which resolves, parses
1387
+ * and binds the whole transitive `.d.ts` closure of the project — in a 5-file sandbox
1388
+ * that is 161 files and 5.1MB, most of it `node_modules`, and it grows with the
1389
+ * dependency graph rather than with the user's source. The extractor is built to avoid
1390
+ * exactly that: `createTsProject` sets `skipAddingFilesFromTsConfig`,
1391
+ * `skipFileDependencyResolution` and `skipLoadingLibFiles`, and `getModuleSpecifierSourceFile`
1392
+ * carries the same note. Inside a bundler the program is built alongside the module graph
1393
+ * in one heap, so the cost lands as a slow build and then an OOM.
1394
+ *
1395
+ * A lexical walk is enough because this only ever runs for a callee that is declared in
1396
+ * this file: `resolveCallee` matches against the import map first, and `collectImports`
1397
+ * covers every import form. Innermost scope wins, which is how the binding resolves
1398
+ * anyway, and a scope can hold only one declaration of a given name.
1399
+ */
1400
+ const findLocalDeclaration = (identifier) => {
1401
+ const name = identifier.getText();
1402
+ for (let scope = identifier.getParent(); scope; scope = scope.getParent()) {
1403
+ if (!ts_morph.Node.isStatemented(scope)) continue;
1404
+ const variable = scope.getVariableDeclaration(name);
1405
+ if (variable) return variable;
1406
+ const fn = scope.getFunction(name);
1407
+ if (fn) return fn;
1408
+ }
1409
+ };
1351
1410
  const createCompiledJsxContext = (sourceFile) => {
1352
1411
  const imports = collectImports(sourceFile);
1353
1412
  const normalizeCallee = (node) => {
@@ -1374,30 +1433,26 @@ const createCompiledJsxContext = (sourceFile) => {
1374
1433
  if (ts_morph.Node.isConditionalExpression(expression)) return resolveLocalAlias(expression.getWhenTrue()) ?? resolveLocalAlias(expression.getWhenFalse());
1375
1434
  if (ts_morph.Node.isBinaryExpression(expression) && expression.getOperatorToken().getKind() === ts_morph.SyntaxKind.CommaToken) return resolveLocalAlias(expression.getRight());
1376
1435
  };
1377
- for (const definition of identifier.getDefinitions()) {
1378
- const declaration = definition.getDeclarationNode();
1379
- if (!declaration) continue;
1380
- if (ts_morph.Node.isFunctionDeclaration(declaration)) {
1381
- const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1382
- if (!imported) continue;
1383
- const resolved = {
1384
- mod: imported.mod,
1385
- importedName: imported.importedName
1386
- };
1387
- localDefinitionCache.set(name, resolved);
1388
- return resolved;
1389
- }
1390
- if (ts_morph.Node.isVariableDeclaration(declaration)) {
1391
- const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1392
- const aliasImport = directImport ? {
1393
- mod: directImport.mod,
1394
- importedName: directImport.importedName
1395
- } : resolveLocalAlias(declaration.getInitializer());
1396
- if (!aliasImport) continue;
1397
- localDefinitionCache.set(name, aliasImport);
1398
- return aliasImport;
1399
- }
1436
+ const declaration = findLocalDeclaration(identifier);
1437
+ if (!declaration) return;
1438
+ if (ts_morph.Node.isFunctionDeclaration(declaration)) {
1439
+ const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1440
+ if (!imported) return;
1441
+ const resolved = {
1442
+ mod: imported.mod,
1443
+ importedName: imported.importedName
1444
+ };
1445
+ localDefinitionCache.set(name, resolved);
1446
+ return resolved;
1400
1447
  }
1448
+ const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1449
+ const aliasImport = directImport ? {
1450
+ mod: directImport.mod,
1451
+ importedName: directImport.importedName
1452
+ } : resolveLocalAlias(declaration.getInitializer());
1453
+ if (!aliasImport) return;
1454
+ localDefinitionCache.set(name, aliasImport);
1455
+ return aliasImport;
1401
1456
  };
1402
1457
  const resolveCallee = (node) => {
1403
1458
  const expression = normalizeCallee(node);
@@ -1492,7 +1547,7 @@ const objectLikeToMap = (maybeObject, node) => {
1492
1547
  const isImportOrExport = (node) => ts_morph.Node.isImportDeclaration(node) || ts_morph.Node.isExportDeclaration(node);
1493
1548
  const isJsxElement = (node) => ts_morph.Node.isJsxOpeningElement(node) || ts_morph.Node.isJsxSelfClosingElement(node);
1494
1549
  const extract = ({ ast, ...ctx }) => {
1495
- const { components, functions, taggedTemplates } = ctx;
1550
+ const { components, functions } = ctx;
1496
1551
  const compiledJsx = createCompiledJsxContext(ast);
1497
1552
  /** contains all the extracted nodes from this ast parsing */
1498
1553
  const byName = /* @__PURE__ */ new Map();
@@ -1662,6 +1717,7 @@ const extract = ({ ast, ...ctx }) => {
1662
1717
  });
1663
1718
  const boxMap = box.map(mapValue, node, boxNode.getStack());
1664
1719
  if (box.isMap(boxNode) && boxNode.spreadConditions?.length) boxMap.spreadConditions = boxNode.spreadConditions;
1720
+ if (box.isMap(boxNode) && boxNode.resolvedSpreads?.length) boxMap.resolvedSpreads = boxNode.resolvedSpreads;
1665
1721
  return boxMap;
1666
1722
  }
1667
1723
  return boxNode;
@@ -1673,26 +1729,6 @@ const extract = ({ ast, ...ctx }) => {
1673
1729
  };
1674
1730
  fnResultMap.queryList.push(query);
1675
1731
  }
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
1732
  });
1697
1733
  componentByNode.forEach((parentRef, componentNode) => {
1698
1734
  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({
@@ -1347,6 +1378,34 @@ const runtimeProfiles = [
1347
1378
  getPropNodes: getArgsAt(1)
1348
1379
  }
1349
1380
  ];
1381
+ /**
1382
+ * The declaration `identifier` binds to, found by walking its enclosing scopes outward.
1383
+ *
1384
+ * Deliberately not `identifier.getDefinitions()`. That is a language-service query, and
1385
+ * the first one forces `synchronizeHostData` -> `createProgram`, which resolves, parses
1386
+ * and binds the whole transitive `.d.ts` closure of the project — in a 5-file sandbox
1387
+ * that is 161 files and 5.1MB, most of it `node_modules`, and it grows with the
1388
+ * dependency graph rather than with the user's source. The extractor is built to avoid
1389
+ * exactly that: `createTsProject` sets `skipAddingFilesFromTsConfig`,
1390
+ * `skipFileDependencyResolution` and `skipLoadingLibFiles`, and `getModuleSpecifierSourceFile`
1391
+ * carries the same note. Inside a bundler the program is built alongside the module graph
1392
+ * in one heap, so the cost lands as a slow build and then an OOM.
1393
+ *
1394
+ * A lexical walk is enough because this only ever runs for a callee that is declared in
1395
+ * this file: `resolveCallee` matches against the import map first, and `collectImports`
1396
+ * covers every import form. Innermost scope wins, which is how the binding resolves
1397
+ * anyway, and a scope can hold only one declaration of a given name.
1398
+ */
1399
+ const findLocalDeclaration = (identifier) => {
1400
+ const name = identifier.getText();
1401
+ for (let scope = identifier.getParent(); scope; scope = scope.getParent()) {
1402
+ if (!Node.isStatemented(scope)) continue;
1403
+ const variable = scope.getVariableDeclaration(name);
1404
+ if (variable) return variable;
1405
+ const fn = scope.getFunction(name);
1406
+ if (fn) return fn;
1407
+ }
1408
+ };
1350
1409
  const createCompiledJsxContext = (sourceFile) => {
1351
1410
  const imports = collectImports(sourceFile);
1352
1411
  const normalizeCallee = (node) => {
@@ -1373,30 +1432,26 @@ const createCompiledJsxContext = (sourceFile) => {
1373
1432
  if (Node.isConditionalExpression(expression)) return resolveLocalAlias(expression.getWhenTrue()) ?? resolveLocalAlias(expression.getWhenFalse());
1374
1433
  if (Node.isBinaryExpression(expression) && expression.getOperatorToken().getKind() === SyntaxKind.CommaToken) return resolveLocalAlias(expression.getRight());
1375
1434
  };
1376
- for (const definition of identifier.getDefinitions()) {
1377
- const declaration = definition.getDeclarationNode();
1378
- if (!declaration) continue;
1379
- if (Node.isFunctionDeclaration(declaration)) {
1380
- const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1381
- if (!imported) continue;
1382
- const resolved = {
1383
- mod: imported.mod,
1384
- importedName: imported.importedName
1385
- };
1386
- localDefinitionCache.set(name, resolved);
1387
- return resolved;
1388
- }
1389
- if (Node.isVariableDeclaration(declaration)) {
1390
- const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1391
- const aliasImport = directImport ? {
1392
- mod: directImport.mod,
1393
- importedName: directImport.importedName
1394
- } : resolveLocalAlias(declaration.getInitializer());
1395
- if (!aliasImport) continue;
1396
- localDefinitionCache.set(name, aliasImport);
1397
- return aliasImport;
1398
- }
1435
+ const declaration = findLocalDeclaration(identifier);
1436
+ if (!declaration) return;
1437
+ if (Node.isFunctionDeclaration(declaration)) {
1438
+ const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1439
+ if (!imported) return;
1440
+ const resolved = {
1441
+ mod: imported.mod,
1442
+ importedName: imported.importedName
1443
+ };
1444
+ localDefinitionCache.set(name, resolved);
1445
+ return resolved;
1399
1446
  }
1447
+ const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1448
+ const aliasImport = directImport ? {
1449
+ mod: directImport.mod,
1450
+ importedName: directImport.importedName
1451
+ } : resolveLocalAlias(declaration.getInitializer());
1452
+ if (!aliasImport) return;
1453
+ localDefinitionCache.set(name, aliasImport);
1454
+ return aliasImport;
1400
1455
  };
1401
1456
  const resolveCallee = (node) => {
1402
1457
  const expression = normalizeCallee(node);
@@ -1491,7 +1546,7 @@ const objectLikeToMap = (maybeObject, node) => {
1491
1546
  const isImportOrExport = (node) => Node.isImportDeclaration(node) || Node.isExportDeclaration(node);
1492
1547
  const isJsxElement = (node) => Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node);
1493
1548
  const extract = ({ ast, ...ctx }) => {
1494
- const { components, functions, taggedTemplates } = ctx;
1549
+ const { components, functions } = ctx;
1495
1550
  const compiledJsx = createCompiledJsxContext(ast);
1496
1551
  /** contains all the extracted nodes from this ast parsing */
1497
1552
  const byName = /* @__PURE__ */ new Map();
@@ -1661,6 +1716,7 @@ const extract = ({ ast, ...ctx }) => {
1661
1716
  });
1662
1717
  const boxMap = box.map(mapValue, node, boxNode.getStack());
1663
1718
  if (box.isMap(boxNode) && boxNode.spreadConditions?.length) boxMap.spreadConditions = boxNode.spreadConditions;
1719
+ if (box.isMap(boxNode) && boxNode.resolvedSpreads?.length) boxMap.resolvedSpreads = boxNode.resolvedSpreads;
1664
1720
  return boxMap;
1665
1721
  }
1666
1722
  return boxNode;
@@ -1672,26 +1728,6 @@ const extract = ({ ast, ...ctx }) => {
1672
1728
  };
1673
1729
  fnResultMap.queryList.push(query);
1674
1730
  }
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
1731
  });
1696
1732
  componentByNode.forEach((parentRef, componentNode) => {
1697
1733
  const component = componentByNode.get(componentNode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/extractor",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
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.15.0"
38
+ "@bamboocss/shared": "1.16.1"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsdown src/index.ts --format=cjs,esm --shims --dts",