styled-components-to-stylex-codemod 0.0.53 → 0.0.55

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.
@@ -0,0 +1,1280 @@
1
+ import { r as toRealPath } from "./path-utils-BC4U8X_q.mjs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ //#region src/internal/public-api-validation.ts
4
+ function describeValue(value) {
5
+ if (value === null) return "null";
6
+ if (value === void 0) return "undefined";
7
+ if (Array.isArray(value)) return `Array(${value.length})`;
8
+ if (typeof value === "string") return `"${value}"`;
9
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
10
+ if (typeof value === "bigint") return value.toString();
11
+ if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
12
+ if (typeof value === "function") return "[Function]";
13
+ if (typeof value === "object") {
14
+ const ctor = value?.constructor?.name ?? "Object";
15
+ let keys = [];
16
+ try {
17
+ keys = Object.keys(value);
18
+ } catch {}
19
+ const preview = keys.slice(0, 5).join(", ");
20
+ const suffix = keys.length > 5 ? ", ..." : "";
21
+ return keys.length ? `${ctor} { ${preview}${suffix} }` : ctor;
22
+ }
23
+ return "[Unknown]";
24
+ }
25
+ /** Validates that the candidate is a fully-resolved `Adapter` (externalInterface must be a function). */
26
+ function assertValidAdapter(candidate, where) {
27
+ assertAdapterShape(candidate, where, false);
28
+ }
29
+ /** Validates that the candidate is a valid `AdapterInput` (externalInterface may be `"auto"` or a function). */
30
+ function assertValidAdapterInput(candidate, where) {
31
+ assertAdapterShape(candidate, where, true);
32
+ }
33
+ function assertAdapterShape(candidate, where, allowAutoExtIf) {
34
+ const obj = candidate;
35
+ const resolveValue = obj?.resolveValue;
36
+ const resolveCall = obj?.resolveCall;
37
+ const resolveSelector = obj?.resolveSelector;
38
+ const resolveBaseComponent = obj?.resolveBaseComponent;
39
+ const externalInterface = obj?.externalInterface;
40
+ const usePhysicalProperties = obj?.usePhysicalProperties;
41
+ if (!candidate || typeof candidate !== "object") throw new Error([
42
+ `${where}: expected an adapter object.`,
43
+ `Received: ${describeValue(candidate)}`,
44
+ "",
45
+ "Adapter requirements:",
46
+ " - adapter.resolveValue(context) is required",
47
+ " - adapter.resolveCall(context) is required",
48
+ " - adapter.resolveSelector(context) is required",
49
+ " - adapter.externalInterface(context) is required",
50
+ "",
51
+ "resolveValue(context) is called with one of these shapes:",
52
+ " - { kind: \"theme\", path }",
53
+ " - { kind: \"cssVariable\", name, fallback?, definedValue? }",
54
+ " - { kind: \"importedValue\", importedName, source, path? }",
55
+ "",
56
+ "resolveCall(context) is called with:",
57
+ " - { callSiteFilePath, calleeImportedName, calleeSource, args }",
58
+ "",
59
+ "resolveSelector(context) is called with:",
60
+ " - { kind: \"selectorInterpolation\", importedName, source, path? }",
61
+ " - { kind: \"mediaQueryInterpolation\", importedName, source, path?, mediaQuery }",
62
+ "",
63
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
64
+ ].join("\n"));
65
+ if (typeof resolveValue !== "function") throw new Error([
66
+ `${where}: adapter.resolveValue must be a function.`,
67
+ `Received: resolveValue=${describeValue(resolveValue)}`,
68
+ "",
69
+ "Adapter shape:",
70
+ " {",
71
+ " resolveValue(context) {",
72
+ " // theme/cssVariable -> { expr, imports, dropDefinition? } | null",
73
+ " }",
74
+ " resolveCall(context) { return { expr, imports } | null }",
75
+ " }",
76
+ "",
77
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
78
+ ].join("\n"));
79
+ if (typeof resolveCall !== "function") throw new Error([
80
+ `${where}: adapter.resolveCall must be a function.`,
81
+ `Received: resolveCall=${describeValue(resolveCall)}`,
82
+ "",
83
+ "Adapter shape:",
84
+ " {",
85
+ " resolveCall(context) { return { expr: string, imports: ImportSpec[] } | null }",
86
+ " }",
87
+ "",
88
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
89
+ ].join("\n"));
90
+ if (typeof resolveSelector !== "function") throw new Error([
91
+ `${where}: adapter.resolveSelector must be a function.`,
92
+ `Received: resolveSelector=${describeValue(resolveSelector)}`,
93
+ "",
94
+ "Adapter shape:",
95
+ " {",
96
+ " resolveSelector(context) { return { kind: \"media\" | \"pseudoAlias\" | \"pseudoExpand\", ... } | undefined }",
97
+ " }",
98
+ "",
99
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
100
+ ].join("\n"));
101
+ if (resolveBaseComponent !== void 0 && typeof resolveBaseComponent !== "function") throw new Error([
102
+ `${where}: adapter.resolveBaseComponent must be a function when provided.`,
103
+ `Received: resolveBaseComponent=${describeValue(resolveBaseComponent)}`,
104
+ "",
105
+ "Adapter shape:",
106
+ " {",
107
+ " resolveBaseComponent(context) {",
108
+ " return { tagName, consumedProps, sx?, mixins? } | undefined",
109
+ " }",
110
+ " }",
111
+ "",
112
+ `Docs/examples: ${ADAPTER_DOCS_URL}`
113
+ ].join("\n"));
114
+ if (!(typeof externalInterface === "function" || allowAutoExtIf && externalInterface === "auto")) throw new Error([`${where}: ${allowAutoExtIf ? "adapter.externalInterface must be a function or \"auto\"." : "adapter.externalInterface must be a function."}`, `Received: externalInterface=${describeValue(externalInterface)}`].join("\n"));
115
+ if (typeof usePhysicalProperties !== "boolean") throw new Error([
116
+ `${where}: adapter.usePhysicalProperties must be explicitly set to true or false.`,
117
+ `Received: usePhysicalProperties=${describeValue(usePhysicalProperties)}`,
118
+ "",
119
+ "Set `usePhysicalProperties: true` to emit physical padding/margin longhands,",
120
+ "or `usePhysicalProperties: false` to emit logical block/inline properties."
121
+ ].join("\n"));
122
+ const styleMerger = obj?.styleMerger;
123
+ if (styleMerger !== null && styleMerger !== void 0) {
124
+ if (typeof styleMerger !== "object") throw new Error([
125
+ `${where}: adapter.styleMerger must be null or an object.`,
126
+ `Received: styleMerger=${describeValue(styleMerger)}`,
127
+ "",
128
+ "Expected shape:",
129
+ " {",
130
+ " functionName: \"stylexProps\",",
131
+ " importSource: { kind: \"specifier\", value: \"@company/ui-utils\" }",
132
+ " }"
133
+ ].join("\n"));
134
+ const { functionName, importSource } = styleMerger;
135
+ assertFunctionNameAndImportSource({
136
+ where,
137
+ configPath: "adapter.styleMerger",
138
+ functionName,
139
+ importSource,
140
+ specifierExample: "@company/ui-utils",
141
+ absolutePathExample: "/path/to/module.ts"
142
+ });
143
+ }
144
+ const markerFile = obj?.markerFile;
145
+ if (markerFile !== void 0 && markerFile !== null && typeof markerFile !== "function") throw new Error([
146
+ `${where}: adapter.markerFile must be a function when provided.`,
147
+ `Received: markerFile=${describeValue(markerFile)}`,
148
+ "",
149
+ "Expected signature:",
150
+ " markerFile(ctx: { filePath: string }) => { kind: \"specifier\" | \"absolutePath\", value: string }"
151
+ ].join("\n"));
152
+ const wrappedComponentInterface = obj?.wrappedComponentInterface;
153
+ if (wrappedComponentInterface !== void 0 && wrappedComponentInterface !== null && typeof wrappedComponentInterface !== "function") throw new Error([
154
+ `${where}: adapter.wrappedComponentInterface must be a function when provided.`,
155
+ `Received: wrappedComponentInterface=${describeValue(wrappedComponentInterface)}`,
156
+ "",
157
+ "Expected signature:",
158
+ " wrappedComponentInterface(ctx: { localName: string; importSource: string; importedName: string; filePath: string })",
159
+ " => { acceptsSx: boolean; sxAllowedProperties?: string[]; sxExcludedProperties?: string[] } | undefined"
160
+ ].join("\n"));
161
+ const themeHook = obj?.themeHook;
162
+ if (themeHook !== null && themeHook !== void 0) {
163
+ if (typeof themeHook !== "object") throw new Error([
164
+ `${where}: adapter.themeHook must be an object when provided.`,
165
+ `Received: themeHook=${describeValue(themeHook)}`,
166
+ "",
167
+ "Expected shape:",
168
+ " {",
169
+ " functionName: \"useTheme\",",
170
+ " importSource: { kind: \"specifier\", value: \"@company/theme-hooks\" }",
171
+ " }"
172
+ ].join("\n"));
173
+ const { functionName, importSource } = themeHook;
174
+ assertFunctionNameAndImportSource({
175
+ where,
176
+ configPath: "adapter.themeHook",
177
+ functionName,
178
+ importSource,
179
+ specifierExample: "@company/theme-hooks",
180
+ absolutePathExample: "/path/to/theme-hooks.ts"
181
+ });
182
+ }
183
+ }
184
+ function assertFunctionNameAndImportSource(args) {
185
+ const { where, configPath, functionName, importSource, specifierExample, absolutePathExample } = args;
186
+ if (typeof functionName !== "string" || !functionName.trim()) throw new Error([`${where}: ${configPath}.functionName must be a non-empty string.`, `Received: functionName=${describeValue(functionName)}`].join("\n"));
187
+ if (!importSource || typeof importSource !== "object") throw new Error([
188
+ `${where}: ${configPath}.importSource must be an object.`,
189
+ `Received: importSource=${describeValue(importSource)}`,
190
+ "",
191
+ "Expected shape:",
192
+ ` { kind: "specifier", value: "${specifierExample}" }`,
193
+ " or",
194
+ ` { kind: "absolutePath", value: "${absolutePathExample}" }`
195
+ ].join("\n"));
196
+ const { kind, value } = importSource;
197
+ if (kind !== "specifier" && kind !== "absolutePath") throw new Error([`${where}: ${configPath}.importSource.kind must be "specifier" or "absolutePath".`, `Received: kind=${describeValue(kind)}`].join("\n"));
198
+ if (typeof value !== "string" || !value.trim()) throw new Error([`${where}: ${configPath}.importSource.value must be a non-empty string.`, `Received: value=${describeValue(value)}`].join("\n"));
199
+ }
200
+ const ADAPTER_DOCS_URL = `https://github.com/skovhus/styled-components-to-stylex-codemod#adapter`;
201
+ //#endregion
202
+ //#region src/adapter.ts
203
+ /**
204
+ * Adapter entry point for customizing the codemod.
205
+ * Core concepts: value resolution hooks and adapter validation.
206
+ */
207
+ /**
208
+ * Type guard: checks whether a resolve result is a directional expansion.
209
+ */
210
+ function isDirectionalResult(r) {
211
+ return "directional" in r;
212
+ }
213
+ const DEFAULT_THEME_HOOK = {
214
+ functionName: "useTheme",
215
+ importSource: {
216
+ kind: "specifier",
217
+ value: "styled-components"
218
+ }
219
+ };
220
+ /**
221
+ * Helper for nicer user authoring + type inference.
222
+ *
223
+ * `defineAdapter(...)` also performs runtime validation (helpful for JS consumers)
224
+ * and will throw a descriptive error message if the adapter shape is invalid.
225
+ *
226
+ * Usage:
227
+ * export default defineAdapter({
228
+ * resolveValue(ctx) {
229
+ * if (ctx.kind === "theme") {
230
+ * // For shorthand properties with multi-value tokens, return directional entries
231
+ * if (ctx.cssProperty === "padding" && ctx.path === "input.padding") {
232
+ * return {
233
+ * directional: [
234
+ * { prop: "paddingBlock", expr: "$input.paddingBlock", imports: [{ from: { kind: "specifier", value: "./tokens" }, names: [{ imported: "$input" }] }] },
235
+ * { prop: "paddingInline", expr: "$input.paddingInline", imports: [{ from: { kind: "specifier", value: "./tokens" }, names: [{ imported: "$input" }] }] },
236
+ * ],
237
+ * };
238
+ * }
239
+ * return {
240
+ * expr: `tokens.${ctx.path}`,
241
+ * imports: [
242
+ * { from: { kind: "specifier", value: "./tokens" }, names: [{ imported: "tokens" }] },
243
+ * ],
244
+ * };
245
+ * }
246
+ * // Return undefined to bail/skip the file
247
+ * },
248
+ *
249
+ * resolveCall(ctx) {
250
+ * // Resolve helper calls inside template interpolations.
251
+ * // Use ctx.cssProperty to determine context:
252
+ * // - If ctx.cssProperty exists → return a CSS value expression
253
+ * // - If ctx.cssProperty is undefined → return a StyleX style object reference
254
+ * // Return { expr, imports } or undefined to bail/skip the file
255
+ * void ctx;
256
+ * },
257
+ *
258
+ * resolveSelector(ctx) {
259
+ * // Resolve imported values used in selector position.
260
+ * // Return one of:
261
+ * // - { kind: "media", expr, imports } for media queries (e.g., breakpoints.phone)
262
+ * // - { kind: "pseudoAlias", values, styleSelectorExpr?, imports? } for pseudo-class expansion
263
+ * // - undefined to bail/skip the file
264
+ * // For @media placeholders, check ctx.kind === "mediaQueryInterpolation" and
265
+ * // use ctx.mediaQuery.feature to choose the correct defineConsts media key.
266
+ * void ctx;
267
+ * },
268
+ *
269
+ * // Configure external interface for exported components
270
+ * externalInterface(ctx) {
271
+ * // Example: Enable styles, `as`, and `ref` for shared components folder
272
+ * if (ctx.filePath.includes("/shared/components/")) {
273
+ * return { styles: true, as: true, ref: true };
274
+ * }
275
+ * return { styles: false, as: false, ref: false };
276
+ * },
277
+ *
278
+ * // Optional: provide a custom merger, or use `null` for the default verbose merge output
279
+ * styleMerger: null,
280
+ *
281
+ * // Emit sx={} JSX attributes instead of {...stylex.props()} spreads (requires StyleX ≥0.18)
282
+ * useSxProp: false,
283
+ *
284
+ * // Choose how 2-value padding/margin shorthands are expanded.
285
+ * usePhysicalProperties: true,
286
+ *
287
+ * // Optional: customize runtime theme hook import/call used by emitted wrappers
288
+ * themeHook: {
289
+ * functionName: "useTheme",
290
+ * importSource: { kind: "specifier", value: "styled-components" },
291
+ * },
292
+ * });
293
+ */
294
+ function defineAdapter(adapter) {
295
+ assertValidAdapterInput(adapter, "defineAdapter(adapter)");
296
+ return adapter;
297
+ }
298
+ //#endregion
299
+ //#region src/internal/merge-markers.ts
300
+ /**
301
+ * Shared utility for merging marker sidecar content.
302
+ * Core concepts: deduplication of defineMarker declarations across files.
303
+ */
304
+ /** Regex matching a marker block: optional JSDoc comment followed by the export line. */
305
+ const MARKER_BLOCK_RE = /(?:\/\*\*[^]*?\*\/\n)?export const \w+ = stylex\.defineMarker\(\);/gm;
306
+ /** Regex matching just the export line (used for dedup checks). */
307
+ const MARKER_EXPORT_RE = /^export const \w+ = stylex\.defineMarker\(\);$/gm;
308
+ /** Regex matching a generated defineVars export block. */
309
+ const DEFINE_VARS_BLOCK_RE = /export const \w+ = stylex\.defineVars\(\{\n[\s\S]*?\n\}\);/gm;
310
+ /** Regex matching just the defineVars export line (used for dedup checks). */
311
+ const DEFINE_VARS_EXPORT_RE = /^export const \w+ = stylex\.defineVars\(\{$/gm;
312
+ /**
313
+ * Merge marker declarations from `incoming` into `base`, appending only new
314
+ * marker blocks (JSDoc + export). Returns `base` unchanged if all markers already exist.
315
+ */
316
+ function mergeMarkerDeclarations(base, incoming) {
317
+ const mergedDefineVars = mergeDefineVarsBlocks(base, incoming);
318
+ const markerBlocks = getNewBlocks({
319
+ base: mergedDefineVars,
320
+ incoming,
321
+ blockRegex: MARKER_BLOCK_RE,
322
+ exportRegex: MARKER_EXPORT_RE
323
+ });
324
+ const defineVarsBlocks = getNewBlocks({
325
+ base: mergedDefineVars,
326
+ incoming,
327
+ blockRegex: DEFINE_VARS_BLOCK_RE,
328
+ exportRegex: DEFINE_VARS_EXPORT_RE
329
+ });
330
+ const blocksToAdd = [...markerBlocks, ...defineVarsBlocks];
331
+ if (blocksToAdd.length === 0) return mergedDefineVars;
332
+ let merged = mergedDefineVars;
333
+ if (!merged.includes("@stylexjs/stylex")) merged = `import * as stylex from "@stylexjs/stylex";\n\n${merged}`;
334
+ return merged.trimEnd() + "\n\n" + blocksToAdd.join("\n\n") + "\n";
335
+ }
336
+ function getNewBlocks(args) {
337
+ const { base, incoming, blockRegex, exportRegex } = args;
338
+ const incomingExports = [...incoming.matchAll(exportRegex)].map((m) => m[0]);
339
+ if (incomingExports.length === 0) return [];
340
+ const newExportLines = incomingExports.filter((line) => !base.includes(line));
341
+ if (newExportLines.length === 0) return [];
342
+ const newExportSet = new Set(newExportLines);
343
+ return [...incoming.matchAll(blockRegex)].map((m) => m[0]).filter((block) => {
344
+ const exportLine = block.match(exportRegex);
345
+ return exportLine && newExportSet.has(exportLine[0]);
346
+ });
347
+ }
348
+ function mergeDefineVarsBlocks(base, incoming) {
349
+ let merged = base;
350
+ for (const incomingBlock of incoming.matchAll(DEFINE_VARS_BLOCK_RE)) {
351
+ const incomingText = incomingBlock[0];
352
+ const exportName = readDefineVarsExportName(incomingText);
353
+ if (!exportName) continue;
354
+ const existingBlock = findDefineVarsBlockByExportName(merged, exportName);
355
+ if (!existingBlock) continue;
356
+ const entriesToAdd = getMissingDefineVarsEntries({
357
+ existingBlock: existingBlock.text,
358
+ incomingBlock: incomingText
359
+ });
360
+ if (entriesToAdd.length === 0) continue;
361
+ const insertionPoint = existingBlock.start + existingBlock.text.lastIndexOf("\n});");
362
+ const linesToAdd = entriesToAdd.map((entry) => entry.line);
363
+ merged = `${merged.slice(0, insertionPoint)}\n${linesToAdd.join("\n")}${merged.slice(insertionPoint)}`;
364
+ }
365
+ return merged;
366
+ }
367
+ function readDefineVarsExportName(block) {
368
+ return /^export const ([A-Za-z_$][\w$]*) = stylex\.defineVars\(\{/m.exec(block)?.[1] ?? null;
369
+ }
370
+ function findDefineVarsBlockByExportName(source, exportName) {
371
+ for (const match of source.matchAll(DEFINE_VARS_BLOCK_RE)) {
372
+ const text = match[0];
373
+ if (readDefineVarsExportName(text) === exportName && match.index !== void 0) return {
374
+ text,
375
+ start: match.index
376
+ };
377
+ }
378
+ return null;
379
+ }
380
+ function getMissingDefineVarsEntries(args) {
381
+ const { existingBlock, incomingBlock } = args;
382
+ const existingKeys = new Set(readDefineVarsEntryKeys(existingBlock));
383
+ return readDefineVarsEntries(incomingBlock).filter((entry) => !existingKeys.has(entry.key));
384
+ }
385
+ function readDefineVarsEntryKeys(block) {
386
+ return readDefineVarsEntries(block).map((entry) => entry.key);
387
+ }
388
+ function readDefineVarsEntries(block) {
389
+ return block.split("\n").map((line) => ({
390
+ line,
391
+ match: /^\s*(?:(["']--[^"']+["'])|([A-Za-z_$][\w$]*))\s*:/.exec(line)
392
+ })).filter((entry) => Boolean(entry.match)).map(({ line, match }) => ({
393
+ key: normalizeDefineVarsEntryKey(match[1] ?? match[2]),
394
+ line
395
+ }));
396
+ }
397
+ function normalizeDefineVarsEntryKey(key) {
398
+ if (key.length >= 2 && (key.startsWith("\"") && key.endsWith("\"") || key.startsWith("'") && key.endsWith("'"))) return key.slice(1, -1);
399
+ return key;
400
+ }
401
+ //#endregion
402
+ //#region src/internal/utilities/jscodeshift-utils.ts
403
+ /**
404
+ * Extracts the root identifier and member path from an expression.
405
+ *
406
+ * Examples:
407
+ * `zIndex` → { rootName: "zIndex", rootNode: <ident>, path: [] }
408
+ * `zIndex.modal` → { rootName: "zIndex", rootNode: <ident>, path: ["modal"] }
409
+ * `config.ui.spacing.small` → { rootName: "config", rootNode: <ident>, path: ["ui", "spacing", "small"] }
410
+ *
411
+ * Returns null for computed properties, non-identifier roots, or invalid expressions.
412
+ */
413
+ function extractRootAndPath(node) {
414
+ if (!node || typeof node !== "object") return null;
415
+ if (isIdentifierNode(node)) return {
416
+ rootName: node.name,
417
+ rootNode: node,
418
+ path: []
419
+ };
420
+ if (!isMemberExpressionNode(node)) return null;
421
+ const parts = [];
422
+ let cur = node;
423
+ while (cur && typeof cur === "object") {
424
+ if (!isMemberExpressionNode(cur)) break;
425
+ if (cur.computed) return null;
426
+ const prop = cur.property;
427
+ if (!isIdentifierNode(prop)) return null;
428
+ parts.unshift(prop.name);
429
+ cur = cur.object;
430
+ }
431
+ if (!cur || typeof cur !== "object") return null;
432
+ if (!isIdentifierNode(cur)) return null;
433
+ return {
434
+ rootName: cur.name,
435
+ rootNode: cur,
436
+ path: parts
437
+ };
438
+ }
439
+ /**
440
+ * Extracts the member path from an expression, validating that the root is a specific identifier.
441
+ *
442
+ * Examples (with rootIdentName="props"):
443
+ * `props.theme.color.primary` → ["theme", "color", "primary"]
444
+ * `props.className` → ["className"]
445
+ * `other.theme` → null (root doesn't match)
446
+ *
447
+ * Returns null if:
448
+ * - The expression contains computed properties
449
+ * - The root identifier doesn't match rootIdentName
450
+ * - The expression is not a valid member chain
451
+ */
452
+ function getMemberPathFromIdentifier(expr, rootIdentName) {
453
+ const info = extractRootAndPath(expr);
454
+ if (!info || info.rootName !== rootIdentName) return null;
455
+ return info.path;
456
+ }
457
+ /**
458
+ * Extracts a single prop name from a member expression like `props.foo`.
459
+ * Returns null if the expression doesn't resolve to a single-level property access.
460
+ */
461
+ function getSinglePropFromMemberExpr(node, rootIdentName) {
462
+ if (!node || typeof node !== "object") return null;
463
+ const path = getMemberPathFromIdentifier(node, rootIdentName);
464
+ if (!path || path.length !== 1 || !path[0]) return null;
465
+ return path[0];
466
+ }
467
+ /**
468
+ * Extracts the root JSX identifier name from a JSX name node.
469
+ *
470
+ * Examples:
471
+ * `Foo` → "Foo"
472
+ * `Foo.Bar` → "Foo"
473
+ * `Foo.Bar.Baz` → "Foo"
474
+ *
475
+ * Returns null for invalid or non-JSX name nodes.
476
+ */
477
+ function getRootJsxIdentifierName(node) {
478
+ let current = node;
479
+ while (current && typeof current === "object") {
480
+ if (isJsxIdentifierNode(current)) return current.name;
481
+ const typed = current;
482
+ if (typed.type === "JSXMemberExpression") {
483
+ current = typed.object;
484
+ continue;
485
+ }
486
+ break;
487
+ }
488
+ return null;
489
+ }
490
+ /**
491
+ * Extracts the JSX element name with optional member-expression support.
492
+ *
493
+ * When allowMemberExpression is false, only simple JSX identifiers are returned.
494
+ */
495
+ function getJsxElementName(node, options) {
496
+ if (!options?.allowMemberExpression) return isJsxIdentifierNode(node) ? node.name : null;
497
+ return getRootJsxIdentifierName(node);
498
+ }
499
+ /**
500
+ * Type guard for IdentifierNode (minimal type with just type and name).
501
+ */
502
+ function isIdentifierNode(node) {
503
+ if (!node || typeof node !== "object") return false;
504
+ const typed = node;
505
+ return typed.type === "Identifier" && typeof typed.name === "string";
506
+ }
507
+ /**
508
+ * Type guard for CallExpression nodes.
509
+ */
510
+ function isCallExpressionNode(node) {
511
+ return !!node && typeof node === "object" && node.type === "CallExpression";
512
+ }
513
+ /** Type guard for `MemberExpression` and `OptionalMemberExpression` nodes. */
514
+ function isMemberExpressionNode(node) {
515
+ if (!node || typeof node !== "object") return false;
516
+ const type = node.type;
517
+ return type === "MemberExpression" || type === "OptionalMemberExpression";
518
+ }
519
+ /**
520
+ * Reads `object.property` when both sides are identifiers and the property is
521
+ * non-optional and non-computed. Returns null for optional/computed paths the
522
+ * caller cannot safely treat as a static member reference.
523
+ */
524
+ function getIdentifierMemberPropertyName(node, objectName) {
525
+ if (!isMemberExpressionNode(node) || node.type !== "MemberExpression" || node.computed) return null;
526
+ if (!isIdentifierNode(node.object) || node.object.name !== objectName) return null;
527
+ return isIdentifierNode(node.property) ? node.property.name : null;
528
+ }
529
+ /** Matches a static `objectName.propertyName` member reference. */
530
+ function isIdentifierMemberExpression(node, objectName, propertyName) {
531
+ return getIdentifierMemberPropertyName(node, objectName) === propertyName;
532
+ }
533
+ /**
534
+ * Type guard for AstPath objects (jscodeshift path wrappers).
535
+ */
536
+ function isAstPath(value) {
537
+ return !!value && typeof value === "object" && "node" in value;
538
+ }
539
+ /**
540
+ * Type guard for AST nodes (objects with a string `type` property).
541
+ * Returns false for arrays and non-objects.
542
+ */
543
+ function isAstNode(v) {
544
+ return !!v && typeof v === "object" && !Array.isArray(v) && typeof v.type === "string";
545
+ }
546
+ /**
547
+ * Generic type guard narrowing an unknown value to an AST node of a specific `type`.
548
+ * Returns false for arrays and non-objects.
549
+ */
550
+ function isNodeOfType(node, type) {
551
+ return isAstNode(node) && node.type === type;
552
+ }
553
+ /**
554
+ * Type guard for function-like nodes including class/object methods.
555
+ */
556
+ function isFunctionNode(node) {
557
+ if (!node || typeof node !== "object") return false;
558
+ const type = node.type;
559
+ return type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression" || type === "ObjectMethod" || type === "ClassMethod";
560
+ }
561
+ /**
562
+ * Returns true when a destructuring/binding pattern (or nested pattern) binds
563
+ * any of the given names. Handles Identifier, Rest/Assignment patterns, object
564
+ * and array patterns, and Property/ObjectProperty nodes.
565
+ */
566
+ function patternBindsAnyName(node, names) {
567
+ if (!node || typeof node !== "object") return false;
568
+ const record = node;
569
+ if (record.type === "Identifier") return typeof record.name === "string" && names.has(record.name);
570
+ if (record.type === "RestElement") return patternBindsAnyName(record.argument, names);
571
+ if (record.type === "AssignmentPattern") return patternBindsAnyName(record.left, names);
572
+ if (record.type === "ObjectPattern") return Array.isArray(record.properties) && record.properties.some((prop) => patternBindsAnyName(prop, names));
573
+ if (record.type === "ObjectProperty" || record.type === "Property") return patternBindsAnyName(record.value, names);
574
+ if (record.type === "ArrayPattern") return Array.isArray(record.elements) && record.elements.some((element) => patternBindsAnyName(element, names));
575
+ return false;
576
+ }
577
+ /**
578
+ * Returns true when `name` is one of React's `ComponentProps` utility-type
579
+ * names (`ComponentProps`, `ComponentPropsWithRef`, `ComponentPropsWithoutRef`).
580
+ */
581
+ function isReactComponentPropsUtilityName(name) {
582
+ return name === "ComponentProps" || name === "ComponentPropsWithRef" || name === "ComponentPropsWithoutRef";
583
+ }
584
+ /**
585
+ * Returns true when a TS type node is numeric (`number`, numeric literal, or a
586
+ * union of such). When `allowOptional` is true, union members may also be
587
+ * `undefined`/`null` (matching an optional numeric prop); otherwise the union
588
+ * must be non-empty and every member must itself be numeric.
589
+ */
590
+ function isNumericTsType(tsType, options = {}) {
591
+ if (!tsType || typeof tsType !== "object") return false;
592
+ const allowOptional = options.allowOptional ?? false;
593
+ const type = tsType;
594
+ if (type.type === "TSNumberKeyword") return true;
595
+ if (type.type === "TSLiteralType") return typeof type.literal?.value === "number";
596
+ if (type.type === "TSUnionType" && Array.isArray(type.types)) {
597
+ if (allowOptional) return type.types.every((member) => {
598
+ const memberType = member?.type;
599
+ return memberType === "TSUndefinedKeyword" || memberType === "TSNullKeyword" || isNumericTsType(member, options);
600
+ });
601
+ return type.types.length > 0 && type.types.every((member) => isNumericTsType(member, options));
602
+ }
603
+ return false;
604
+ }
605
+ /**
606
+ * Extracts the name of an Identifier node, or null when the node is not a
607
+ * (named) Identifier. Accepts any value for convenient optional-chaining use.
608
+ */
609
+ function identifierName(node) {
610
+ if (!node || typeof node !== "object") return null;
611
+ const typed = node;
612
+ return typed.type === "Identifier" && typeof typed.name === "string" ? typed.name : null;
613
+ }
614
+ /** Returns true when `node` is an Identifier whose name equals `name`. */
615
+ function isIdentifierNamed(node, name) {
616
+ const typed = node;
617
+ return typed?.type === "Identifier" && typed.name === name;
618
+ }
619
+ /** Type guard for the `undefined` identifier literal. */
620
+ function isUndefinedIdentifier(node) {
621
+ return isIdentifierNamed(node, "undefined");
622
+ }
623
+ /**
624
+ * Type guard for ArrowFunctionExpression nodes.
625
+ */
626
+ function isArrowFunctionExpression(node) {
627
+ return !!node && typeof node === "object" && node.type === "ArrowFunctionExpression";
628
+ }
629
+ /**
630
+ * Extracts the id from a VariableDeclarator node.
631
+ */
632
+ function getDeclaratorId(decl) {
633
+ if (!decl || typeof decl !== "object") return null;
634
+ if (!("id" in decl)) return null;
635
+ return decl.id ?? null;
636
+ }
637
+ function getArrowFnSingleParamName(fn) {
638
+ if (fn.params.length !== 1) return null;
639
+ const p = fn.params[0];
640
+ return isIdentifier(p) ? p.name : null;
641
+ }
642
+ /**
643
+ * Extracts parameter binding information from an arrow function.
644
+ *
645
+ * Supports:
646
+ * - Simple identifier params: `(props) => ...`
647
+ * - Destructured params: `({ color }) => ...`
648
+ * - Renamed destructured params: `({ color: color_ }) => ...`
649
+ * - Default values: `({ color = "red" }) => ...`
650
+ * - Renamed with defaults: `({ color: color_ = "red" }) => ...`
651
+ *
652
+ * Returns null for:
653
+ * - Functions with != 1 parameter
654
+ * - Rest elements in destructuring (not supported)
655
+ * - Computed property keys
656
+ */
657
+ function getArrowFnParamBindings(fn) {
658
+ if (fn.params.length !== 1) return null;
659
+ const p = fn.params[0];
660
+ if (isIdentifier(p)) return {
661
+ kind: "simple",
662
+ paramName: p.name
663
+ };
664
+ if (p?.type === "ObjectPattern" && Array.isArray(p.properties)) {
665
+ const bindings = /* @__PURE__ */ new Map();
666
+ const defaults = /* @__PURE__ */ new Map();
667
+ const props = p.properties;
668
+ for (const prop of props) {
669
+ if (prop.type === "RestElement") return null;
670
+ if (prop.type !== "Property" && prop.type !== "ObjectProperty") continue;
671
+ if (prop.computed === true) return null;
672
+ const key = prop.key;
673
+ if (!key || key.type !== "Identifier") return null;
674
+ const propName = key.name;
675
+ if (!propName) continue;
676
+ const value = prop.value;
677
+ if (value?.type === "Identifier" && typeof value.name === "string") bindings.set(value.name, propName);
678
+ else if (value?.type === "AssignmentPattern") {
679
+ const left = value.left;
680
+ if (left?.type === "Identifier" && typeof left.name === "string") {
681
+ bindings.set(left.name, propName);
682
+ if (value.right) defaults.set(propName, value.right);
683
+ }
684
+ }
685
+ }
686
+ if (bindings.size === 0) return null;
687
+ return {
688
+ kind: "destructured",
689
+ bindings,
690
+ defaults: defaults.size > 0 ? defaults : void 0
691
+ };
692
+ }
693
+ return null;
694
+ }
695
+ /**
696
+ * Given an identifier node and param bindings, resolves the original prop name.
697
+ *
698
+ * For destructured params like `({ color: color_ }) => color_`:
699
+ * resolveIdentifierToPropName(color_Node, bindings) -> "color"
700
+ *
701
+ * Returns null if the identifier doesn't correspond to a destructured prop.
702
+ */
703
+ function resolveIdentifierToPropName(node, bindings) {
704
+ if (!node || typeof node !== "object") return null;
705
+ const typed = node;
706
+ if (typed.type !== "Identifier" || typeof typed.name !== "string") return null;
707
+ if (bindings.kind === "simple") return null;
708
+ return bindings.bindings.get(typed.name) ?? null;
709
+ }
710
+ function getNodeLocStart(node) {
711
+ const loc = node?.loc?.start;
712
+ if (!loc) return null;
713
+ return {
714
+ line: loc.line,
715
+ column: loc.column
716
+ };
717
+ }
718
+ const isExpressionKindNode = (node) => isAstNode(node);
719
+ const isReturnStatementNode = (node) => isAstNode(node) && node.type === "ReturnStatement";
720
+ const isIfStatementNode = (node) => isAstNode(node) && node.type === "IfStatement";
721
+ /**
722
+ * Extracts the single ReturnStatement expression from a statement.
723
+ * Handles both bare ReturnStatements and BlockStatements containing exactly one ReturnStatement.
724
+ */
725
+ function extractReturnExpr(stmt) {
726
+ if (isReturnStatementNode(stmt)) return isExpressionKindNode(stmt.argument) ? stmt.argument : void 0;
727
+ if (isAstNode(stmt) && stmt.type === "BlockStatement") {
728
+ const body = stmt.body;
729
+ if (Array.isArray(body) && body.length === 1) return extractReturnExpr(body[0]);
730
+ }
731
+ }
732
+ /**
733
+ * Converts a series of if/return statements into a nested ternary (ConditionalExpression).
734
+ *
735
+ * Handles patterns like:
736
+ * ```
737
+ * if (A) { return E1; }
738
+ * if (B) { return E2; }
739
+ * return E3;
740
+ * ```
741
+ * → `A ? E1 : B ? E2 : E3`
742
+ *
743
+ * Constraints (returns undefined if not met):
744
+ * - All statements are IfStatement (no else) or ReturnStatement
745
+ * - Last statement must be ReturnStatement with an expression argument
746
+ * - Each IfStatement consequent has exactly one ReturnStatement
747
+ */
748
+ function tryFlattenIfReturnChainToExpr(blockBody) {
749
+ const last = blockBody[blockBody.length - 1];
750
+ if (!isReturnStatementNode(last) || !isExpressionKindNode(last.argument)) return;
751
+ let result = last.argument;
752
+ for (let i = blockBody.length - 2; i >= 0; i--) {
753
+ const stmt = blockBody[i];
754
+ if (!isIfStatementNode(stmt)) return;
755
+ if (stmt.alternate != null) return;
756
+ const test = stmt.test;
757
+ if (!isExpressionKindNode(test)) return;
758
+ const consequentExpr = extractReturnExpr(stmt.consequent);
759
+ if (!consequentExpr) return;
760
+ result = {
761
+ type: "ConditionalExpression",
762
+ test,
763
+ consequent: consequentExpr,
764
+ alternate: result
765
+ };
766
+ }
767
+ return result;
768
+ }
769
+ /**
770
+ * Extracts the expression from an arrow/function expression body.
771
+ * - For expression bodies: returns the expression directly
772
+ * - For block bodies: returns the argument of the return statement,
773
+ * but ONLY if the block contains exactly one statement (a ReturnStatement).
774
+ * This ensures we don't support arrow functions with complex logic in the body.
775
+ */
776
+ function getFunctionBodyExpr(fn) {
777
+ const body = fn.body;
778
+ if (!body || typeof body !== "object") return;
779
+ if (isAstNode(body) && body.type === "BlockStatement") {
780
+ const blockBody = body.body;
781
+ if (!Array.isArray(blockBody) || blockBody.length === 0) return;
782
+ if (blockBody.length !== 1) return tryFlattenIfReturnChainToExpr(blockBody);
783
+ const statement = blockBody[0];
784
+ if (!isReturnStatementNode(statement)) return;
785
+ const argument = statement.argument ?? null;
786
+ if (argument === null) return null;
787
+ return isExpressionKindNode(argument) ? argument : void 0;
788
+ }
789
+ return isExpressionKindNode(body) ? body : void 0;
790
+ }
791
+ /**
792
+ * Recursively collects all Identifier names from an AST node into a Set.
793
+ * Skips 'loc' and 'comments' properties to avoid traversing metadata.
794
+ *
795
+ * @param node - The AST node to traverse
796
+ * @param out - The Set to collect identifier names into
797
+ */
798
+ function collectIdentifiers(node, out) {
799
+ if (!node || typeof node !== "object") return;
800
+ if (Array.isArray(node)) {
801
+ for (const child of node) collectIdentifiers(child, out);
802
+ return;
803
+ }
804
+ const typed = node;
805
+ if (typed.type === "Identifier" && typed.name) out.add(typed.name);
806
+ for (const key of Object.keys(node)) {
807
+ if (key === "loc" || key === "comments") continue;
808
+ collectIdentifiers(node[key], out);
809
+ }
810
+ }
811
+ /**
812
+ * Collects binding names introduced by an object/array destructuring pattern.
813
+ */
814
+ function collectPatternBindingNames(node, names) {
815
+ if (!node || typeof node !== "object") return;
816
+ if (Array.isArray(node)) {
817
+ for (const child of node) collectPatternBindingNames(child, names);
818
+ return;
819
+ }
820
+ const typed = node;
821
+ if (typed.type === "Identifier" && typed.name) {
822
+ names.add(typed.name);
823
+ return;
824
+ }
825
+ if (typed.type === "MemberExpression" || typed.type === "OptionalMemberExpression" || typed.type === "TSQualifiedName") return;
826
+ for (const key of Object.keys(node)) {
827
+ if (key === "loc" || key === "comments" || key === "leadingComments") continue;
828
+ collectPatternBindingNames(node[key], names);
829
+ }
830
+ }
831
+ /**
832
+ * Converts an AST literal node to its static JavaScript value.
833
+ *
834
+ * Supports:
835
+ * - StringLiteral, NumericLiteral, BooleanLiteral (Babel AST)
836
+ * - Literal (ESTree/recast AST)
837
+ * - TemplateLiteral without expressions (static template strings)
838
+ * - TaggedTemplateExpression with css tag (styled-components css helper)
839
+ * - TSAsExpression and TSSatisfiesExpression wrapping static values
840
+ * - ArrowFunctionExpression with no params and a static body (e.g., `() => "value"` or `() => \`value\``)
841
+ *
842
+ * Returns null for non-literal or dynamic nodes.
843
+ */
844
+ function literalToStaticValue(node, options = {}) {
845
+ if (!node || typeof node !== "object") return null;
846
+ const allowCssTaggedTemplates = options.allowCssTaggedTemplates ?? true;
847
+ const allowStaticArrowFunctions = options.allowStaticArrowFunctions ?? true;
848
+ const type = node.type;
849
+ if (type === "TSAsExpression" || type === "TSSatisfiesExpression") return literalToStaticValue(node.expression, options);
850
+ if (type === "StringLiteral") return node.value;
851
+ if (type === "BooleanLiteral") return node.value;
852
+ if (type === "Literal") {
853
+ const v = node.value;
854
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return v;
855
+ }
856
+ if (type === "NumericLiteral") return node.value;
857
+ if (type === "UnaryExpression") {
858
+ const n = node;
859
+ if (n.prefix && (n.operator === "-" || n.operator === "+")) {
860
+ const argVal = literalToStaticValue(n.argument, options);
861
+ if (typeof argVal === "number") return n.operator === "-" ? -argVal : argVal;
862
+ }
863
+ }
864
+ if (type === "TemplateLiteral") {
865
+ const n = node;
866
+ if (!n.expressions || n.expressions.length === 0) {
867
+ const quasis = n.quasis ?? [];
868
+ const parts = [];
869
+ for (const q of quasis) {
870
+ const cooked = q.value?.cooked;
871
+ if (cooked == null) return null;
872
+ parts.push(cooked);
873
+ }
874
+ return parts.join("");
875
+ }
876
+ }
877
+ if (type === "TaggedTemplateExpression" && allowCssTaggedTemplates) {
878
+ const n = node;
879
+ if (n.tag?.type === "Identifier" && n.tag.name === "css") return literalToStaticValue(n.quasi, options);
880
+ }
881
+ if (type === "ArrowFunctionExpression" && allowStaticArrowFunctions) {
882
+ const n = node;
883
+ if (!n.params || n.params.length === 0) return literalToStaticValue(n.body, options);
884
+ }
885
+ return null;
886
+ }
887
+ /**
888
+ * Extracts a static literal value from an AST node, distinguishing null literals
889
+ * from extraction failure. Returns `undefined` when the node is not a recognized
890
+ * static literal, and the actual value (including `null`) otherwise.
891
+ *
892
+ * Function expressions are not coerced by default: a function-valued expression
893
+ * is runtime data, even if its body is a static literal.
894
+ */
895
+ function extractStaticLiteralValue(node, options = {}) {
896
+ if (!node || typeof node !== "object") return;
897
+ const typed = node;
898
+ if (typed.type === "TSAsExpression" || typed.type === "TSSatisfiesExpression") return extractStaticLiteralValue(typed.expression, options);
899
+ if (typed.type === "NullLiteral" || typed.type === "Literal" && typed.value === null) return null;
900
+ const v = literalToStaticValue(node, {
901
+ ...options,
902
+ allowStaticArrowFunctions: options.allowStaticArrowFunctions ?? false
903
+ });
904
+ return v !== null ? v : void 0;
905
+ }
906
+ /**
907
+ * Converts an AST literal node to a string value.
908
+ * Returns null if the node is not a string literal.
909
+ */
910
+ function literalToString(node) {
911
+ const v = literalToStaticValue(node);
912
+ return typeof v === "string" ? v : null;
913
+ }
914
+ /**
915
+ * Scans the file AST for TypeScript string/numeric enum declarations and
916
+ * returns a two-level map: enumName -> memberName -> static value.
917
+ * Only handles members with literal initializers.
918
+ */
919
+ function buildEnumValueMap(root, j) {
920
+ const map = /* @__PURE__ */ new Map();
921
+ root.find(j.TSEnumDeclaration).forEach((path) => {
922
+ const enumNode = path.value;
923
+ const enumName = enumNode.id.name;
924
+ const members = /* @__PURE__ */ new Map();
925
+ let nextNumericValue = 0;
926
+ for (const member of enumNode.members) {
927
+ const memberName = member.id.type === "Identifier" ? member.id.name : member.id.value;
928
+ if (member.initializer) {
929
+ const val = literalToStaticValue(member.initializer);
930
+ if (val !== null) {
931
+ members.set(memberName, val);
932
+ nextNumericValue = typeof val === "number" ? val + 1 : 0;
933
+ }
934
+ } else {
935
+ members.set(memberName, nextNumericValue);
936
+ nextNumericValue++;
937
+ }
938
+ }
939
+ if (members.size > 0) map.set(enumName, members);
940
+ });
941
+ return map;
942
+ }
943
+ /**
944
+ * Resolves a MemberExpression node (e.g., `ProgressType.success`) to its
945
+ * static enum value using a pre-built enum map. Returns null if the node
946
+ * is not a MemberExpression or doesn't reference a known enum member.
947
+ */
948
+ function resolveStaticExpressionValue(node, enumValueMap) {
949
+ const v = literalToStaticValue(node);
950
+ if (v !== null) return v;
951
+ if (!enumValueMap || !node || typeof node !== "object") return null;
952
+ const n = node;
953
+ if (n.type !== "MemberExpression" || n.computed) return null;
954
+ const obj = n.object;
955
+ const prop = n.property;
956
+ if (obj?.type !== "Identifier" || !obj.name || prop?.type !== "Identifier" || !prop.name) return null;
957
+ return enumValueMap.get(obj.name)?.get(prop.name) ?? null;
958
+ }
959
+ /**
960
+ * Returns true if an AST node represents an "empty" CSS branch value.
961
+ * Styled-components treats falsy interpolations as "omit this declaration".
962
+ *
963
+ * Recognized empty values: `""`, empty TemplateLiteral, `null`, `undefined`,
964
+ * `false`, and trivial `void` expressions (`void 0`, `void null`, etc.).
965
+ */
966
+ function isEmptyCssBranch(node) {
967
+ if (!node || typeof node !== "object") return false;
968
+ const n = node;
969
+ if ((n.type === "StringLiteral" || n.type === "Literal") && n.value === "") return true;
970
+ if (n.type === "TemplateLiteral") {
971
+ const tpl = node;
972
+ if (tpl.expressions && tpl.expressions.length > 0) return false;
973
+ return (tpl.quasis ?? []).every((q) => (q.value?.raw ?? "") === "");
974
+ }
975
+ if (n.type === "NullLiteral") return true;
976
+ if (n.type === "Identifier" && n.name === "undefined") return true;
977
+ if (n.type === "BooleanLiteral" && n.value === false) return true;
978
+ if (n.type === "UnaryExpression" && n.operator === "void") return isTriviallyPureVoidArg(node.argument);
979
+ return false;
980
+ }
981
+ /**
982
+ * Creates a literal AST node from a static JavaScript value.
983
+ * This is the inverse of `literalToStaticValue`.
984
+ *
985
+ * @param j - The jscodeshift API
986
+ * @param value - The static value to convert
987
+ * @returns A literal AST node (StringLiteral, NumericLiteral, or BooleanLiteral)
988
+ */
989
+ function staticValueToLiteral(j, value) {
990
+ if (typeof value === "string") return j.stringLiteral(value);
991
+ if (typeof value === "boolean") return j.booleanLiteral(value);
992
+ return j.numericLiteral(value);
993
+ }
994
+ /**
995
+ * Set of AST metadata keys that should typically be skipped during traversal or cloning.
996
+ * These keys contain position/source information and are not part of the logical AST structure.
997
+ */
998
+ const AST_METADATA_KEYS = new Set([
999
+ "loc",
1000
+ "start",
1001
+ "end",
1002
+ "range",
1003
+ "comments",
1004
+ "tokens"
1005
+ ]);
1006
+ /**
1007
+ * Deep clones an AST node, stripping metadata properties (loc, comments, tokens, etc.).
1008
+ * Useful for creating modified copies of AST nodes without mutating the original.
1009
+ *
1010
+ * @param node - The AST node to clone
1011
+ * @returns A deep clone with metadata properties removed
1012
+ */
1013
+ function cloneAstNode(node) {
1014
+ if (!node || typeof node !== "object") return node;
1015
+ if (Array.isArray(node)) return node.map(cloneAstNode);
1016
+ const out = {};
1017
+ for (const key of Object.keys(node)) {
1018
+ if (AST_METADATA_KEYS.has(key)) continue;
1019
+ out[key] = cloneAstNode(node[key]);
1020
+ }
1021
+ return out;
1022
+ }
1023
+ /**
1024
+ * Conservative purity check for a JS expression: returns true when evaluating
1025
+ * the expression repeatedly yields the same value and produces no observable
1026
+ * side effects (assignment, function call, mutation, await, etc.).
1027
+ *
1028
+ * Use this before lifting an expression out of N evaluations into a single
1029
+ * shared evaluation (e.g. collapsing `cond ? a : b` repeated for several
1030
+ * properties into one `cond && variant` guard). Anything that *might* return a
1031
+ * different value on a subsequent call (function/method calls, `new`, `yield`,
1032
+ * `await`, assignments, increment/decrement) is treated as impure.
1033
+ *
1034
+ * Conservatively returns false for unfamiliar node types.
1035
+ */
1036
+ function isPureIdempotentExpression(node) {
1037
+ if (!isAstNode(node)) return false;
1038
+ const n = node;
1039
+ switch (n.type) {
1040
+ case "Identifier":
1041
+ case "ThisExpression":
1042
+ case "Super":
1043
+ case "NullLiteral":
1044
+ case "BooleanLiteral":
1045
+ case "NumericLiteral":
1046
+ case "StringLiteral":
1047
+ case "BigIntLiteral":
1048
+ case "RegExpLiteral":
1049
+ case "Literal": return true;
1050
+ case "TemplateLiteral": return Array.isArray(n.expressions) && n.expressions.every(isPureIdempotentExpression);
1051
+ case "UnaryExpression":
1052
+ if (n.operator === "delete") return false;
1053
+ return isPureIdempotentExpression(n.argument);
1054
+ case "BinaryExpression": return isPureIdempotentExpression(n.left) && isPureIdempotentExpression(n.right);
1055
+ case "LogicalExpression": return isPureIdempotentExpression(n.left) && isPureIdempotentExpression(n.right);
1056
+ case "ConditionalExpression": return isPureIdempotentExpression(n.test) && isPureIdempotentExpression(n.consequent) && isPureIdempotentExpression(n.alternate);
1057
+ case "MemberExpression":
1058
+ case "OptionalMemberExpression":
1059
+ if (!isPureIdempotentExpression(n.object)) return false;
1060
+ if (n.computed) return false;
1061
+ return true;
1062
+ case "TSAsExpression":
1063
+ case "TSTypeAssertion":
1064
+ case "TSNonNullExpression":
1065
+ case "TSSatisfiesExpression": return isPureIdempotentExpression(n.expression);
1066
+ case "ParenthesizedExpression": return isPureIdempotentExpression(n.expression);
1067
+ case "ArrayExpression": return Array.isArray(n.elements) && n.elements.every((el) => el === null || isPureIdempotentExpression(el));
1068
+ case "ObjectExpression": return Array.isArray(n.properties) && n.properties.every((p) => {
1069
+ if (!isAstNode(p)) return false;
1070
+ const prop = p;
1071
+ if (prop.type !== "Property" && prop.type !== "ObjectProperty") return false;
1072
+ if (prop.computed && !isPureIdempotentExpression(prop.key)) return false;
1073
+ return isPureIdempotentExpression(prop.value);
1074
+ });
1075
+ default: return false;
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Structural equality check for AST nodes.
1080
+ * Compares two nodes recursively, ignoring metadata (loc, start/end, comments, tokens).
1081
+ * Returns true when the nodes have the same shape and values.
1082
+ */
1083
+ function astNodesEqual(a, b) {
1084
+ if (a === b) return true;
1085
+ if (a === null || b === null || a === void 0 || b === void 0) return false;
1086
+ if (typeof a !== "object" || typeof b !== "object") return false;
1087
+ if (Array.isArray(a) || Array.isArray(b)) {
1088
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1089
+ for (let i = 0; i < a.length; i++) if (!astNodesEqual(a[i], b[i])) return false;
1090
+ return true;
1091
+ }
1092
+ const aKeys = Object.keys(a).filter((k) => !AST_METADATA_KEYS.has(k));
1093
+ const bKeys = Object.keys(b).filter((k) => !AST_METADATA_KEYS.has(k));
1094
+ if (aKeys.length !== bKeys.length) return false;
1095
+ for (const key of aKeys) {
1096
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
1097
+ if (!astNodesEqual(a[key], b[key])) return false;
1098
+ }
1099
+ return true;
1100
+ }
1101
+ /**
1102
+ * Type guard for LogicalExpression nodes.
1103
+ */
1104
+ function isLogicalExpressionNode(node) {
1105
+ return !!node && typeof node === "object" && node.type === "LogicalExpression";
1106
+ }
1107
+ /**
1108
+ * Extracts the left side of a `??` or `||` logical expression when it's a
1109
+ * MemberExpression or OptionalMemberExpression. Returns null if the pattern
1110
+ * doesn't match.
1111
+ *
1112
+ * Used to extract the theme access part from fallback patterns like
1113
+ * `props.theme.color.labelBase ?? "black"` for resolution. Callers are
1114
+ * responsible for re-wrapping the resolved value with the original fallback.
1115
+ */
1116
+ function unwrapLogicalFallback(expr) {
1117
+ if (isLogicalExpressionNode(expr) && (expr.operator === "??" || expr.operator === "||") && isMemberExpressionNode(expr.left)) return expr.left;
1118
+ return null;
1119
+ }
1120
+ /**
1121
+ * Returns true when `expr` is a `??` / `||` logical expression whose
1122
+ * right-hand side is a non-literal value (e.g., `props.fallbackColor`).
1123
+ *
1124
+ * Used to bail on theme resolution when the fallback references a runtime
1125
+ * value the user depends on — dropping it silently would change semantics.
1126
+ */
1127
+ function hasNonLiteralLogicalFallback(expr) {
1128
+ return isLogicalExpressionNode(expr) && (expr.operator === "??" || expr.operator === "||") && literalToStaticValue(expr.right) === null;
1129
+ }
1130
+ /**
1131
+ * Type guard for ConditionalExpression nodes.
1132
+ */
1133
+ function isConditionalExpressionNode(node) {
1134
+ return !!node && typeof node === "object" && node.type === "ConditionalExpression";
1135
+ }
1136
+ /**
1137
+ * Sets the type annotation on an identifier node.
1138
+ * This encapsulates the pattern needed because jscodeshift's TypeScript types
1139
+ * don't include `typeAnnotation` on Identifier nodes in all contexts.
1140
+ *
1141
+ * @param identifier - The identifier node to annotate
1142
+ * @param typeAnnotation - The type annotation (typically from j.tsTypeAnnotation())
1143
+ */
1144
+ function setIdentifierTypeAnnotation(identifier, typeAnnotation) {
1145
+ identifier.typeAnnotation = typeAnnotation;
1146
+ }
1147
+ /**
1148
+ * Builds the appropriate expression for a style function call based on condition type.
1149
+ *
1150
+ * Handles three cases:
1151
+ * - "truthy": wraps with `prop ? call : undefined` to avoid passing false/"" into stylex.props
1152
+ * - "always": returns call directly (the callArg already handles null, e.g., `${$prop ?? 0}ms`)
1153
+ * - undefined (default): wraps with `prop != null && call` for optional props, or returns call for required props
1154
+ */
1155
+ function buildStyleFnConditionExpr(args) {
1156
+ const { j, condition, propExpr, call, isRequired } = args;
1157
+ if (condition === "truthy") return j.conditionalExpression(propExpr, call, j.identifier("undefined"));
1158
+ if (condition === "always" || isRequired) return call;
1159
+ return j.logicalExpression("&&", j.binaryExpression("!=", propExpr, j.nullLiteral()), call);
1160
+ }
1161
+ function isIdentifier(node, name) {
1162
+ return isIdentifierNode(node) && (name ? node.name === name : true);
1163
+ }
1164
+ function isJsxIdentifierNode(node) {
1165
+ if (!node || typeof node !== "object") return false;
1166
+ const typed = node;
1167
+ return typed.type === "JSXIdentifier" && typeof typed.name === "string";
1168
+ }
1169
+ /** Allow `void 0`, `void null`, `void ""`, `void 1`, `void false` as empty. */
1170
+ function isTriviallyPureVoidArg(arg) {
1171
+ if (!arg || typeof arg !== "object") return false;
1172
+ const n = arg;
1173
+ if (n.type === "NumericLiteral" && n.value === 0) return true;
1174
+ if (n.type === "NullLiteral") return true;
1175
+ if (n.type === "StringLiteral" && n.value === "") return true;
1176
+ if (n.type === "BooleanLiteral" && n.value === false) return true;
1177
+ if (n.type === "Literal") {
1178
+ const v = n.value;
1179
+ return v === 0 || v === null || v === "" || v === false;
1180
+ }
1181
+ return false;
1182
+ }
1183
+ //#endregion
1184
+ //#region src/internal/utilities/sx-surface.ts
1185
+ function transformedComponentAcceptsSx(args) {
1186
+ const source = readTransformedSource(args.absolutePath, args.sourceOverrides);
1187
+ if (!source) return false;
1188
+ return args.componentNames.some((componentName) => componentHasSxProp(source, componentName));
1189
+ }
1190
+ function readTransformedSource(absolutePath, sourceOverrides) {
1191
+ for (const candidate of sourcePathCandidates(absolutePath)) {
1192
+ const source = sourceOverrides?.get(toRealPath(candidate));
1193
+ if (source !== void 0) return source;
1194
+ if (existsSync(candidate)) try {
1195
+ return readFileSync(candidate, "utf8");
1196
+ } catch {
1197
+ return;
1198
+ }
1199
+ }
1200
+ }
1201
+ function sourcePathCandidates(absolutePath) {
1202
+ return [
1203
+ "",
1204
+ ".tsx",
1205
+ ".ts",
1206
+ ".jsx",
1207
+ ".js"
1208
+ ].map((ext) => absolutePath + ext);
1209
+ }
1210
+ function componentHasSxProp(source, componentName) {
1211
+ const propsType = readFunctionPropsType(source, componentName) ?? readArrowFunctionPropsType(source, componentName);
1212
+ return propsType ? typeTextHasSx(source, propsType, /* @__PURE__ */ new Set()) : false;
1213
+ }
1214
+ function readFunctionPropsType(source, componentName) {
1215
+ if (componentName === "default") {
1216
+ const defaultMatch = source.match(/export\s+default\s+function\s*\(/);
1217
+ if (defaultMatch?.index !== void 0) return readFirstPropsType(source, defaultMatch.index + defaultMatch[0].length);
1218
+ }
1219
+ const match = source.match(new RegExp(`(?:export\\s+)?function\\s+${escapeRegex(componentName)}\\s*\\(`));
1220
+ return match?.index === void 0 ? void 0 : readFirstPropsType(source, match.index + match[0].length);
1221
+ }
1222
+ function readArrowFunctionPropsType(source, componentName) {
1223
+ const match = source.match(new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${escapeRegex(componentName)}\\s*=\\s*\\(`));
1224
+ return match?.index === void 0 ? void 0 : readFirstPropsType(source, match.index + match[0].length);
1225
+ }
1226
+ function readFirstPropsType(source, startIndex) {
1227
+ const prefix = source.slice(startIndex).match(/^\s*props\s*:\s*/);
1228
+ if (!prefix) return;
1229
+ let depth = 0;
1230
+ const typeStart = startIndex + prefix[0].length;
1231
+ for (let i = typeStart; i < source.length; i++) {
1232
+ const ch = source[i];
1233
+ if (ch === "{" || ch === "<" || ch === "[" || ch === "(") depth++;
1234
+ else if (ch === "}" || ch === ">" || ch === "]" || ch === ")" && depth > 0) depth--;
1235
+ else if ((ch === "," || ch === ")") && depth <= 0) return source.slice(typeStart, i);
1236
+ }
1237
+ }
1238
+ function typeTextHasSx(source, typeText, visited) {
1239
+ if (/\bsx\??\s*:\s*(?:stylex\.)?StyleXStyles\b/.test(typeText)) return true;
1240
+ const typeName = typeText.trim().match(/^([A-Za-z_$][\w$]*)$/)?.[1];
1241
+ if (!typeName || visited.has(typeName)) return false;
1242
+ visited.add(typeName);
1243
+ const declaration = readTypeAlias(source, typeName) ?? readInterfaceBody(source, typeName);
1244
+ return declaration ? typeTextHasSx(source, declaration, visited) : false;
1245
+ }
1246
+ function readTypeAlias(source, typeName) {
1247
+ const match = source.match(new RegExp(`type\\s+${escapeRegex(typeName)}\\b\\s*=\\s*`));
1248
+ if (match?.index === void 0) return;
1249
+ return readUntilTopLevelTerminator(source, match.index + match[0].length, ";");
1250
+ }
1251
+ function readInterfaceBody(source, typeName) {
1252
+ const match = source.match(new RegExp(`interface\\s+${escapeRegex(typeName)}\\b[^{]*\\{`));
1253
+ if (match?.index === void 0) return;
1254
+ return readUntilMatchingBrace(source, match.index + match[0].length - 1);
1255
+ }
1256
+ function readUntilTopLevelTerminator(source, startIndex, terminator) {
1257
+ let depth = 0;
1258
+ for (let i = startIndex; i < source.length; i++) {
1259
+ const ch = source[i];
1260
+ if (ch === "{" || ch === "<" || ch === "[" || ch === "(") depth++;
1261
+ else if (ch === "}" || ch === ">" || ch === "]" || ch === ")" && depth > 0) depth--;
1262
+ else if (ch === terminator && depth <= 0) return source.slice(startIndex, i);
1263
+ }
1264
+ }
1265
+ function readUntilMatchingBrace(source, braceIndex) {
1266
+ let depth = 0;
1267
+ for (let i = braceIndex; i < source.length; i++) {
1268
+ const ch = source[i];
1269
+ if (ch === "{") depth++;
1270
+ else if (ch === "}") {
1271
+ depth--;
1272
+ if (depth === 0) return source.slice(braceIndex + 1, i);
1273
+ }
1274
+ }
1275
+ }
1276
+ function escapeRegex(value) {
1277
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1278
+ }
1279
+ //#endregion
1280
+ export { describeValue as $, isIdentifierNamed as A, literalToString as B, isAstNode as C, isEmptyCssBranch as D, isConditionalExpressionNode as E, isNumericTsType as F, staticValueToLiteral as G, resolveIdentifierToPropName as H, isPureIdempotentExpression as I, DEFAULT_THEME_HOOK as J, unwrapLogicalFallback as K, isReactComponentPropsUtilityName as L, isLogicalExpressionNode as M, isMemberExpressionNode as N, isFunctionNode as O, isNodeOfType as P, assertValidAdapterInput as Q, isUndefinedIdentifier as R, isArrowFunctionExpression as S, isCallExpressionNode as T, resolveStaticExpressionValue as U, patternBindsAnyName as V, setIdentifierTypeAnnotation as W, isDirectionalResult as X, defineAdapter as Y, assertValidAdapter as Z, getNodeLocStart as _, cloneAstNode as a, hasNonLiteralLogicalFallback as b, extractRootAndPath as c, getArrowFnSingleParamName as d, getDeclaratorId as f, getMemberPathFromIdentifier as g, getJsxElementName as h, buildStyleFnConditionExpr as i, isIdentifierNode as j, isIdentifierMemberExpression as k, extractStaticLiteralValue as l, getIdentifierMemberPropertyName as m, astNodesEqual as n, collectIdentifiers as o, getFunctionBodyExpr as p, mergeMarkerDeclarations as q, buildEnumValueMap as r, collectPatternBindingNames as s, transformedComponentAcceptsSx as t, getArrowFnParamBindings as u, getRootJsxIdentifierName as v, isAstPath as w, identifierName as x, getSinglePropFromMemberExpr as y, literalToStaticValue as z };