styled-components-to-stylex-codemod 0.0.49 → 0.0.51
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/{ast-walk-DigTJqU7.mjs → ast-walk-BOXS-DT7.mjs} +142 -1
- package/dist/{bridge-consumer-patcher-CjR2kIOd.mjs → bridge-consumer-patcher-BlOkZiEv.mjs} +1 -1
- package/dist/{prepass-parser-CwdDzSgx.mjs → compute-leaf-set-ghdXvfbp.mjs} +2 -100
- package/dist/{forwarded-as-consumer-patcher-CaLmjoiP.mjs → forwarded-as-consumer-patcher-CPBlZAjY.mjs} +1 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +117 -25
- package/dist/prop-usage-SADzZJdX.mjs +600 -0
- package/dist/{run-prepass-BWQCThfh.mjs → run-prepass-CWnoXqI6.mjs} +15 -286
- package/dist/{string-utils-DD9wdRHW.mjs → string-utils-BYTEHwNg.mjs} +2 -2
- package/dist/{sx-surface-BzqO3hcC.mjs → sx-surface-CEPFSTO1.mjs} +10 -2
- package/dist/{transform-types-CHRHLCj_.d.mts → transform-types-BIv4-1OO.d.mts} +16 -12
- package/dist/transform.d.mts +1 -1
- package/dist/transform.mjs +2778 -481
- package/dist/{transient-prop-consumer-patcher-DGSFnxSo.mjs → transient-prop-consumer-patcher-DpfimNca.mjs} +1 -1
- package/package.json +1 -1
- package/dist/prop-usage-QtOSsKTr.mjs +0 -79
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import { n as createPrepassParser } from "./ast-walk-BOXS-DT7.mjs";
|
|
2
|
+
import { n as isTemplatePlaceholderInSelectorContext, r as PLACEHOLDER_RE } from "./selector-context-heuristic-LVizWWOR.mjs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import "node:fs";
|
|
5
|
+
//#region src/internal/utilities/collection-utils.ts
|
|
6
|
+
/** Add a value to a Set stored in a Map, creating the Set if it doesn't exist. */
|
|
7
|
+
function addToSetMap(map, key, value) {
|
|
8
|
+
let set = map.get(key);
|
|
9
|
+
if (!set) {
|
|
10
|
+
set = /* @__PURE__ */ new Set();
|
|
11
|
+
map.set(key, set);
|
|
12
|
+
}
|
|
13
|
+
set.add(value);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/internal/prepass/scan-cross-file-selectors.ts
|
|
17
|
+
/**
|
|
18
|
+
* Pre-filter: matches any bare `${Identifier}` template expression.
|
|
19
|
+
* Used to skip files that only contain arrow functions or member expressions
|
|
20
|
+
* in template literals (e.g. `${props => ...}`, `${theme.color}`).
|
|
21
|
+
*/
|
|
22
|
+
const BARE_TEMPLATE_IDENTIFIER_RE = /\$\{\s*[a-zA-Z_$][\w$]*\s*\}/;
|
|
23
|
+
/**
|
|
24
|
+
* Categorize cross-file selector usages into marker sidecar and global selector bridge maps.
|
|
25
|
+
*
|
|
26
|
+
* Bridge usages (from already-converted files) are skipped — the consumer handles marker
|
|
27
|
+
* generation via the forward selector handler, so no sidecar/bridge is needed on the target.
|
|
28
|
+
*/
|
|
29
|
+
function categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, componentsNeedingGlobalSelectorBridge) {
|
|
30
|
+
for (const usage of usages) {
|
|
31
|
+
if (usage.bridgeComponentName) continue;
|
|
32
|
+
if (usage.consumerIsTransformed) addToSetMap(componentsNeedingMarkerSidecar, usage.resolvedPath, usage.importedName);
|
|
33
|
+
addToSetMap(componentsNeedingGlobalSelectorBridge, usage.resolvedPath, usage.importedName);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Regex matching bridge GlobalSelector export patterns (global for matchAll).
|
|
38
|
+
* Matches both:
|
|
39
|
+
* - Old format: `export const XGlobalSelector = ".sc2sx-..."`
|
|
40
|
+
* - New format: `` export const XGlobalSelector = `.${xBridgeClass}` ``
|
|
41
|
+
*/
|
|
42
|
+
const BRIDGE_EXPORT_RE = /export\s+const\s+(\w+GlobalSelector)\s*=\s*(?:["']\.sc2sx-|`\.\$\{)/g;
|
|
43
|
+
/**
|
|
44
|
+
* Detect whether an imported name is a bridge GlobalSelector from an
|
|
45
|
+
* already-converted StyleX file.
|
|
46
|
+
*
|
|
47
|
+
* Detection criteria (hybrid fast + safe):
|
|
48
|
+
* 1. Variable name ends with "GlobalSelector" AND the stripped name starts uppercase
|
|
49
|
+
* 2. Target file contains "@stylexjs/stylex" (string check, no parse)
|
|
50
|
+
* 3. Target file has a matching `export const XGlobalSelector = ".sc2sx-"` pattern
|
|
51
|
+
*
|
|
52
|
+
* @returns The stripped component name (e.g., "CollapseArrowIcon" for
|
|
53
|
+
* "CollapseArrowIconGlobalSelector"), or null if not a bridge.
|
|
54
|
+
*/
|
|
55
|
+
function detectBridgeGlobalSelector(importedName, resolvedPath, readFile) {
|
|
56
|
+
if (!importedName.endsWith("GlobalSelector")) return null;
|
|
57
|
+
const stripped = importedName.slice(0, -14);
|
|
58
|
+
if (!stripped || !/^[A-Z]/.test(stripped)) return null;
|
|
59
|
+
const content = readFile(resolvedPath);
|
|
60
|
+
if (!content || !content.includes("@stylexjs/stylex")) return null;
|
|
61
|
+
let found = false;
|
|
62
|
+
for (const m of content.matchAll(BRIDGE_EXPORT_RE)) if (m[1] === importedName) {
|
|
63
|
+
found = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (!found) return null;
|
|
67
|
+
return stripped;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* If `importedName` is a bridge GlobalSelector, populate bridge fields on `usage`
|
|
71
|
+
* and find the corresponding component import from the same source.
|
|
72
|
+
*/
|
|
73
|
+
function applyBridgeFields(usage, importedName, localName, resolvedPath, importMap, readFile) {
|
|
74
|
+
const bridgeName = detectBridgeGlobalSelector(importedName, resolvedPath, readFile);
|
|
75
|
+
if (!bridgeName) return;
|
|
76
|
+
usage.bridgeComponentName = bridgeName;
|
|
77
|
+
const imp = importMap.get(localName);
|
|
78
|
+
if (!imp) return;
|
|
79
|
+
let defaultImportLocal;
|
|
80
|
+
for (const [otherLocal, otherImp] of importMap) {
|
|
81
|
+
if (otherImp.source !== imp.source || otherLocal === localName) continue;
|
|
82
|
+
if (otherImp.importedName === bridgeName) {
|
|
83
|
+
usage.bridgeComponentLocalName = otherLocal;
|
|
84
|
+
defaultImportLocal = void 0;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (otherImp.importedName === "default" && defaultImportLocal === void 0) defaultImportLocal = otherLocal;
|
|
88
|
+
}
|
|
89
|
+
if (defaultImportLocal !== void 0) usage.bridgeComponentLocalName = defaultImportLocal;
|
|
90
|
+
}
|
|
91
|
+
/** Global version for matchAll/replace operations */
|
|
92
|
+
const PLACEHOLDER_RE_G = new RegExp(PLACEHOLDER_RE.source, "g");
|
|
93
|
+
/**
|
|
94
|
+
* Walk the AST collecting ImportDeclaration and TaggedTemplateExpression nodes.
|
|
95
|
+
*
|
|
96
|
+
* Uses a targeted recursive walk — only descends into node types that can
|
|
97
|
+
* contain these targets (skips type annotations, comments, etc.).
|
|
98
|
+
*/
|
|
99
|
+
function walkForImportsAndTemplates(node, imports, templates) {
|
|
100
|
+
if (!node || typeof node !== "object") return;
|
|
101
|
+
const n = node;
|
|
102
|
+
if (n.type === "ImportDeclaration") {
|
|
103
|
+
imports.push(n);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (n.type === "TaggedTemplateExpression") templates.push(n);
|
|
107
|
+
for (const key of Object.keys(n)) {
|
|
108
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc") continue;
|
|
109
|
+
const val = n[key];
|
|
110
|
+
if (Array.isArray(val)) for (const child of val) walkForImportsAndTemplates(child, imports, templates);
|
|
111
|
+
else if (val && typeof val === "object" && val.type) walkForImportsAndTemplates(val, imports, templates);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Build a map of localName → import info from raw ImportDeclaration nodes. */
|
|
115
|
+
function buildImportMapFromNodes(importNodes) {
|
|
116
|
+
const map = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const node of importNodes) {
|
|
118
|
+
const sourceValue = node.source?.value;
|
|
119
|
+
if (typeof sourceValue !== "string") continue;
|
|
120
|
+
const specifiers = node.specifiers;
|
|
121
|
+
if (!specifiers) continue;
|
|
122
|
+
for (const spec of specifiers) {
|
|
123
|
+
const localName = getNodeName(spec.local);
|
|
124
|
+
if (!localName) continue;
|
|
125
|
+
if (spec.type === "ImportDefaultSpecifier") map.set(localName, {
|
|
126
|
+
source: sourceValue,
|
|
127
|
+
importedName: "default"
|
|
128
|
+
});
|
|
129
|
+
else if (spec.type === "ImportNamespaceSpecifier") map.set(localName, {
|
|
130
|
+
source: sourceValue,
|
|
131
|
+
importedName: "*"
|
|
132
|
+
});
|
|
133
|
+
else if (spec.type === "ImportSpecifier") {
|
|
134
|
+
const importedName = getNodeName(spec.imported) ?? localName;
|
|
135
|
+
map.set(localName, {
|
|
136
|
+
source: sourceValue,
|
|
137
|
+
importedName
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return map;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Local identifiers that refer to `styled` from `"styled-components"` (default and/or
|
|
146
|
+
* `import { styled }` / `import { styled as sc }`).
|
|
147
|
+
*/
|
|
148
|
+
function collectStyledLocalBindingNames(importNodes) {
|
|
149
|
+
const names = /* @__PURE__ */ new Set();
|
|
150
|
+
for (const node of importNodes) {
|
|
151
|
+
if (node.source?.value !== "styled-components") continue;
|
|
152
|
+
const specifiers = node.specifiers;
|
|
153
|
+
if (!specifiers) continue;
|
|
154
|
+
for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") {
|
|
155
|
+
const name = getNodeName(spec.local);
|
|
156
|
+
if (name) names.add(name);
|
|
157
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
158
|
+
if (getNodeName(spec.imported) === "styled") {
|
|
159
|
+
const localName = getNodeName(spec.local);
|
|
160
|
+
if (localName) names.add(localName);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return names;
|
|
165
|
+
}
|
|
166
|
+
/** Find the local name for the styled-components default import. */
|
|
167
|
+
function findStyledImportNameFromNodes(importNodes) {
|
|
168
|
+
let namedStyledLocal;
|
|
169
|
+
for (const node of importNodes) {
|
|
170
|
+
if (node.source?.value !== "styled-components") continue;
|
|
171
|
+
const specifiers = node.specifiers;
|
|
172
|
+
if (!specifiers) continue;
|
|
173
|
+
for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") {
|
|
174
|
+
const name = getNodeName(spec.local);
|
|
175
|
+
if (name) return name;
|
|
176
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
177
|
+
if (getNodeName(spec.imported) === "styled") {
|
|
178
|
+
const localName = getNodeName(spec.local);
|
|
179
|
+
if (localName) namedStyledLocal = localName;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return namedStyledLocal;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Find local names of `css` imported from styled-components.
|
|
187
|
+
* Handles aliased imports like `import { css as sc } from "styled-components"`.
|
|
188
|
+
*/
|
|
189
|
+
function findCssImportNamesFromNodes(importNodes) {
|
|
190
|
+
const names = /* @__PURE__ */ new Set();
|
|
191
|
+
for (const node of importNodes) {
|
|
192
|
+
if (node.source?.value !== "styled-components") continue;
|
|
193
|
+
const specifiers = node.specifiers;
|
|
194
|
+
if (!specifiers) continue;
|
|
195
|
+
for (const spec of specifiers) if (spec.type === "ImportSpecifier") {
|
|
196
|
+
if (getNodeName(spec.imported) === "css") {
|
|
197
|
+
const localName = getNodeName(spec.local);
|
|
198
|
+
if (localName) names.add(localName);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return names;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Find local names of imported components used as selectors inside
|
|
206
|
+
* styled-components template literals (both `styled` and `css` tagged templates).
|
|
207
|
+
*/
|
|
208
|
+
function findComponentSelectorLocalsFromNodes(templateNodes, styledImportName, cssImportNames) {
|
|
209
|
+
const selectorLocals = /* @__PURE__ */ new Set();
|
|
210
|
+
for (const node of templateNodes) {
|
|
211
|
+
if (!isStyledTag(node.tag, styledImportName) && !isCssTag(node.tag, cssImportNames)) continue;
|
|
212
|
+
const quasi = node.quasi;
|
|
213
|
+
if (!quasi) continue;
|
|
214
|
+
const quasis = quasi.quasis;
|
|
215
|
+
const expressions = quasi.expressions;
|
|
216
|
+
if (!quasis || !expressions) continue;
|
|
217
|
+
const rawParts = [];
|
|
218
|
+
for (let i = 0; i < quasis.length; i++) {
|
|
219
|
+
const value = quasis[i]?.value;
|
|
220
|
+
rawParts.push(value?.raw ?? "");
|
|
221
|
+
if (i < expressions.length) rawParts.push(`__SC_EXPR_${i}__`);
|
|
222
|
+
}
|
|
223
|
+
const rawCss = rawParts.join("");
|
|
224
|
+
for (const match of rawCss.matchAll(PLACEHOLDER_RE_G)) {
|
|
225
|
+
const exprIndex = Number(match[1]);
|
|
226
|
+
const pos = match.index;
|
|
227
|
+
if (isTemplatePlaceholderInSelectorContext(rawCss, pos, match[0].length)) {
|
|
228
|
+
const expr = expressions[exprIndex];
|
|
229
|
+
if (expr?.type === "Identifier" && typeof expr.name === "string") selectorLocals.add(expr.name);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return selectorLocals;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check whether a styled-components tag expression is a styled call.
|
|
237
|
+
* Matches: styled.div, styled(X), styled.div.attrs(...), styled(X).withConfig(...), etc.
|
|
238
|
+
*/
|
|
239
|
+
function isStyledTag(tag, styledName) {
|
|
240
|
+
if (!tag || typeof tag !== "object") return false;
|
|
241
|
+
if (tag.type === "MemberExpression") {
|
|
242
|
+
const obj = tag.object;
|
|
243
|
+
if (obj?.type === "Identifier" && obj.name === styledName) return true;
|
|
244
|
+
}
|
|
245
|
+
if (tag.type === "CallExpression") {
|
|
246
|
+
const callee = tag.callee;
|
|
247
|
+
if (callee?.type === "Identifier" && callee.name === styledName) return true;
|
|
248
|
+
if (callee?.type === "MemberExpression" && callee.object) return isStyledTag(callee.object, styledName);
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
/** Check if a template tag is the `css` helper from styled-components. */
|
|
253
|
+
function isCssTag(tag, cssImportNames) {
|
|
254
|
+
if (!tag || !cssImportNames || cssImportNames.size === 0) return false;
|
|
255
|
+
return tag.type === "Identifier" && typeof tag.name === "string" && cssImportNames.has(tag.name);
|
|
256
|
+
}
|
|
257
|
+
/** Safely extract the name string from an AST identifier-like node. */
|
|
258
|
+
function getNodeName(node) {
|
|
259
|
+
if (!node || typeof node !== "object") return;
|
|
260
|
+
if (node.type === "Identifier" && typeof node.name === "string") return node.name;
|
|
261
|
+
}
|
|
262
|
+
/** Deduplicate and resolve two file lists into a single array of absolute paths. */
|
|
263
|
+
function deduplicateAndResolve(filesToTransform, consumerPaths) {
|
|
264
|
+
const seen = /* @__PURE__ */ new Set();
|
|
265
|
+
const result = [];
|
|
266
|
+
for (const f of filesToTransform) {
|
|
267
|
+
const abs = resolve(f);
|
|
268
|
+
if (!seen.has(abs)) {
|
|
269
|
+
seen.add(abs);
|
|
270
|
+
result.push(abs);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const f of consumerPaths) {
|
|
274
|
+
const abs = resolve(f);
|
|
275
|
+
if (!seen.has(abs)) {
|
|
276
|
+
seen.add(abs);
|
|
277
|
+
result.push(abs);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/internal/prepass/stylex-component-exports.ts
|
|
284
|
+
/**
|
|
285
|
+
* Prepass helpers for identifying exported components that already apply StyleX.
|
|
286
|
+
* Core concepts: StyleX import bindings, export names, and local binding traces.
|
|
287
|
+
*/
|
|
288
|
+
function collectStylexExportNames(source) {
|
|
289
|
+
const parsed = parseProgram(source);
|
|
290
|
+
if (!parsed) return /* @__PURE__ */ new Set();
|
|
291
|
+
const stylexUsage = collectStylexUsage(parsed);
|
|
292
|
+
if (!stylexUsage.hasStylexSurface) return /* @__PURE__ */ new Set();
|
|
293
|
+
const names = /* @__PURE__ */ new Set();
|
|
294
|
+
for (const stmt of programBody(parsed)) {
|
|
295
|
+
collectStylexNamedExports(parsed, stmt, stylexUsage, names);
|
|
296
|
+
collectStylexDefaultExport(parsed, stmt, stylexUsage, names);
|
|
297
|
+
}
|
|
298
|
+
return names;
|
|
299
|
+
}
|
|
300
|
+
function parseProgram(source) {
|
|
301
|
+
for (const parserName of ["tsx", "babel"]) try {
|
|
302
|
+
const ast = createPrepassParser(parserName).parse(source);
|
|
303
|
+
return ast.program ?? ast;
|
|
304
|
+
} catch {}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
function collectStylexNamedExports(program, stmt, stylexUsage, names) {
|
|
308
|
+
if (stmt.type !== "ExportNamedDeclaration") return;
|
|
309
|
+
const declaration = stmt.declaration;
|
|
310
|
+
for (const localName of declarationLocalNames(declaration)) if (localBindingUsesStylex(program, localName, stylexUsage)) names.add(localName);
|
|
311
|
+
for (const specifier of astArray(stmt.specifiers)) {
|
|
312
|
+
if (specifier.type !== "ExportSpecifier") continue;
|
|
313
|
+
const exportedName = nodeName(specifier.exported);
|
|
314
|
+
const localName = nodeName(specifier.local);
|
|
315
|
+
if (exportedName && localName && localBindingUsesStylex(program, localName, stylexUsage)) names.add(exportedName);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function declarationLocalNames(declaration) {
|
|
319
|
+
if (!declaration) return [];
|
|
320
|
+
if (declaration.type === "VariableDeclaration") return astArray(declaration.declarations).flatMap((declarator) => {
|
|
321
|
+
const localName = nodeName(declarator.id);
|
|
322
|
+
return localName ? [localName] : [];
|
|
323
|
+
});
|
|
324
|
+
const localName = nodeName(declaration.id);
|
|
325
|
+
return localName ? [localName] : [];
|
|
326
|
+
}
|
|
327
|
+
function collectStylexDefaultExport(program, stmt, stylexUsage, names) {
|
|
328
|
+
if (stmt.type !== "ExportDefaultDeclaration") return;
|
|
329
|
+
const declaration = stmt.declaration;
|
|
330
|
+
const localName = nodeName(declaration?.id) ?? nodeName(declaration);
|
|
331
|
+
if (localName ? localBindingUsesStylex(program, localName, stylexUsage) || nodeUsesStylex(declaration, stylexUsage) : nodeUsesStylex(declaration, stylexUsage) || (declaration ? referencedLocalNames(program, declaration).some((referencedName) => localBindingUsesStylex(program, referencedName, stylexUsage)) : false)) names.add("default");
|
|
332
|
+
}
|
|
333
|
+
function collectStylexUsage(program) {
|
|
334
|
+
const namespaceNames = /* @__PURE__ */ new Set();
|
|
335
|
+
const createNames = /* @__PURE__ */ new Set();
|
|
336
|
+
const propsNames = /* @__PURE__ */ new Set();
|
|
337
|
+
const styleObjectNames = /* @__PURE__ */ new Set();
|
|
338
|
+
for (const stmt of programBody(program)) {
|
|
339
|
+
if (stmt.type !== "ImportDeclaration") continue;
|
|
340
|
+
const importSource = stmt.source?.value;
|
|
341
|
+
if (typeof importSource !== "string") continue;
|
|
342
|
+
for (const specifier of astArray(stmt.specifiers)) {
|
|
343
|
+
const localName = nodeName(specifier.local);
|
|
344
|
+
if (!localName) continue;
|
|
345
|
+
if (importSource.includes(".stylex")) {
|
|
346
|
+
styleObjectNames.add(localName);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (importSource !== "@stylexjs/stylex") continue;
|
|
350
|
+
if (specifier.type === "ImportNamespaceSpecifier") {
|
|
351
|
+
namespaceNames.add(localName);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (specifier.type === "ImportSpecifier") {
|
|
355
|
+
const importedName = nodeName(specifier.imported);
|
|
356
|
+
if (importedName === "create") createNames.add(localName);
|
|
357
|
+
else if (importedName === "props") propsNames.add(localName);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const binding of localBindings(program)) if (nodeIsStylexCreateCall(binding.node, namespaceNames, createNames)) styleObjectNames.add(binding.name);
|
|
362
|
+
return {
|
|
363
|
+
namespaceNames,
|
|
364
|
+
propsNames,
|
|
365
|
+
styleObjectNames,
|
|
366
|
+
hasStylexSurface: namespaceNames.size > 0 || propsNames.size > 0 || createNames.size > 0 || styleObjectNames.size > 0
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function localBindingUsesStylex(program, localName, stylexUsage, checkedNames = /* @__PURE__ */ new Set()) {
|
|
370
|
+
if (checkedNames.has(localName)) return false;
|
|
371
|
+
checkedNames.add(localName);
|
|
372
|
+
const node = findLocalBindingNode(program, localName);
|
|
373
|
+
if (!node) return false;
|
|
374
|
+
if (nodeUsesStylex(node, stylexUsage)) return true;
|
|
375
|
+
return referencedLocalNames(program, node).some((referencedName) => localBindingUsesStylex(program, referencedName, stylexUsage, checkedNames));
|
|
376
|
+
}
|
|
377
|
+
function nodeUsesStylex(node, stylexUsage) {
|
|
378
|
+
if (!node) return false;
|
|
379
|
+
let found = false;
|
|
380
|
+
walkValueAst(node, (candidate) => {
|
|
381
|
+
found ||= candidateUsesStylex(candidate, stylexUsage);
|
|
382
|
+
});
|
|
383
|
+
return found;
|
|
384
|
+
}
|
|
385
|
+
function candidateUsesStylex(candidate, stylexUsage) {
|
|
386
|
+
return nodeIsStylexPropsCall(candidate, stylexUsage.namespaceNames, stylexUsage.propsNames) || nodeIsMergedSxCall(candidate, stylexUsage.styleObjectNames) || isStylexSxAttribute(candidate, stylexUsage.styleObjectNames);
|
|
387
|
+
}
|
|
388
|
+
function findLocalBindingNode(program, localName) {
|
|
389
|
+
for (const binding of localBindings(program)) if (binding.name === localName) return binding.node;
|
|
390
|
+
}
|
|
391
|
+
function localBindings(program) {
|
|
392
|
+
const bindings = [];
|
|
393
|
+
for (const stmt of programBody(program)) {
|
|
394
|
+
const declaration = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
|
|
395
|
+
if (!declaration) continue;
|
|
396
|
+
const directBinding = localBindingFromDeclaration(declaration);
|
|
397
|
+
if (directBinding) {
|
|
398
|
+
bindings.push(directBinding);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (declaration.type === "VariableDeclaration") for (const declarator of astArray(declaration.declarations)) {
|
|
402
|
+
const name = nodeName(declarator.id);
|
|
403
|
+
const init = declarator.init;
|
|
404
|
+
if (name && init) bindings.push({
|
|
405
|
+
name,
|
|
406
|
+
node: init
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return bindings;
|
|
411
|
+
}
|
|
412
|
+
function localBindingFromDeclaration(declaration) {
|
|
413
|
+
if (declaration.type !== "FunctionDeclaration" && declaration.type !== "ClassDeclaration") return null;
|
|
414
|
+
const name = nodeName(declaration.id);
|
|
415
|
+
const body = declaration.body;
|
|
416
|
+
return name && body ? {
|
|
417
|
+
name,
|
|
418
|
+
node: body
|
|
419
|
+
} : null;
|
|
420
|
+
}
|
|
421
|
+
function referencedLocalNames(program, node) {
|
|
422
|
+
const localNameSet = new Set(localBindings(program).map((binding) => binding.name));
|
|
423
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
424
|
+
walkValueAst(node, (candidate) => {
|
|
425
|
+
if (candidate.type !== "Identifier" && candidate.type !== "JSXIdentifier") return;
|
|
426
|
+
const name = candidate.name;
|
|
427
|
+
if (typeof name === "string" && localNameSet.has(name)) referenced.add(name);
|
|
428
|
+
});
|
|
429
|
+
return [...referenced];
|
|
430
|
+
}
|
|
431
|
+
function nodeIsStylexCreateCall(node, namespaceNames, createNames) {
|
|
432
|
+
if (node.type !== "CallExpression") return false;
|
|
433
|
+
const callee = node.callee;
|
|
434
|
+
if (callee?.type === "Identifier") return typeof callee.name === "string" && createNames.has(callee.name);
|
|
435
|
+
return isStylexMemberCall(callee, namespaceNames, "create");
|
|
436
|
+
}
|
|
437
|
+
function nodeIsStylexPropsCall(node, namespaceNames, propsNames) {
|
|
438
|
+
if (node.type !== "CallExpression") return false;
|
|
439
|
+
const callee = node.callee;
|
|
440
|
+
if (callee?.type === "Identifier") return typeof callee.name === "string" && propsNames.has(callee.name);
|
|
441
|
+
return isStylexMemberCall(callee, namespaceNames, "props");
|
|
442
|
+
}
|
|
443
|
+
function nodeIsMergedSxCall(node, styleObjectNames) {
|
|
444
|
+
if (styleObjectNames.size === 0 || node.type !== "CallExpression") return false;
|
|
445
|
+
const callee = node.callee;
|
|
446
|
+
if (callee?.type !== "Identifier" || callee.name !== "mergedSx") return false;
|
|
447
|
+
return astArray(node.arguments).some((arg) => nodeReferencesLocalNames(arg, styleObjectNames));
|
|
448
|
+
}
|
|
449
|
+
function isStylexMemberCall(callee, namespaceNames, propertyName) {
|
|
450
|
+
if (callee?.type !== "MemberExpression") return false;
|
|
451
|
+
const objectName = nodeName(callee.object);
|
|
452
|
+
const property = callee.property;
|
|
453
|
+
const actualPropertyName = callee.computed === true ? void 0 : nodeName(property);
|
|
454
|
+
return !!objectName && namespaceNames.has(objectName) && actualPropertyName === propertyName;
|
|
455
|
+
}
|
|
456
|
+
function isStylexSxAttribute(node, styleObjectNames) {
|
|
457
|
+
if (styleObjectNames.size === 0 || node.type !== "JSXAttribute") return false;
|
|
458
|
+
if (nodeName(node.name) !== "sx") return false;
|
|
459
|
+
const value = node.value;
|
|
460
|
+
if (!value) return false;
|
|
461
|
+
return nodeReferencesLocalNames(value.type === "JSXExpressionContainer" ? value.expression : value, styleObjectNames);
|
|
462
|
+
}
|
|
463
|
+
function nodeReferencesLocalNames(node, localNames) {
|
|
464
|
+
if (!node) return false;
|
|
465
|
+
let found = false;
|
|
466
|
+
walkValueAst(node, (candidate) => {
|
|
467
|
+
if (!found && isNamedReference(candidate, localNames)) found = true;
|
|
468
|
+
});
|
|
469
|
+
return found;
|
|
470
|
+
}
|
|
471
|
+
function walkValueAst(root, visitor) {
|
|
472
|
+
const visit = (node) => {
|
|
473
|
+
if (!node || typeof node !== "object") return;
|
|
474
|
+
if (Array.isArray(node)) {
|
|
475
|
+
for (const child of node) visit(child);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const astNode = node;
|
|
479
|
+
visitor(astNode);
|
|
480
|
+
for (const key of Object.keys(astNode)) {
|
|
481
|
+
if (shouldSkipChild(astNode, key)) continue;
|
|
482
|
+
const child = astNode[key];
|
|
483
|
+
if (child && typeof child === "object") visit(child);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
visit(root);
|
|
487
|
+
}
|
|
488
|
+
function shouldSkipChild(node, key) {
|
|
489
|
+
if ([
|
|
490
|
+
"loc",
|
|
491
|
+
"comments",
|
|
492
|
+
"leadingComments",
|
|
493
|
+
"trailingComments"
|
|
494
|
+
].includes(key)) return true;
|
|
495
|
+
if ([
|
|
496
|
+
"typeAnnotation",
|
|
497
|
+
"typeParameters",
|
|
498
|
+
"returnType"
|
|
499
|
+
].includes(key)) return true;
|
|
500
|
+
if (key === "key" && (node.type === "ObjectProperty" || node.type === "Property") && node.computed !== true) return true;
|
|
501
|
+
if (key === "property" && node.type === "MemberExpression" && node.computed !== true) return true;
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
function isNamedReference(node, localNames) {
|
|
505
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier") return typeof node.name === "string" && localNames.has(node.name);
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
function programBody(program) {
|
|
509
|
+
return astArray(program.body);
|
|
510
|
+
}
|
|
511
|
+
function astArray(value) {
|
|
512
|
+
return Array.isArray(value) ? value.filter(isAstNode) : [];
|
|
513
|
+
}
|
|
514
|
+
function isAstNode(value) {
|
|
515
|
+
return Boolean(value && typeof value === "object");
|
|
516
|
+
}
|
|
517
|
+
function nodeName(node) {
|
|
518
|
+
if (!node) return;
|
|
519
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier" || node.type === "StringLiteral") return typeof node.name === "string" ? node.name : typeof node.value === "string" ? node.value : void 0;
|
|
520
|
+
}
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/internal/utilities/jsx-static-literal.ts
|
|
523
|
+
function readStaticJsxLiteral(attr) {
|
|
524
|
+
if (!isObjectRecord(attr) || attr.type !== "JSXAttribute") return;
|
|
525
|
+
if (!("value" in attr) || attr.value == null) return true;
|
|
526
|
+
const directLiteral = readLiteralNodeValue(attr.value);
|
|
527
|
+
if (directLiteral !== void 0) return directLiteral;
|
|
528
|
+
if (!isObjectRecord(attr.value) || attr.value.type !== "JSXExpressionContainer") return;
|
|
529
|
+
return readLiteralNodeValue(attr.value.expression);
|
|
530
|
+
}
|
|
531
|
+
function readLiteralNodeValue(node) {
|
|
532
|
+
if (!isObjectRecord(node)) return;
|
|
533
|
+
if (node.type === "StringLiteral" || node.type === "NumericLiteral" || node.type === "BooleanLiteral") return isStaticLiteral(node.value) ? node.value : void 0;
|
|
534
|
+
if (node.type === "Literal") return isStaticLiteral(node.value) ? node.value : void 0;
|
|
535
|
+
if (node.type === "UnaryExpression" && node.operator === "-") {
|
|
536
|
+
const value = readLiteralNodeValue(node.argument);
|
|
537
|
+
return typeof value === "number" ? -value : void 0;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function isObjectRecord(value) {
|
|
541
|
+
return typeof value === "object" && value !== null;
|
|
542
|
+
}
|
|
543
|
+
function isStaticLiteral(value) {
|
|
544
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/internal/utilities/prop-usage.ts
|
|
548
|
+
const KNOWN_NON_ELEMENT_PROPS = new Set([
|
|
549
|
+
"className",
|
|
550
|
+
"style",
|
|
551
|
+
"as",
|
|
552
|
+
"ref",
|
|
553
|
+
"forwardedAs",
|
|
554
|
+
"key",
|
|
555
|
+
"children"
|
|
556
|
+
]);
|
|
557
|
+
function createComponentPropUsageInfo(name) {
|
|
558
|
+
return {
|
|
559
|
+
componentName: name,
|
|
560
|
+
usageCount: 0,
|
|
561
|
+
hasUnknownUsage: false,
|
|
562
|
+
props: {}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function mergeComponentPropUsage(info, usage) {
|
|
566
|
+
info.usageCount += 1;
|
|
567
|
+
if (usage.hasSpread) info.hasUnknownUsage = true;
|
|
568
|
+
const presentProps = new Set(Object.keys(usage.props));
|
|
569
|
+
for (const [propName, propInfo] of Object.entries(info.props)) if (!presentProps.has(propName)) propInfo.omittedCount += 1;
|
|
570
|
+
for (const [propName, value] of Object.entries(usage.props)) {
|
|
571
|
+
const propInfo = info.props[propName] ?? (info.props[propName] = {
|
|
572
|
+
values: [],
|
|
573
|
+
hasUnknown: false,
|
|
574
|
+
usageCount: 0,
|
|
575
|
+
omittedCount: info.usageCount - 1
|
|
576
|
+
});
|
|
577
|
+
propInfo.usageCount += 1;
|
|
578
|
+
if (value.kind === "unknown") {
|
|
579
|
+
propInfo.hasUnknown = true;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (!propInfo.values.some((existing) => existing === value.value)) propInfo.values.push(value.value);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Formats a `prop === value` JS condition for an observed static variant bucket,
|
|
587
|
+
* quoting strings and emitting numbers bare. Shared by every observed-variant emitter.
|
|
588
|
+
*/
|
|
589
|
+
function formatObservedVariantCondition(propName, value) {
|
|
590
|
+
return `${propName} === ${typeof value === "number" ? String(value) : JSON.stringify(value)}`;
|
|
591
|
+
}
|
|
592
|
+
function getExhaustiveObservedStaticValues(info, propName) {
|
|
593
|
+
const propUsage = info?.props[propName];
|
|
594
|
+
if (!info || info.hasUnknownUsage || !propUsage || propUsage.hasUnknown) return null;
|
|
595
|
+
if (propUsage.values.length < 1) return null;
|
|
596
|
+
const values = propUsage.values.filter((value) => typeof value === "string" || typeof value === "number");
|
|
597
|
+
return values.length === propUsage.values.length ? values : null;
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
export { walkForImportsAndTemplates as _, mergeComponentPropUsage as a, BARE_TEMPLATE_IDENTIFIER_RE as c, categorizeSelectorUsages as d, collectStyledLocalBindingNames as f, findStyledImportNameFromNodes as g, findCssImportNamesFromNodes as h, getExhaustiveObservedStaticValues as i, applyBridgeFields as l, findComponentSelectorLocalsFromNodes as m, createComponentPropUsageInfo as n, readStaticJsxLiteral as o, deduplicateAndResolve as p, formatObservedVariantCondition as r, collectStylexExportNames as s, KNOWN_NON_ELEMENT_PROPS as t, buildImportMapFromNodes as u, addToSetMap as v };
|