styled-components-to-stylex-codemod 0.0.37 → 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.
@@ -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,8 @@
1
- import { a as Logger, i as resolveBarrelReExport, n as fileImportsFrom, r as findImportSource, t as fileExports } from "./extract-external-interface-BgvS5GC0.mjs";
2
- import { r as escapeRegex } from "./string-utils-ChXtospT.mjs";
3
- import { t as PLACEHOLDER_RE } from "./styled-css-DVtGPEUe.mjs";
4
- import { t as isSelectorContext } from "./selector-context-heuristic-DE3JAmpc.mjs";
1
+ import { a as Logger, i as resolveBarrelReExport, n as fileImportsFrom, r as findImportSource, t as fileExports } from "./extract-external-interface-CdHbvfxu.mjs";
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-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
+ import { t as isSelectorContext } from "./selector-context-heuristic-6_jSRGkZ.mjs";
5
6
  import { relative, resolve } from "node:path";
6
7
  import { readFileSync, realpathSync } from "node:fs";
7
8
  import { execSync } from "node:child_process";
@@ -383,243 +384,6 @@ function deduplicateAndResolve(filesToTransform, consumerPaths) {
383
384
  return result;
384
385
  }
385
386
  //#endregion
386
- //#region src/internal/prepass/compute-leaf-set.ts
387
- /**
388
- * Computes which styled-component bindings are "leaves" for leaves-only mode:
389
- * intrinsic bases (`styled.div`) or transitive wrappers around other leaf styled
390
- * components in the transform set. Uses AST extraction (primary), regex fallback,
391
- * fixed-point + import resolution (same helpers as consumer analysis).
392
- */
393
- const RX_EXPORT_DECL = String.raw`(?:export\s+)?(?:const|let|var)\s+`;
394
- /** `const Name = styled.tag` — intrinsic HTML/SVG tag member. */
395
- const STYLED_INTRINSIC_MEMBER_RE = new RegExp(String.raw`\b${RX_EXPORT_DECL}([A-Z][A-Za-z0-9]*)\b[^=]*=\s*styled\.([a-z][a-zA-Z0-9]*)\b`, "g");
396
- /** `const Name = styled("tag")` — intrinsic string tag. */
397
- const STYLED_INTRINSIC_STRING_RE = new RegExp(String.raw`\b${RX_EXPORT_DECL}([A-Z][A-Za-z0-9]*)\b[^=]*=\s*styled\s*\(\s*["']([^"']+)["']`, "g");
398
- /** `const Name = styled(Component)` — wraps another component identifier. */
399
- const STYLED_COMPONENT_RE = new RegExp(String.raw`\b${RX_EXPORT_DECL}([A-Z][A-Za-z0-9]*)\b[^=]*=\s*styled\s*\(\s*([A-Z][A-Za-z0-9]*)\s*\)`, "g");
400
- /**
401
- * Regex-derived styled definition bases for files in the transform set.
402
- * Later entries for the same component name overwrite earlier ones (rare).
403
- */
404
- function extractStyledDefBasesFromSource(filePath, source, into) {
405
- let map = into.get(filePath);
406
- if (!map) {
407
- map = /* @__PURE__ */ new Map();
408
- into.set(filePath, map);
409
- }
410
- STYLED_INTRINSIC_MEMBER_RE.lastIndex = 0;
411
- for (const m of source.matchAll(STYLED_INTRINSIC_MEMBER_RE)) {
412
- const name = m[1];
413
- if (name) map.set(name, { kind: "intrinsic" });
414
- }
415
- STYLED_INTRINSIC_STRING_RE.lastIndex = 0;
416
- for (const m of source.matchAll(STYLED_INTRINSIC_STRING_RE)) {
417
- const name = m[1];
418
- if (name) map.set(name, { kind: "intrinsic" });
419
- }
420
- STYLED_COMPONENT_RE.lastIndex = 0;
421
- for (const m of source.matchAll(STYLED_COMPONENT_RE)) {
422
- const name = m[1];
423
- const ident = m[2];
424
- if (name && ident) map.set(name, {
425
- kind: "component",
426
- ident
427
- });
428
- }
429
- }
430
- /**
431
- * AST-based extraction: understands `let`/`var`, export blocks, named `styled` imports,
432
- * and `.attrs` / `.withConfig` chains before the tagged template.
433
- * Results merge into `into`; bindings found here override regex entries for the same name.
434
- */
435
- function extractStyledDefBasesFromAstProgram(filePath, program, styledLocalNames, into) {
436
- if (styledLocalNames.size === 0) return;
437
- let map = into.get(filePath);
438
- if (!map) {
439
- map = /* @__PURE__ */ new Map();
440
- into.set(filePath, map);
441
- }
442
- const body = program.body;
443
- if (!body) return;
444
- for (const stmt of body) walkStatement(stmt);
445
- function walkStatement(stmt) {
446
- if (stmt.type === "VariableDeclaration") {
447
- for (const d of stmt.declarations ?? []) processDeclarator(d);
448
- return;
449
- }
450
- if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) walkStatement(stmt.declaration);
451
- }
452
- function processDeclarator(decl) {
453
- if (decl.type !== "VariableDeclarator") return;
454
- const id = decl.id;
455
- if (id.type !== "Identifier" || typeof id.name !== "string") return;
456
- const tpl = findTaggedTemplate(unwrapInitializer(decl.init));
457
- if (!tpl || tpl.type !== "TaggedTemplateExpression") return;
458
- const base = classifyStyledTemplateTag(tpl.tag, styledLocalNames);
459
- if (base) map.set(id.name, base);
460
- }
461
- }
462
- function unwrapInitializer(node) {
463
- let cur = node ?? void 0;
464
- while (cur) {
465
- if (cur.type === "TSAsExpression" || cur.type === "AsExpression") {
466
- cur = cur.expression;
467
- continue;
468
- }
469
- if (cur.type === "ParenthesizedExpression") {
470
- cur = cur.expression;
471
- continue;
472
- }
473
- return cur;
474
- }
475
- }
476
- function findTaggedTemplate(node) {
477
- const n = unwrapInitializer(node);
478
- if (!n) return;
479
- if (n.type === "TaggedTemplateExpression") return n;
480
- }
481
- /** Peel `.attrs` / `.withConfig` / nested calls down to `styled.div` or `styled(X)`. */
482
- function peelStyledApplication(tag, styledNames) {
483
- let cur = tag;
484
- while (cur) {
485
- if (cur.type === "CallExpression") {
486
- const callee = cur.callee;
487
- if (callee?.type === "MemberExpression") {
488
- cur = callee;
489
- continue;
490
- }
491
- if (callee?.type === "Identifier" && typeof callee.name === "string" && styledNames.has(callee.name)) return cur;
492
- return null;
493
- }
494
- if (cur.type === "MemberExpression") {
495
- const obj = cur.object;
496
- if (obj?.type === "Identifier" && typeof obj.name === "string" && styledNames.has(obj.name)) return cur;
497
- cur = obj;
498
- continue;
499
- }
500
- break;
501
- }
502
- return null;
503
- }
504
- function classifyStyledTemplateTag(tag, styledNames) {
505
- const root = peelStyledApplication(tag, styledNames);
506
- if (!root) return null;
507
- if (root.type === "MemberExpression") {
508
- const obj = root.object;
509
- const prop = root.property;
510
- const objName = obj?.type === "Identifier" ? obj.name : void 0;
511
- if (obj?.type !== "Identifier" || typeof objName !== "string" || !styledNames.has(objName)) return null;
512
- const isComputed = Boolean(root.computed);
513
- if (isComputed && prop?.type === "StringLiteral" && typeof prop.value === "string") return { kind: "intrinsic" };
514
- if (!isComputed && prop?.type === "Identifier" && typeof prop.name === "string") return { kind: "intrinsic" };
515
- return null;
516
- }
517
- if (root.type === "CallExpression") {
518
- const callee = root.callee;
519
- const arg0 = root.arguments?.[0];
520
- const calleeName = callee?.type === "Identifier" ? callee.name : void 0;
521
- if (callee?.type !== "Identifier" || typeof calleeName !== "string" || !styledNames.has(calleeName) || !arg0) return null;
522
- if (arg0.type === "Identifier" && typeof arg0.name === "string") return {
523
- kind: "component",
524
- ident: arg0.name
525
- };
526
- if (arg0.type === "StringLiteral" && typeof arg0.value === "string") return { kind: "intrinsic" };
527
- return null;
528
- }
529
- return null;
530
- }
531
- /**
532
- * Fixed-point: a styled binding is a leaf if its base is intrinsic, or its base
533
- * component resolves (same-file or import) to another leaf binding in the transform set.
534
- *
535
- * @param transformSet - Absolute realpaths of files being transformed
536
- * @param styledDefBases - From {@link extractStyledDefBasesFromSource}
537
- * @param resolve - Module path resolver
538
- * @param cachedRead - Read file source for import resolution
539
- */
540
- function computeGlobalLeafKeys(args) {
541
- const { transformSet, styledDefBases, resolve, cachedRead, toRealPath, resolveBaseComponent } = args;
542
- /** fileRealPath → Set of local binding names that are leaves */
543
- const globalLeaves = /* @__PURE__ */ new Map();
544
- const ensureSet = (file) => {
545
- let s = globalLeaves.get(file);
546
- if (!s) {
547
- s = /* @__PURE__ */ new Set();
548
- globalLeaves.set(file, s);
549
- }
550
- return s;
551
- };
552
- const isLeaf = (file, name) => globalLeaves.get(file)?.has(name) ?? false;
553
- const tryResolveImportedLeaf = (file, ident) => {
554
- const importInfo = findImportSource(cachedRead(file), ident);
555
- if (!importInfo) return false;
556
- const initialDefFile = resolve(importInfo.source, file);
557
- if (!initialDefFile) return false;
558
- const defReal = toRealPath(resolveBarrelReExport(initialDefFile, importInfo.isDefault ? "default" : importInfo.exportedName, resolve, cachedRead) ?? initialDefFile);
559
- if (!transformSet.has(defReal)) return false;
560
- return leafKeyExists(defReal, importInfo.exportedName, importInfo.isDefault, cachedRead, globalLeaves);
561
- };
562
- const tryResolveAdapterIntrinsic = (file, ident) => {
563
- if (!resolveBaseComponent) return false;
564
- const importInfo = findImportSource(cachedRead(file), ident);
565
- if (!importInfo) return false;
566
- try {
567
- const result = resolveBaseComponent({
568
- importSource: importInfo.source,
569
- importedName: importInfo.exportedName,
570
- staticProps: {},
571
- filePath: file
572
- });
573
- return typeof result?.tagName === "string" && result.tagName.trim() !== "";
574
- } catch {
575
- return false;
576
- }
577
- };
578
- let changed = true;
579
- while (changed) {
580
- changed = false;
581
- for (const [filePath, nameMap] of styledDefBases) {
582
- const fileReal = toRealPath(filePath);
583
- if (!transformSet.has(fileReal)) continue;
584
- for (const [name, base] of nameMap) {
585
- if (isLeaf(fileReal, name)) continue;
586
- if (base.kind === "intrinsic") {
587
- ensureSet(fileReal).add(name);
588
- changed = true;
589
- continue;
590
- }
591
- const ident = base.ident;
592
- if (isLeaf(fileReal, ident)) {
593
- ensureSet(fileReal).add(name);
594
- changed = true;
595
- continue;
596
- }
597
- if (tryResolveAdapterIntrinsic(filePath, ident)) {
598
- ensureSet(fileReal).add(name);
599
- changed = true;
600
- continue;
601
- }
602
- if (tryResolveImportedLeaf(filePath, ident)) {
603
- ensureSet(fileReal).add(name);
604
- changed = true;
605
- }
606
- }
607
- }
608
- }
609
- const globalLeafKeys = /* @__PURE__ */ new Set();
610
- for (const [file, names] of globalLeaves) for (const name of names) globalLeafKeys.add(`${file}:${name}`);
611
- return globalLeafKeys;
612
- }
613
- function leafKeyExists(defFile, exportedName, allowDefaultFallback, cachedRead, globalLeaves) {
614
- if (globalLeaves.get(defFile)?.has(exportedName) ?? false) return true;
615
- if (!allowDefaultFallback) return false;
616
- const defaultLocalName = findDefaultExportedLocalName(cachedRead(defFile));
617
- return defaultLocalName ? globalLeaves.get(defFile)?.has(defaultLocalName) ?? false : false;
618
- }
619
- function findDefaultExportedLocalName(source) {
620
- return source.match(/\bexport\s+default\s+([A-Z][A-Za-z0-9]*)\b/)?.[1] ?? source.match(/\bexport\s*\{[^}]*\b([A-Z][A-Za-z0-9]*)\s+as\s+default\b[^}]*\}/)?.[1];
621
- }
622
- //#endregion
623
387
  //#region src/internal/prepass/run-prepass.ts
