styled-components-to-stylex-codemod 0.0.38 → 0.0.39

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.
@@ -1,5 +1,5 @@
1
1
  import { n as toRealPath } from "./path-utils-BIpoL4Ue.mjs";
2
- import { r as escapeRegex } from "./string-utils-KggM5TNH.mjs";
2
+ import { r as escapeRegex } from "./string-utils-DD9wdRHW.mjs";
3
3
  import { t as isSelectorContext } from "./selector-context-heuristic-6_jSRGkZ.mjs";
4
4
  import { readFileSync } from "node:fs";
5
5
  //#region src/internal/bridge-consumer-patcher.ts
@@ -102,6 +102,7 @@ function patchConsumerFile(filePath, replacements) {
102
102
  return match;
103
103
  });
104
104
  }
105
+ modified = removeNowUnusedComponentImports(modified, replacements);
105
106
  return modified !== source ? modified : null;
106
107
  }
107
108
  /** Find the end position of an import statement starting at startIdx (handles multi-line imports). */
@@ -119,5 +120,149 @@ function isInStyledTemplateSelectorContext(source, offset, length) {
119
120
  function hasExactImportName(importSpecifiers, name) {
120
121
  return new RegExp(`(?:^|[^A-Za-z0-9_$])${escapeRegex(name)}(?:$|[^A-Za-z0-9_$])`).test(importSpecifiers);
121
122
  }
123
+ function removeNowUnusedComponentImports(source, replacements) {
124
+ let modified = source;
125
+ for (const replacement of replacements) {
126
+ if (hasIdentifierUsageOutsideImports(modified, replacement.localName)) continue;
127
+ modified = removeNamedImportSpecifier(modified, replacement.importSource, replacement.localName);
128
+ }
129
+ return modified;
130
+ }
131
+ function hasIdentifierUsageOutsideImports(source, name) {
132
+ const withoutImports = stripImportDeclarations(stripComments(source));
133
+ return new RegExp(`(^|[^A-Za-z0-9_$])${escapeRegex(name)}($|[^A-Za-z0-9_$])`).test(withoutImports);
134
+ }
135
+ function stripImportDeclarations(source) {
136
+ const lines = source.split(/(?<=\n)/);
137
+ let result = "";
138
+ let inImport = false;
139
+ let nestingDepth = 0;
140
+ for (const line of lines) {
141
+ if (!inImport && !isImportDeclarationStart(line)) {
142
+ result += line;
143
+ continue;
144
+ }
145
+ inImport = true;
146
+ nestingDepth += getImportNestingDelta(line);
147
+ if (nestingDepth <= 0 && isImportDeclarationEnd(line)) {
148
+ inImport = false;
149
+ nestingDepth = 0;
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+ function isImportDeclarationStart(line) {
155
+ const match = line.match(/^[ \t]*import\b/);
156
+ if (!match) return false;
157
+ const next = line.slice(match[0].length).trimStart()[0];
158
+ return next !== "(" && next !== ".";
159
+ }
160
+ function isImportDeclarationEnd(line) {
161
+ const trimmed = line.trim();
162
+ return trimmed.endsWith(";") || /^import\s+["'][^"']+["']$/.test(trimmed) || /\bfrom\s+["'][^"']+["'](?:\s+with\s+\{[^}]*\})?$/.test(trimmed);
163
+ }
164
+ function getImportNestingDelta(line) {
165
+ let depth = 0;
166
+ let quote = null;
167
+ for (let index = 0; index < line.length; index += 1) {
168
+ const char = line[index];
169
+ if (quote) {
170
+ if (char === "\\") index += 1;
171
+ else if (char === quote) quote = null;
172
+ continue;
173
+ }
174
+ if (char === "'" || char === "\"") quote = char;
175
+ else if (char === "{" || char === "(" || char === "[") depth += 1;
176
+ else if (char === "}" || char === ")" || char === "]") depth -= 1;
177
+ }
178
+ return depth;
179
+ }
180
+ function stripComments(source) {
181
+ let result = "";
182
+ let state = "normal";
183
+ for (let index = 0; index < source.length; index += 1) {
184
+ const char = source[index] ?? "";
185
+ const next = source[index + 1] ?? "";
186
+ if (state === "lineComment") {
187
+ if (char === "\n") {
188
+ result += char;
189
+ state = "normal";
190
+ } else result += " ";
191
+ continue;
192
+ }
193
+ if (state === "blockComment") {
194
+ if (char === "*" && next === "/") {
195
+ result += " ";
196
+ index += 1;
197
+ state = "normal";
198
+ } else result += char === "\n" ? "\n" : " ";
199
+ continue;
200
+ }
201
+ if (state === "template") {
202
+ if (char === "/" && next === "/") {
203
+ result += " ";
204
+ index += 1;
205
+ state = "lineComment";
206
+ continue;
207
+ }
208
+ if (char === "/" && next === "*") {
209
+ result += " ";
210
+ index += 1;
211
+ state = "blockComment";
212
+ continue;
213
+ }
214
+ result += char;
215
+ if (char === "\\") {
216
+ result += next;
217
+ index += 1;
218
+ continue;
219
+ }
220
+ if (char === "`") state = "normal";
221
+ continue;
222
+ }
223
+ if (state === "singleQuote" || state === "doubleQuote") {
224
+ result += char;
225
+ if (char === "\\") {
226
+ result += next;
227
+ index += 1;
228
+ continue;
229
+ }
230
+ if (state === "singleQuote" && char === "'" || state === "doubleQuote" && char === "\"") state = "normal";
231
+ continue;
232
+ }
233
+ if (char === "/" && next === "/") {
234
+ result += " ";
235
+ index += 1;
236
+ state = "lineComment";
237
+ continue;
238
+ }
239
+ if (char === "/" && next === "*") {
240
+ result += " ";
241
+ index += 1;
242
+ state = "blockComment";
243
+ continue;
244
+ }
245
+ if (char === "'") state = "singleQuote";
246
+ else if (char === "\"") state = "doubleQuote";
247
+ else if (char === "`") state = "template";
248
+ result += char;
249
+ }
250
+ return result;
251
+ }
252
+ function removeNamedImportSpecifier(source, importSource, localName) {
253
+ const namedImportRegex = new RegExp(`import\\s+([\\w$]+\\s*,\\s*)?\\{([^}]*)\\}\\s+from\\s+(['"]${escapeRegex(importSource)}['"]\\s*;?)`, "g");
254
+ return source.replace(namedImportRegex, (_match, defaultPart, specifierList, fromClause) => {
255
+ const remainingSpecifiers = String(specifierList).split(",").map((specifier) => specifier.trim()).filter(Boolean).filter((specifier) => getImportedSpecifierLocalName(specifier) !== localName);
256
+ if (remainingSpecifiers.length > 0) return `import ${defaultPart ?? ""}{ ${remainingSpecifiers.join(", ")} } from ${fromClause}`;
257
+ const defaultName = String(defaultPart ?? "").replace(/,\s*$/, "").trim();
258
+ if (defaultName) return `import ${defaultName} from ${fromClause}`;
259
+ return "";
260
+ });
261
+ }
262
+ function getImportedSpecifierLocalName(specifier) {
263
+ const aliasMatch = specifier.match(/\bas\s+([A-Za-z_$][\w$]*)$/);
264
+ if (aliasMatch?.[1]) return aliasMatch[1];
265
+ return specifier.trim();
266
+ }
122
267
  //#endregion
123
268
  export { buildConsumerReplacements, patchConsumerFile };
@@ -1,5 +1,5 @@
1
1
  import { n as toRealPath } from "./path-utils-BIpoL4Ue.mjs";
2
- import { r as escapeRegex } from "./string-utils-KggM5TNH.mjs";
2
+ import { r as escapeRegex } from "./string-utils-DD9wdRHW.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  //#region src/internal/forwarded-as-consumer-patcher.ts
5
5
  /**
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as CollectedWarning, c as MarkerFileContext, l as defineAdapter, n as TransformMode, o as AdapterInput, s as ImportSource } from "./transform-types--9qCqNSJ.mjs";
1
+ import { a as CollectedWarning, c as MarkerFileContext, l as defineAdapter, n as TransformMode, o as AdapterInput, s as ImportSource } from "./transform-types-DJpFQ5xm.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
package/dist/index.mjs CHANGED
@@ -149,7 +149,7 @@ async function runTransform(options) {
149
149
  const { createModuleResolver } = await import("./resolve-imports-BlxKezSJ.mjs").then((n) => n.n);
150
150
  const sharedResolver = createModuleResolver();
151
151
  filePaths = orderFilesByLocalImportDependencies(filePaths, sharedResolver, toRealPath);
152
- const { runPrepass } = await import("./run-prepass-Us5SBTib.mjs");
152
+ const { runPrepass } = await import("./run-prepass-qEr_Mc3y.mjs");
153
153
  const absoluteFiles = filePaths.map((f) => resolve(f));
154
154
  const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
155
155
  let prepassResult;
@@ -172,6 +172,7 @@ async function runTransform(options) {
172
172
  selectorUsages: /* @__PURE__ */ new Map(),
173
173
  componentsNeedingMarkerSidecar: /* @__PURE__ */ new Map(),
174
174
  componentsNeedingGlobalSelectorBridge: /* @__PURE__ */ new Map(),
175
+ propUsageByFile: /* @__PURE__ */ new Map(),
175
176
  globalLeafKeys: leavesOnly ? /* @__PURE__ */ new Set() : void 0
176
177
  },
177
178
  consumerAnalysis: void 0,
@@ -308,7 +309,7 @@ async function runTransform(options) {
308
309
  const result = await runTransformSequentially(transformModule, filePaths, runnerOptions);
309
310
  if (sidecarFiles.size > 0 && !dryRun) for (const [sidecarPath, content] of sidecarFiles) await writeFile(sidecarPath, mergeSidecarContent(sidecarPath, content), "utf-8");
310
311
  if (bridgeResults.size > 0 && !dryRun) {
311
- const { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-BzAIO9pC.mjs");
312
+ const { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-31jI1854.mjs");
312
313
  const consumerReplacements = buildConsumerReplacements(crossFilePrepassResult.selectorUsages, bridgeResults, transformedFiles);
313
314
  const patchedFiles = [];
314
315
  for (const [consumerPath, replacements] of consumerReplacements) {
@@ -321,7 +322,7 @@ async function runTransform(options) {
321
322
  if (formatterCommands && patchedFiles.length > 0) await runFormatters(formatterCommands, patchedFiles);
322
323
  }
323
324
  if (prepassResult.forwardedAsConsumers.size > 0 && !dryRun) {
324
- const { buildForwardedAsReplacements, patchConsumerForwardedAs } = await import("./forwarded-as-consumer-patcher-Cs0X-olz.mjs");
325
+ const { buildForwardedAsReplacements, patchConsumerForwardedAs } = await import("./forwarded-as-consumer-patcher-BYCrqzRm.mjs");
325
326
  const forwardedAsReplacements = buildForwardedAsReplacements(prepassResult.forwardedAsConsumers, transformedFiles);
326
327
  const patchedFiles = [];
327
328
  for (const [consumerPath, entries] of forwardedAsReplacements) {
@@ -334,7 +335,7 @@ async function runTransform(options) {
334
335
  if (formatterCommands && patchedFiles.length > 0) await runFormatters(formatterCommands, patchedFiles);
335
336
  }
336
337
  if (transientPropRenames.size > 0 && !dryRun) {
337
- const { collectTransientPropPatches } = await import("./transient-prop-consumer-patcher-DLsKxg1R.mjs");
338
+ const { collectTransientPropPatches } = await import("./transient-prop-consumer-patcher-DdIYPSFk.mjs");
338
339
  const patches = collectTransientPropPatches({
339
340
  transientPropRenames,
340
341
  consumerFilePaths: consumerFilePaths.map((p) => resolve(p)),
@@ -0,0 +1,136 @@
1
+ import { compile } from "stylis";
2
+ //#region src/internal/styled-css.ts
3
+ /** Matches `__SC_EXPR_N__` and captures the slot index in group 1. */
4
+ const PLACEHOLDER_RE = /__SC_EXPR_(\d+)__/;
5
+ function parseStyledTemplateLiteral(template) {
6
+ const parts = [];
7
+ const slots = [];
8
+ for (let i = 0; i < template.quasis.length; i++) {
9
+ const quasi = template.quasis[i];
10
+ parts.push(quasi.value.raw);
11
+ const expr = template.expressions[i];
12
+ if (!expr) continue;
13
+ const placeholder = makeInterpolationPlaceholder(i);
14
+ const startOffset = parts.join("").length;
15
+ parts.push(placeholder);
16
+ const endOffset = parts.join("").length;
17
+ slots.push({
18
+ index: i,
19
+ placeholder,
20
+ expression: expr,
21
+ startOffset,
22
+ endOffset
23
+ });
24
+ }
25
+ const rawCss = parts.join("");
26
+ return {
27
+ rawCss,
28
+ slots,
29
+ stylisAst: compile(terminateStandaloneInterpolationStatements(rawCss))
30
+ };
31
+ }
32
+ function terminateStandaloneInterpolationStatements(css) {
33
+ let parenDepth = 0;
34
+ const lines = css.split(/(?<=\n)/);
35
+ const depthsBeforeLine = [];
36
+ for (const line of lines) {
37
+ depthsBeforeLine.push(parenDepth);
38
+ parenDepth = updateParenDepth(parenDepth, line);
39
+ }
40
+ return lines.map((line, index) => {
41
+ return depthsBeforeLine[index] === 0 && /^\s*__SC_EXPR_\d+__\s*$/.test(line) && isBeforeAtRule(lines, index) ? line.replace(/(\s*)$/, ";$1") : line;
42
+ }).join("");
43
+ }
44
+ function makeInterpolationPlaceholder(index) {
45
+ return `__SC_EXPR_${index}__`;
46
+ }
47
+ function isBeforeAtRule(lines, startIndex) {
48
+ for (let i = startIndex + 1; i < lines.length; i++) {
49
+ const trimmed = lines[i].trim();
50
+ if (!trimmed || /^__SC_EXPR_\d+__\s*;?$/.test(trimmed)) continue;
51
+ return trimmed.startsWith("@");
52
+ }
53
+ return false;
54
+ }
55
+ function updateParenDepth(startDepth, line) {
56
+ let depth = startDepth;
57
+ let inString = false;
58
+ for (let i = 0; i < line.length; i++) {
59
+ const ch = line[i];
60
+ if ((ch === "\"" || ch === "'") && line[i - 1] !== "\\") {
61
+ if (!inString) inString = ch;
62
+ else if (inString === ch) inString = false;
63
+ continue;
64
+ }
65
+ if (inString) continue;
66
+ if (ch === "(") depth++;
67
+ else if (ch === ")") depth = Math.max(0, depth - 1);
68
+ }
69
+ return depth;
70
+ }
71
+ //#endregion
72
+ //#region src/internal/utilities/jsx-static-literal.ts
73
+ function readStaticJsxLiteral(attr) {
74
+ if (!isObjectRecord(attr) || attr.type !== "JSXAttribute") return;
75
+ if (!("value" in attr) || attr.value == null) return true;
76
+ const directLiteral = readLiteralNodeValue(attr.value);
77
+ if (directLiteral !== void 0) return directLiteral;
78
+ if (!isObjectRecord(attr.value) || attr.value.type !== "JSXExpressionContainer") return;
79
+ return readLiteralNodeValue(attr.value.expression);
80
+ }
81
+ function readLiteralNodeValue(node) {
82
+ if (!isObjectRecord(node)) return;
83
+ if (node.type === "StringLiteral" || node.type === "NumericLiteral" || node.type === "BooleanLiteral") return isStaticLiteral(node.value) ? node.value : void 0;
84
+ if (node.type === "Literal") return isStaticLiteral(node.value) ? node.value : void 0;
85
+ if (node.type === "UnaryExpression" && node.operator === "-") {
86
+ const value = readLiteralNodeValue(node.argument);
87
+ return typeof value === "number" ? -value : void 0;
88
+ }
89
+ }
90
+ function isObjectRecord(value) {
91
+ return typeof value === "object" && value !== null;
92
+ }
93
+ function isStaticLiteral(value) {
94
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
95
+ }
96
+ //#endregion
97
+ //#region src/internal/utilities/prop-usage.ts
98
+ const KNOWN_NON_ELEMENT_PROPS = new Set([
99
+ "className",
100
+ "style",
101
+ "as",
102
+ "ref",
103
+ "forwardedAs",
104
+ "key",
105
+ "children"
106
+ ]);
107
+ function createComponentPropUsageInfo(name) {
108
+ return {
109
+ componentName: name,
110
+ usageCount: 0,
111
+ hasUnknownUsage: false,
112
+ props: {}
113
+ };
114
+ }
115
+ function mergeComponentPropUsage(info, usage) {
116
+ info.usageCount += 1;
117
+ if (usage.hasSpread) info.hasUnknownUsage = true;
118
+ const presentProps = new Set(Object.keys(usage.props));
119
+ for (const [propName, propInfo] of Object.entries(info.props)) if (!presentProps.has(propName)) propInfo.omittedCount += 1;
120
+ for (const [propName, value] of Object.entries(usage.props)) {
121
+ const propInfo = info.props[propName] ?? (info.props[propName] = {
122
+ values: [],
123
+ hasUnknown: false,
124
+ usageCount: 0,
125
+ omittedCount: info.usageCount - 1
126
+ });
127
+ propInfo.usageCount += 1;
128
+ if (value.kind === "unknown") {
129
+ propInfo.hasUnknown = true;
130
+ continue;
131
+ }
132
+ if (!propInfo.values.some((existing) => existing === value.value)) propInfo.values.push(value.value);
133
+ }
134
+ }
135
+ //#endregion
136
+ export { PLACEHOLDER_RE as a, readStaticJsxLiteral as i, createComponentPropUsageInfo as n, parseStyledTemplateLiteral as o, mergeComponentPropUsage as r, terminateStandaloneInterpolationStatements as s, KNOWN_NON_ELEMENT_PROPS as t };
@@ -1,7 +1,7 @@
1
1
  import { a as Logger, i as resolveBarrelReExport, n as fileImportsFrom, r as findImportSource, t as fileExports } from "./extract-external-interface-CdHbvfxu.mjs";
2
2
  import { n as extractStyledDefBasesFromAstProgram, r as extractStyledDefBasesFromSource, t as computeGlobalLeafKeys } from "./compute-leaf-set-Drcu2eju.mjs";
3
- import { r as escapeRegex } from "./string-utils-KggM5TNH.mjs";
4
- import { t as PLACEHOLDER_RE } from "./styled-css-BVR82jN5.mjs";
3
+ import { r as escapeRegex } from "./string-utils-DD9wdRHW.mjs";
4
+ import { a as PLACEHOLDER_RE, i as readStaticJsxLiteral, n as createComponentPropUsageInfo, r as mergeComponentPropUsage, t as KNOWN_NON_ELEMENT_PROPS } from "./prop-usage-D6ZiDfzz.mjs";
5
5
  import { t as isSelectorContext } from "./selector-context-heuristic-6_jSRGkZ.mjs";
6
6
  import { relative, resolve } from "node:path";
7
7
  import { readFileSync, realpathSync } from "node:fs";
@@ -457,6 +457,7 @@ async function runPrepass(options) {
457
457
  const styleUsages = /* @__PURE__ */ new Map();
458
458
  const elementPropUsages = /* @__PURE__ */ new Map();
459
459
  const spreadPropUsages = /* @__PURE__ */ new Map();
460
+ const propUsageCandidates = /* @__PURE__ */ new Map();
460
461
  const styledWrapperUsages = [];
461
462
  const fileContents = /* @__PURE__ */ new Map();
462
463
  const cachedRead = (filePath) => {
@@ -491,14 +492,16 @@ async function runPrepass(options) {
491
492
  categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, componentsNeedingGlobalSelectorBridge);
492
493
  }
493
494
  }
495
+ if (hasStyled) {
496
+ STYLED_DEF_RE.lastIndex = 0;
497
+ for (const m of source.matchAll(STYLED_DEF_RE)) if (m[1]) addToSetMap(styledDefFiles, filePath, m[1]);
498
+ }
494
499
  if (createExternalInterface && hasStyled) {
495
500
  STYLED_CALL_RE.lastIndex = 0;
496
501
  for (const m of source.matchAll(STYLED_CALL_RE)) if (m[1]) styledCallUsages.push({
497
502
  file: filePath,
498
503
  name: m[1]
499
504
  });
500
- STYLED_DEF_RE.lastIndex = 0;
501
- for (const m of source.matchAll(STYLED_DEF_RE)) if (m[1]) addToSetMap(styledDefFiles, filePath, m[1]);
502
505
  STYLED_COMPONENT_WRAPPER_RE.lastIndex = 0;
503
506
  for (const m of source.matchAll(STYLED_COMPONENT_WRAPPER_RE)) if (m[1] && m[2]) styledWrapperUsages.push({
504
507
  file: filePath,
@@ -523,22 +526,37 @@ async function runPrepass(options) {
523
526
  }
524
527
  }
525
528
  const styledFileCount = fileContents.size;
526
- if (createExternalInterface && styledDefFiles.size > 0) {
529
+ if (styledDefFiles.size > 0) {
527
530
  const allStyledNames = /* @__PURE__ */ new Set();
528
531
  for (const names of styledDefFiles.values()) for (const name of names) allStyledNames.add(name);
529
532
  if (allStyledNames.size > 0) {
530
- const rgHits = rgClassNameStyleFilter(uniqueAllFiles);
533
+ const rgHits = createExternalInterface ? rgClassNameStyleFilter(uniqueAllFiles) : void 0;
534
+ const jsxHits = rgJsxComponentFilter(uniqueAllFiles);
531
535
  const scanAndRecord = (filePath, source) => {
532
- for (const result of scanConsumerProps(source, allStyledNames)) {
536
+ if (createExternalInterface) for (const result of scanConsumerProps(source, allStyledNames)) {
533
537
  addToSetMap(classNameStyleUsages, result.name, filePath);
534
538
  if (result.className) addToSetMap(classNameUsages, result.name, filePath);
535
539
  if (result.style) addToSetMap(styleUsages, result.name, filePath);
536
540
  if (result.elementProps) addToSetMap(elementPropUsages, result.name, filePath);
537
541
  if (result.spreadProps) addToSetMap(spreadPropUsages, result.name, filePath);
538
542
  }
543
+ for (const usage of scanConsumerStaticPropUsages(filePath, source, allStyledNames, parser)) {
544
+ const entries = propUsageCandidates.get(usage.name) ?? [];
545
+ entries.push(usage);
546
+ propUsageCandidates.set(usage.name, entries);
547
+ }
539
548
  };
540
- for (const [filePath, source] of fileContents) scanAndRecord(filePath, source);
541
- const filesToScan = rgHits ? [...rgHits].filter((f) => allFilesSet.has(f) && !fileContents.has(f)) : uniqueAllFiles.filter((f) => !fileContents.has(f));
549
+ for (const [filePath, source] of fileContents) {
550
+ if (jsxHits && !jsxHits.has(filePath) && (!createExternalInterface || !rgHits?.has(filePath))) continue;
551
+ scanAndRecord(filePath, source);
552
+ }
553
+ const filesToScan = (() => {
554
+ const hits = /* @__PURE__ */ new Set();
555
+ const hasAnyFilter = createExternalInterface && rgHits !== void 0 || jsxHits !== void 0;
556
+ if (createExternalInterface && rgHits) for (const f of rgHits) hits.add(f);
557
+ if (jsxHits) for (const f of jsxHits) hits.add(f);
558
+ return hasAnyFilter ? [...hits].filter((f) => allFilesSet.has(f) && !fileContents.has(f)) : uniqueAllFiles.filter((f) => !fileContents.has(f));
559
+ })();
542
560
  for (const filePath of filesToScan) {
543
561
  const source = cachedRead(filePath);
544
562
  if (!source) continue;
@@ -640,6 +658,13 @@ async function runPrepass(options) {
640
658
  targetPath: defFile
641
659
  });
642
660
  }
661
+ const propUsageByFile = buildPropUsageByFile({
662
+ styledDefFiles,
663
+ propUsageCandidates,
664
+ cachedRead,
665
+ resolve: resolve$1,
666
+ toRealPath
667
+ });
643
668
  let globalLeafKeys;
644
669
  if (leavesOnly) {
645
670
  const styledDefBases = /* @__PURE__ */ new Map();
@@ -657,6 +682,7 @@ async function runPrepass(options) {
657
682
  selectorUsages,
658
683
  componentsNeedingMarkerSidecar,
659
684
  componentsNeedingGlobalSelectorBridge,
685
+ propUsageByFile,
660
686
  styledDefFiles: createExternalInterface ? styledDefFiles : void 0,
661
687
  globalLeafKeys
662
688
  };
@@ -665,7 +691,10 @@ async function runPrepass(options) {
665
691
  const reStyled = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.styles).length : 0;
666
692
  const asProp = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.as).length : 0;
667
693
  const refProp = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.ref).length : 0;
668
- Logger.info(`Prepass: scanned ${uniqueAllFiles.length} files in ${elapsed}s — ${styledFileCount} with styled-components, ${selectorUsages.size} cross-file selectors, ${reStyled} re-styled, ${asProp} as-prop, ${refProp} ref-prop, ${classNameStyleUsages.size} className/style, ${forwardedAsConsumers.size} forwardedAs\n`);
694
+ const propUsageCount = [...propUsageByFile.values()].reduce((sum, byComponent) => {
695
+ return sum + byComponent.size;
696
+ }, 0);
697
+ Logger.info(`Prepass: scanned ${uniqueAllFiles.length} files in ${elapsed}s — ${styledFileCount} with styled-components, ${selectorUsages.size} cross-file selectors, ${reStyled} re-styled, ${asProp} as-prop, ${refProp} ref-prop, ${classNameStyleUsages.size} className/style, ${propUsageCount} prop-usage, ${forwardedAsConsumers.size} forwardedAs\n`);
669
698
  }
670
699
  if (process.env.DEBUG_CODEMOD) logPrepassDebug(uniqueAllFiles, crossFileInfo, consumerAnalysis);
671
700
  return {
@@ -751,16 +780,6 @@ function buildLocalToImportedMap(source) {
751
780
  }
752
781
  return map;
753
782
  }
754
- /** Props that don't indicate element-specific usage (non-element props). */
755
- const KNOWN_NON_ELEMENT_PROPS = new Set([
756
- "className",
757
- "style",
758
- "as",
759
- "ref",
760
- "forwardedAs",
761
- "key",
762
- "children"
763
- ]);
764
783
  /**
765
784
  * Scan source for JSX usage of specific components with className, style,
766
785
  * element-specific props, or JSX spread.
@@ -811,6 +830,114 @@ function scanConsumerProps(source, componentNames) {
811
830
  }
812
831
  return [...resultMap.values()];
813
832
  }
833
+ function scanConsumerStaticPropUsages(filePath, source, componentNames, parser) {
834
+ if (!/<[A-Z]/.test(source)) return [];
835
+ let ast;
836
+ try {
837
+ ast = parser.parse(source);
838
+ } catch {
839
+ return [];
840
+ }
841
+ const importNodes = [];
842
+ const jsxOpenings = [];
843
+ walkForImportsAndJsxOpenings(ast.program ?? ast, importNodes, jsxOpenings);
844
+ const importMap = buildImportMapFromNodes(importNodes);
845
+ const usages = [];
846
+ for (const opening of jsxOpenings) {
847
+ const tagName = getJsxOpeningIdentifierName(opening.name);
848
+ if (!tagName) continue;
849
+ const importEntry = importMap.get(tagName);
850
+ const resolvedName = componentNames.has(tagName) ? tagName : importEntry && componentNames.has(importEntry.importedName) ? importEntry.importedName : void 0;
851
+ if (!resolvedName) continue;
852
+ const props = {};
853
+ let hasSpread = false;
854
+ for (const attr of opening.attributes ?? []) {
855
+ if (!attr) continue;
856
+ if (attr.type === "JSXSpreadAttribute") {
857
+ hasSpread = true;
858
+ continue;
859
+ }
860
+ if (attr.type !== "JSXAttribute") continue;
861
+ const propName = getJsxAttributeName(attr.name);
862
+ if (!propName || KNOWN_NON_ELEMENT_PROPS.has(propName)) continue;
863
+ const value = readStaticJsxLiteral(attr);
864
+ props[propName] = value === void 0 ? { kind: "unknown" } : {
865
+ kind: "static",
866
+ value
867
+ };
868
+ }
869
+ usages.push({
870
+ name: resolvedName,
871
+ filePath,
872
+ usage: {
873
+ props,
874
+ hasSpread
875
+ }
876
+ });
877
+ }
878
+ return usages;
879
+ }
880
+ function buildPropUsageByFile(args) {
881
+ const { styledDefFiles, propUsageCandidates, cachedRead, resolve, toRealPath } = args;
882
+ const propUsageByFile = /* @__PURE__ */ new Map();
883
+ for (const [defFile, names] of styledDefFiles) {
884
+ const defSrc = cachedRead(defFile);
885
+ for (const name of names) {
886
+ const candidates = propUsageCandidates.get(name);
887
+ if (!candidates || !fileExports(defSrc, name)) continue;
888
+ for (const candidate of candidates) {
889
+ const usageFile = candidate.filePath;
890
+ if (usageFile !== defFile && !fileImportsFrom(cachedRead(usageFile), usageFile, name, defFile, resolve)) continue;
891
+ mergeComponentPropUsage(getOrCreateComponentPropUsage(getOrCreatePropUsageFileMap(propUsageByFile, toRealPath(defFile)), name), candidate.usage);
892
+ }
893
+ }
894
+ }
895
+ return propUsageByFile;
896
+ }
897
+ function getOrCreatePropUsageFileMap(propUsageByFile, filePath) {
898
+ let byComponent = propUsageByFile.get(filePath);
899
+ if (!byComponent) {
900
+ byComponent = /* @__PURE__ */ new Map();
901
+ propUsageByFile.set(filePath, byComponent);
902
+ }
903
+ return byComponent;
904
+ }
905
+ function getOrCreateComponentPropUsage(byComponent, name) {
906
+ let info = byComponent.get(name);
907
+ if (!info) {
908
+ info = createComponentPropUsageInfo(name);
909
+ byComponent.set(name, info);
910
+ }
911
+ return info;
912
+ }
913
+ function walkForImportsAndJsxOpenings(node, imports, jsxOpenings) {
914
+ if (!node || typeof node !== "object") return;
915
+ const n = node;
916
+ if (n.type === "ImportDeclaration") {
917
+ imports.push(n);
918
+ return;
919
+ }
920
+ if (n.type === "JSXOpeningElement") {
921
+ jsxOpenings.push(n);
922
+ return;
923
+ }
924
+ for (const key of Object.keys(n)) {
925
+ if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
926
+ const val = n[key];
927
+ if (Array.isArray(val)) for (const child of val) walkForImportsAndJsxOpenings(child, imports, jsxOpenings);
928
+ else if (val && typeof val === "object" && val.type) walkForImportsAndJsxOpenings(val, imports, jsxOpenings);
929
+ }
930
+ }
931
+ function getJsxOpeningIdentifierName(name) {
932
+ if (!name) return null;
933
+ if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
934
+ return null;
935
+ }
936
+ function getJsxAttributeName(name) {
937
+ if (!name) return null;
938
+ if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
939
+ return null;
940
+ }
814
941
  /**
815
942
  * Use ripgrep to find files containing `className` or `style` props.
816
943
  * Searches for the prop keywords (not component names) to keep the pattern
@@ -840,6 +967,31 @@ function rgClassNameStyleFilter(files) {
840
967
  return;
841
968
  }
842
969
  }
970
+ /** Use ripgrep to find files with PascalCase JSX tags. */
971
+ function rgJsxComponentFilter(files) {
972
+ const dirs = deduplicateParentDirs(files);
973
+ if (dirs.length === 0) return;
974
+ try {
975
+ const globArgs = [
976
+ "*.tsx",
977
+ "*.jsx",
978
+ "*.ts",
979
+ "*.js",
980
+ "*.mts",
981
+ "*.cts",
982
+ "*.mjs",
983
+ "*.cjs"
984
+ ].map((glob) => `--glob ${shellQuote(glob)}`).join(" ");
985
+ const output = execSync(`rg -l ${shellQuote(String.raw`<[A-Z]`)} ${globArgs} ${dirs.map(shellQuote).join(" ")}`, {
986
+ encoding: "utf-8",
987
+ maxBuffer: 10 * 1024 * 1024
988
+ });
989
+ return new Set(output.trim().split("\n").filter(Boolean).map((f) => resolve(f)));
990
+ } catch (err) {
991
+ if (err instanceof Error && "status" in err && err.status === 1) return /* @__PURE__ */ new Set();
992
+ return;
993
+ }
994
+ }
843
995
  /** Scan a single file for cross-file selector usages using AST parsing. */
844
996
  function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parser, toRealPath, readFile, cache, failOnParseError) {
845
997
  const hash = cache ? createHash("md5").update(source).digest("hex") : void 0;
@@ -104,6 +104,21 @@ function isPrettierIgnoreComment(body) {
104
104
  return /^\s*prettier-ignore\s*$/.test(body);
105
105
  }
106
106
  /**
107
+ * Returns true if a comment body is only a visual marker for a styles section.
108
+ * These markers describe the old styled-components grouping, not emitted StyleX code.
109
+ */
110
+ function isStyleSectionMarkerComment(body) {
111
+ const marker = body.trim().replace(/^[-\s]+/, "").replace(/[-\s]+$/, "").trim().toLowerCase();
112
+ return /^(?:styled\s*-?\s*components?|styles?)$/.test(marker);
113
+ }
114
+ function getCommentBody(comment) {
115
+ const c = comment && typeof comment === "object" ? comment : {};
116
+ return typeof c.value === "string" ? c.value : "";
117
+ }
118
+ function isJSDocBlockComment(comment) {
119
+ return (comment && typeof comment === "object" ? comment : {}).type === "CommentBlock" && getCommentBody(comment).trimStart().startsWith("*");
120
+ }
121
+ /**
107
122
  * Normalizes whitespace in a CSS value string.
108
123
  * Collapses all sequences of whitespace (including newlines) to single spaces
109
124
  * and trims leading/trailing whitespace.
@@ -125,4 +140,4 @@ function hasTopLevelMatch(value, pattern) {
125
140
  return false;
126
141
  }
127
142
  //#endregion
128
- export { isPrettierIgnoreComment as a, kebabToCamelCase as c, normalizeWhitespace as d, sanitizeIdentifier as f, isBackgroundImageValue as i, looksLikeLength as l, capitalize as n, isSingleBackgroundComponent as o, escapeRegex as r, isValidIdentifierName as s, camelToKebabCase as t, lowerFirst as u };
143
+ export { isBackgroundImageValue as a, isSingleBackgroundComponent as c, kebabToCamelCase as d, looksLikeLength as f, sanitizeIdentifier as h, getCommentBody as i, isStyleSectionMarkerComment as l, normalizeWhitespace as m, capitalize as n, isJSDocBlockComment as o, lowerFirst as p, escapeRegex as r, isPrettierIgnoreComment as s, camelToKebabCase as t, isValidIdentifierName as u };