624
388
  /**
625
389
  * Unified prepass: single pass for both cross-file selector scanning
@@ -693,6 +457,7 @@ async function runPrepass(options) {
693
457
  const styleUsages = /* @__PURE__ */ new Map();
694
458
  const elementPropUsages = /* @__PURE__ */ new Map();
695
459
  const spreadPropUsages = /* @__PURE__ */ new Map();
460
+ const propUsageCandidates = /* @__PURE__ */ new Map();
696
461
  const styledWrapperUsages = [];
697
462
  const fileContents = /* @__PURE__ */ new Map();
698
463
  const cachedRead = (filePath) => {
@@ -727,14 +492,16 @@ async function runPrepass(options) {
727
492
  categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, componentsNeedingGlobalSelectorBridge);
728
493
  }
729
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
+ }
730
499
  if (createExternalInterface && hasStyled) {
731
500
  STYLED_CALL_RE.lastIndex = 0;
732
501
  for (const m of source.matchAll(STYLED_CALL_RE)) if (m[1]) styledCallUsages.push({
733
502
  file: filePath,
734
503
  name: m[1]
735
504
  });
736
- STYLED_DEF_RE.lastIndex = 0;
737
- for (const m of source.matchAll(STYLED_DEF_RE)) if (m[1]) addToSetMap(styledDefFiles, filePath, m[1]);
738
505
  STYLED_COMPONENT_WRAPPER_RE.lastIndex = 0;
739
506
  for (const m of source.matchAll(STYLED_COMPONENT_WRAPPER_RE)) if (m[1] && m[2]) styledWrapperUsages.push({
740
507
  file: filePath,
@@ -759,22 +526,37 @@ async function runPrepass(options) {
759
526
  }
760
527
  }
761
528
  const styledFileCount = fileContents.size;
762
- if (createExternalInterface && styledDefFiles.size > 0) {
529
+ if (styledDefFiles.size > 0) {
763
530
  const allStyledNames = /* @__PURE__ */ new Set();
764
531
  for (const names of styledDefFiles.values()) for (const name of names) allStyledNames.add(name);
765
532
  if (allStyledNames.size > 0) {
766
- const rgHits = rgClassNameStyleFilter(uniqueAllFiles);
533
+ const rgHits = createExternalInterface ? rgClassNameStyleFilter(uniqueAllFiles) : void 0;
534
+ const jsxHits = rgJsxComponentFilter(uniqueAllFiles);
767
535
  const scanAndRecord = (filePath, source) => {
768
- for (const result of scanConsumerProps(source, allStyledNames)) {
536
+ if (createExternalInterface) for (const result of scanConsumerProps(source, allStyledNames)) {
769
537
  addToSetMap(classNameStyleUsages, result.name, filePath);
770
538
  if (result.className) addToSetMap(classNameUsages, result.name, filePath);
771
539
  if (result.style) addToSetMap(styleUsages, result.name, filePath);
772
540
  if (result.elementProps) addToSetMap(elementPropUsages, result.name, filePath);
773
541
  if (result.spreadProps) addToSetMap(spreadPropUsages, result.name, filePath);
774
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
+ }
775
548
  };
776
- for (const [filePath, source] of fileContents) scanAndRecord(filePath, source);
777
- 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
+ })();
778
560
  for (const filePath of filesToScan) {
779
561
  const source = cachedRead(filePath);
780
562
  if (!source) continue;
@@ -876,6 +658,13 @@ async function runPrepass(options) {
876
658
  targetPath: defFile
877
659
  });
878
660
  }
661
+ const propUsageByFile = buildPropUsageByFile({
662
+ styledDefFiles,
663
+ propUsageCandidates,
664
+ cachedRead,
665
+ resolve: resolve$1,
666
+ toRealPath
667
+ });
879
668
  let globalLeafKeys;
880
669
  if (leavesOnly) {
881
670
  const styledDefBases = /* @__PURE__ */ new Map();
@@ -893,6 +682,7 @@ async function runPrepass(options) {
893
682
  selectorUsages,
894
683
  componentsNeedingMarkerSidecar,
895
684
  componentsNeedingGlobalSelectorBridge,
685
+ propUsageByFile,
896
686
  styledDefFiles: createExternalInterface ? styledDefFiles : void 0,
897
687
  globalLeafKeys
898
688
  };
@@ -901,7 +691,10 @@ async function runPrepass(options) {
901
691
  const reStyled = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.styles).length : 0;
902
692
  const asProp = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.as).length : 0;
903
693
  const refProp = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.ref).length : 0;
904
- 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`);
905
698
  }
906
699
  if (process.env.DEBUG_CODEMOD) logPrepassDebug(uniqueAllFiles, crossFileInfo, consumerAnalysis);
907
700
  return {
@@ -987,16 +780,6 @@ function buildLocalToImportedMap(source) {
987
780
  }
988
781
  return map;
989
782
  }
990
- /** Props that don't indicate element-specific usage (non-element props). */
991
- const KNOWN_NON_ELEMENT_PROPS = new Set([
992
- "className",
993
- "style",
994
- "as",
995
- "ref",
996
- "forwardedAs",
997
- "key",
998
- "children"
999
- ]);
1000
783
  /**
1001
784
  * Scan source for JSX usage of specific components with className, style,
1002
785
  * element-specific props, or JSX spread.
@@ -1047,6 +830,114 @@ function scanConsumerProps(source, componentNames) {
1047
830
  }
1048
831
  return [...resultMap.values()];
1049
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
+ }
1050
941
  /**
1051
942
  * Use ripgrep to find files containing `className` or `style` props.
1052
943
  * Searches for the prop keywords (not component names) to keep the pattern
@@ -1076,6 +967,31 @@ function rgClassNameStyleFilter(files) {
1076
967
  return;
1077
968
  }
1078
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
+ }
1079
995
  /** Scan a single file for cross-file selector usages using AST parsing. */
1080
996
  function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parser, toRealPath, readFile, cache, failOnParseError) {
1081
997
  const hash = cache ? createHash("md5").update(source).digest("hex") : void 0;
@@ -1123,7 +1039,8 @@ function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parse
1123
1039
  if (!imp || imp.source === "styled-components") continue;
1124
1040
  const resolvedPath = resolver.resolve(filePath, imp.source);
1125
1041
  if (!resolvedPath) continue;
1126
- const realResolved = toRealPath(resolvedPath);
1042
+ const initialResolved = toRealPath(resolvedPath);
1043
+ const realResolved = toRealPath(resolveBarrelReExport(initialResolved, imp.importedName, (specifier, fromFile) => resolver.resolve(fromFile, specifier) ?? null, readFile) ?? initialResolved);
1127
1044
  if (realResolved === filePath) continue;
1128
1045
  const usage = {
1129
1046
  localName,