styled-components-to-stylex-codemod 0.0.54 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/{ast-walk-CCXrDCKY.mjs → ast-walk-CLvMH7Lm.mjs} +313 -8
- package/dist/{bridge-consumer-patcher-DDcYZM_G.mjs → bridge-consumer-patcher-B__X3jOg.mjs} +3 -3
- package/dist/{compute-leaf-set-u51kTofH.mjs → compute-leaf-set-D5GvkV-H.mjs} +18 -5
- package/dist/{forwarded-as-consumer-patcher-Bva_36Gy.mjs → forwarded-as-consumer-patcher-Bs9ymhBa.mjs} +2 -2
- package/dist/index.d.mts +91 -2
- package/dist/index.mjs +454 -22
- package/dist/{prop-usage-DyWABApg.mjs → prop-usage-z-bcXTOD.mjs} +2 -283
- package/dist/{run-prepass-DRjZWWHF.mjs → run-prepass-BueJvYyf.mjs} +7 -14
- package/dist/sx-surface-_Hjc6ZDq.mjs +1280 -0
- package/dist/transform.mjs +36985 -37139
- package/dist/{transient-prop-consumer-patcher-DSd7uVA6.mjs → transient-prop-consumer-patcher-BDruM1OI.mjs} +2 -2
- package/dist/{typescript-analysis-CmIDxfLv.mjs → typescript-analysis-BzsnorIV.mjs} +476 -468
- package/package.json +1 -1
- package/dist/sx-surface-CEPFSTO1.mjs +0 -498
- /package/dist/{path-utils-BC4U8X_q.mjs → path-utils-ByFNVtHo.mjs} +0 -0
- /package/dist/{selector-context-heuristic-LVizWWOR.mjs → selector-context-heuristic-Dptd93Xe.mjs} +0 -0
- /package/dist/{string-utils-Bo3cWgss.mjs → string-utils-4eeXGa48.mjs} +0 -0
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ The adapter maps your project's `props.theme.*` access, CSS variables, and helpe
|
|
|
37
37
|
|
|
38
38
|
When a component wraps another component that internally uses styled-components (e.g. `styled(GroupHeader)` where `GroupHeader` renders a `StyledHeader`), CSS cascade conflicts can arise after migration. Convert leaf files — the ones that don't wrap other styled-components — first, then work your way up. The codemod will bail with a warning if it detects this pattern.
|
|
39
39
|
|
|
40
|
+
Run [`analyzeMigrationPlan`](#planning-manual-conversions) to get the ordered, bottom-up list of files to convert by hand first.
|
|
41
|
+
|
|
40
42
|
### 4. Verify, iterate, clean up
|
|
41
43
|
|
|
42
44
|
Build and test your project. Review warnings — they tell you which files were skipped and why. Fix adapter gaps, re-run on remaining files, and repeat until done. [Report issues](https://github.com/skovhus/styled-components-to-stylex-codemod/issues) with input/output examples if the codemod produces incorrect results.
|
|
@@ -380,6 +382,24 @@ await runTransform({
|
|
|
380
382
|
|
|
381
383
|
</details>
|
|
382
384
|
|
|
385
|
+
### Planning manual conversions
|
|
386
|
+
|
|
387
|
+
`analyzeMigrationPlan` runs the codemod in analysis-only mode (it never writes files) and returns the bottom-up ordered list of files you must convert by hand — the genuine blockers the codemod can't convert — each with its consumer count, the exports to convert, how many files it unblocks, and the bail reasons. `formatMigrationPlan` renders it as a report (high-impact files first).
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
import { analyzeMigrationPlan, formatMigrationPlan } from "styled-components-to-stylex-codemod";
|
|
391
|
+
|
|
392
|
+
const plan = await analyzeMigrationPlan({
|
|
393
|
+
files: "src/**/*.tsx",
|
|
394
|
+
consumerPaths: "src/**/*.tsx",
|
|
395
|
+
adapter, // the same adapter you pass to runTransform
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
console.log(formatMigrationPlan(plan));
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Try it against this repo's own fixtures with `node scripts/migration-plan.mts`.
|
|
402
|
+
|
|
383
403
|
### Adapter
|
|
384
404
|
|
|
385
405
|
Adapters are the main extension point, see full example above. They let you control:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { n as isTemplatePlaceholderInSelectorContext, r as PLACEHOLDER_RE } from "./selector-context-heuristic-Dptd93Xe.mjs";
|
|
2
|
+
import path, { resolve } from "node:path";
|
|
2
3
|
import { readFileSync } from "node:fs";
|
|
3
4
|
import { parse } from "@babel/parser";
|
|
4
5
|
//#region src/internal/logger.ts
|
|
@@ -89,6 +90,14 @@ var Logger = class Logger {
|
|
|
89
90
|
static createReport() {
|
|
90
91
|
return new LoggerReport([...Logger.collected], Logger.fileCount, Logger.maxExamples);
|
|
91
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Restore the collected warnings to a previous snapshot (from
|
|
95
|
+
* `createReport().getWarnings()`). Used to undo the side effects of an
|
|
96
|
+
* analysis-only dry run on the process-global logger.
|
|
97
|
+
*/
|
|
98
|
+
static restoreWarnings(warnings) {
|
|
99
|
+
Logger.collected = [...warnings];
|
|
100
|
+
}
|
|
92
101
|
/** @internal - for testing only */
|
|
93
102
|
static _clearCollected() {
|
|
94
103
|
Logger.collected = [];
|
|
@@ -273,14 +282,16 @@ var LoggerReport = class {
|
|
|
273
282
|
}
|
|
274
283
|
}
|
|
275
284
|
};
|
|
285
|
+
/**
|
|
286
|
+
* Extract the depended-on file path from a cascade-conflict warning's context
|
|
287
|
+
* (the base component's defining file that must be converted first). Owned here
|
|
288
|
+
* alongside the warning-type definition so consumers share one invariant.
|
|
289
|
+
*/
|
|
276
290
|
function getCascadeDependedFilePath(warning) {
|
|
277
291
|
const context = warning.context;
|
|
278
|
-
if (!context
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (typeof definitionPath === "string") return definitionPath;
|
|
282
|
-
const importedPath = record.importedPath;
|
|
283
|
-
return typeof importedPath === "string" ? importedPath : void 0;
|
|
292
|
+
if (!context) return;
|
|
293
|
+
if (typeof context.definitionPath === "string") return context.definitionPath;
|
|
294
|
+
return typeof context.importedPath === "string" ? context.importedPath : void 0;
|
|
284
295
|
}
|
|
285
296
|
function uniqueSorted(values) {
|
|
286
297
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
@@ -522,6 +533,300 @@ const FLOW_PLUGINS = [
|
|
|
522
533
|
"throwExpressions"
|
|
523
534
|
];
|
|
524
535
|
//#endregion
|
|
536
|
+
//#region src/internal/utilities/collection-utils.ts
|
|
537
|
+
/** Add a value to a Set stored in a Map, creating the Set if it doesn't exist. */
|
|
538
|
+
function addToSetMap(map, key, value) {
|
|
539
|
+
let set = map.get(key);
|
|
540
|
+
if (!set) {
|
|
541
|
+
set = /* @__PURE__ */ new Set();
|
|
542
|
+
map.set(key, set);
|
|
543
|
+
}
|
|
544
|
+
set.add(value);
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/internal/prepass/scan-cross-file-selectors.ts
|
|
548
|
+
/**
|
|
549
|
+
* Pre-filter: matches any bare `${Identifier}` template expression.
|
|
550
|
+
* Used to skip files that only contain arrow functions or member expressions
|
|
551
|
+
* in template literals (e.g. `${props => ...}`, `${theme.color}`).
|
|
552
|
+
*/
|
|
553
|
+
const BARE_TEMPLATE_IDENTIFIER_RE = /\$\{\s*[a-zA-Z_$][\w$]*\s*\}/;
|
|
554
|
+
/**
|
|
555
|
+
* Categorize cross-file selector usages into marker sidecar and global selector bridge maps.
|
|
556
|
+
*
|
|
557
|
+
* Bridge usages (from already-converted files) are skipped — the consumer handles marker
|
|
558
|
+
* generation via the forward selector handler, so no sidecar/bridge is needed on the target.
|
|
559
|
+
*/
|
|
560
|
+
function categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, componentsNeedingGlobalSelectorBridge) {
|
|
561
|
+
for (const usage of usages) {
|
|
562
|
+
if (usage.bridgeComponentName) continue;
|
|
563
|
+
if (usage.consumerIsTransformed) addToSetMap(componentsNeedingMarkerSidecar, usage.resolvedPath, usage.importedName);
|
|
564
|
+
addToSetMap(componentsNeedingGlobalSelectorBridge, usage.resolvedPath, usage.importedName);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Regex matching bridge GlobalSelector export patterns (global for matchAll).
|
|
569
|
+
* Matches both:
|
|
570
|
+
* - Old format: `export const XGlobalSelector = ".sc2sx-..."`
|
|
571
|
+
* - New format: `` export const XGlobalSelector = `.${xBridgeClass}` ``
|
|
572
|
+
*/
|
|
573
|
+
const BRIDGE_EXPORT_RE = /export\s+const\s+(\w+GlobalSelector)\s*=\s*(?:["']\.sc2sx-|`\.\$\{)/g;
|
|
574
|
+
/**
|
|
575
|
+
* Detect whether an imported name is a bridge GlobalSelector from an
|
|
576
|
+
* already-converted StyleX file.
|
|
577
|
+
*
|
|
578
|
+
* Detection criteria (hybrid fast + safe):
|
|
579
|
+
* 1. Variable name ends with "GlobalSelector" AND the stripped name starts uppercase
|
|
580
|
+
* 2. Target file contains "@stylexjs/stylex" (string check, no parse)
|
|
581
|
+
* 3. Target file has a matching `export const XGlobalSelector = ".sc2sx-"` pattern
|
|
582
|
+
*
|
|
583
|
+
* @returns The stripped component name (e.g., "CollapseArrowIcon" for
|
|
584
|
+
* "CollapseArrowIconGlobalSelector"), or null if not a bridge.
|
|
585
|
+
*/
|
|
586
|
+
function detectBridgeGlobalSelector(importedName, resolvedPath, readFile) {
|
|
587
|
+
if (!importedName.endsWith("GlobalSelector")) return null;
|
|
588
|
+
const stripped = importedName.slice(0, -14);
|
|
589
|
+
if (!stripped || !/^[A-Z]/.test(stripped)) return null;
|
|
590
|
+
const content = readFile(resolvedPath);
|
|
591
|
+
if (!content || !content.includes("@stylexjs/stylex")) return null;
|
|
592
|
+
let found = false;
|
|
593
|
+
for (const m of content.matchAll(BRIDGE_EXPORT_RE)) if (m[1] === importedName) {
|
|
594
|
+
found = true;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
if (!found) return null;
|
|
598
|
+
return stripped;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* If `importedName` is a bridge GlobalSelector, populate bridge fields on `usage`
|
|
602
|
+
* and find the corresponding component import from the same source.
|
|
603
|
+
*/
|
|
604
|
+
function applyBridgeFields(usage, importedName, localName, resolvedPath, importMap, readFile) {
|
|
605
|
+
const bridgeName = detectBridgeGlobalSelector(importedName, resolvedPath, readFile);
|
|
606
|
+
if (!bridgeName) return;
|
|
607
|
+
usage.bridgeComponentName = bridgeName;
|
|
608
|
+
const imp = importMap.get(localName);
|
|
609
|
+
if (!imp) return;
|
|
610
|
+
let defaultImportLocal;
|
|
611
|
+
for (const [otherLocal, otherImp] of importMap) {
|
|
612
|
+
if (otherImp.source !== imp.source || otherLocal === localName) continue;
|
|
613
|
+
if (otherImp.importedName === bridgeName) {
|
|
614
|
+
usage.bridgeComponentLocalName = otherLocal;
|
|
615
|
+
defaultImportLocal = void 0;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
if (otherImp.importedName === "default" && defaultImportLocal === void 0) defaultImportLocal = otherLocal;
|
|
619
|
+
}
|
|
620
|
+
if (defaultImportLocal !== void 0) usage.bridgeComponentLocalName = defaultImportLocal;
|
|
621
|
+
}
|
|
622
|
+
/** Global version for matchAll/replace operations */
|
|
623
|
+
const PLACEHOLDER_RE_G = new RegExp(PLACEHOLDER_RE.source, "g");
|
|
624
|
+
/**
|
|
625
|
+
* Walk the AST collecting ImportDeclaration and TaggedTemplateExpression nodes.
|
|
626
|
+
*
|
|
627
|
+
* Uses a targeted recursive walk — only descends into node types that can
|
|
628
|
+
* contain these targets (skips type annotations, comments, etc.).
|
|
629
|
+
*/
|
|
630
|
+
function walkForImportsAndTemplates(node, imports, templates) {
|
|
631
|
+
if (!node || typeof node !== "object") return;
|
|
632
|
+
const n = node;
|
|
633
|
+
if (n.type === "ImportDeclaration") {
|
|
634
|
+
imports.push(n);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (n.type === "TaggedTemplateExpression") templates.push(n);
|
|
638
|
+
for (const key of Object.keys(n)) {
|
|
639
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc") continue;
|
|
640
|
+
const val = n[key];
|
|
641
|
+
if (Array.isArray(val)) for (const child of val) walkForImportsAndTemplates(child, imports, templates);
|
|
642
|
+
else if (val && typeof val === "object" && val.type) walkForImportsAndTemplates(val, imports, templates);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/** Build a map of localName → import info from raw ImportDeclaration nodes. */
|
|
646
|
+
function buildImportMapFromNodes(importNodes) {
|
|
647
|
+
const map = /* @__PURE__ */ new Map();
|
|
648
|
+
for (const node of importNodes) {
|
|
649
|
+
const sourceValue = node.source?.value;
|
|
650
|
+
if (typeof sourceValue !== "string") continue;
|
|
651
|
+
const specifiers = node.specifiers;
|
|
652
|
+
if (!specifiers) continue;
|
|
653
|
+
for (const spec of specifiers) {
|
|
654
|
+
const localName = getNodeName(spec.local);
|
|
655
|
+
if (!localName) continue;
|
|
656
|
+
if (spec.type === "ImportDefaultSpecifier") map.set(localName, {
|
|
657
|
+
source: sourceValue,
|
|
658
|
+
importedName: "default"
|
|
659
|
+
});
|
|
660
|
+
else if (spec.type === "ImportNamespaceSpecifier") map.set(localName, {
|
|
661
|
+
source: sourceValue,
|
|
662
|
+
importedName: "*"
|
|
663
|
+
});
|
|
664
|
+
else if (spec.type === "ImportSpecifier") {
|
|
665
|
+
const importedName = getNodeName(spec.imported) ?? localName;
|
|
666
|
+
map.set(localName, {
|
|
667
|
+
source: sourceValue,
|
|
668
|
+
importedName
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return map;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Local identifiers that refer to `styled` from `"styled-components"` (default and/or
|
|
677
|
+
* `import { styled }` / `import { styled as sc }`).
|
|
678
|
+
*/
|
|
679
|
+
function collectStyledLocalBindingNames(importNodes) {
|
|
680
|
+
const names = /* @__PURE__ */ new Set();
|
|
681
|
+
for (const node of importNodes) {
|
|
682
|
+
if (node.source?.value !== "styled-components") continue;
|
|
683
|
+
const specifiers = node.specifiers;
|
|
684
|
+
if (!specifiers) continue;
|
|
685
|
+
for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") {
|
|
686
|
+
const name = getNodeName(spec.local);
|
|
687
|
+
if (name) names.add(name);
|
|
688
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
689
|
+
if (getNodeName(spec.imported) === "styled") {
|
|
690
|
+
const localName = getNodeName(spec.local);
|
|
691
|
+
if (localName) names.add(localName);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return names;
|
|
696
|
+
}
|
|
697
|
+
/** Find the local name for the styled-components default import. */
|
|
698
|
+
function findStyledImportNameFromNodes(importNodes) {
|
|
699
|
+
let namedStyledLocal;
|
|
700
|
+
for (const node of importNodes) {
|
|
701
|
+
if (node.source?.value !== "styled-components") continue;
|
|
702
|
+
const specifiers = node.specifiers;
|
|
703
|
+
if (!specifiers) continue;
|
|
704
|
+
for (const spec of specifiers) if (spec.type === "ImportDefaultSpecifier") {
|
|
705
|
+
const name = getNodeName(spec.local);
|
|
706
|
+
if (name) return name;
|
|
707
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
708
|
+
if (getNodeName(spec.imported) === "styled") {
|
|
709
|
+
const localName = getNodeName(spec.local);
|
|
710
|
+
if (localName) namedStyledLocal = localName;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return namedStyledLocal;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Find local names of `css` imported from styled-components.
|
|
718
|
+
* Handles aliased imports like `import { css as sc } from "styled-components"`.
|
|
719
|
+
*/
|
|
720
|
+
function findCssImportNamesFromNodes(importNodes) {
|
|
721
|
+
const names = /* @__PURE__ */ new Set();
|
|
722
|
+
for (const node of importNodes) {
|
|
723
|
+
if (node.source?.value !== "styled-components") continue;
|
|
724
|
+
const specifiers = node.specifiers;
|
|
725
|
+
if (!specifiers) continue;
|
|
726
|
+
for (const spec of specifiers) if (spec.type === "ImportSpecifier") {
|
|
727
|
+
if (getNodeName(spec.imported) === "css") {
|
|
728
|
+
const localName = getNodeName(spec.local);
|
|
729
|
+
if (localName) names.add(localName);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return names;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Find local names of imported components used as selectors inside
|
|
737
|
+
* styled-components template literals (both `styled` and `css` tagged templates).
|
|
738
|
+
*/
|
|
739
|
+
function findComponentSelectorLocalsFromNodes(templateNodes, styledImportName, cssImportNames) {
|
|
740
|
+
const selectorLocals = /* @__PURE__ */ new Set();
|
|
741
|
+
for (const node of templateNodes) {
|
|
742
|
+
if (!isStyledTag(node.tag, styledImportName) && !isCssTag(node.tag, cssImportNames)) continue;
|
|
743
|
+
const quasi = node.quasi;
|
|
744
|
+
if (!quasi) continue;
|
|
745
|
+
const quasis = quasi.quasis;
|
|
746
|
+
const expressions = quasi.expressions;
|
|
747
|
+
if (!quasis || !expressions) continue;
|
|
748
|
+
const rawParts = [];
|
|
749
|
+
for (let i = 0; i < quasis.length; i++) {
|
|
750
|
+
const value = quasis[i]?.value;
|
|
751
|
+
rawParts.push(value?.raw ?? "");
|
|
752
|
+
if (i < expressions.length) rawParts.push(`__SC_EXPR_${i}__`);
|
|
753
|
+
}
|
|
754
|
+
const rawCss = rawParts.join("");
|
|
755
|
+
for (const match of rawCss.matchAll(PLACEHOLDER_RE_G)) {
|
|
756
|
+
const exprIndex = Number(match[1]);
|
|
757
|
+
const pos = match.index;
|
|
758
|
+
if (isTemplatePlaceholderInSelectorContext(rawCss, pos, match[0].length)) {
|
|
759
|
+
const expr = expressions[exprIndex];
|
|
760
|
+
if (expr?.type === "Identifier" && typeof expr.name === "string") selectorLocals.add(expr.name);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return selectorLocals;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Check whether a styled-components tag expression is a styled call.
|
|
768
|
+
* Matches: styled.div, styled(X), styled.div.attrs(...), styled(X).withConfig(...), etc.
|
|
769
|
+
*/
|
|
770
|
+
function isStyledTag(tag, styledName) {
|
|
771
|
+
if (!tag || typeof tag !== "object") return false;
|
|
772
|
+
if (tag.type === "MemberExpression") {
|
|
773
|
+
const obj = tag.object;
|
|
774
|
+
if (obj?.type === "Identifier" && obj.name === styledName) return true;
|
|
775
|
+
}
|
|
776
|
+
if (tag.type === "CallExpression") {
|
|
777
|
+
const callee = tag.callee;
|
|
778
|
+
if (callee?.type === "Identifier" && callee.name === styledName) return true;
|
|
779
|
+
if (callee?.type === "MemberExpression" && callee.object) return isStyledTag(callee.object, styledName);
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
/** Check if a template tag is the `css` helper from styled-components. */
|
|
784
|
+
function isCssTag(tag, cssImportNames) {
|
|
785
|
+
if (!tag || !cssImportNames || cssImportNames.size === 0) return false;
|
|
786
|
+
return tag.type === "Identifier" && typeof tag.name === "string" && cssImportNames.has(tag.name);
|
|
787
|
+
}
|
|
788
|
+
/** Safely extract the name string from an AST identifier-like node. */
|
|
789
|
+
function getNodeName(node) {
|
|
790
|
+
if (!node || typeof node !== "object") return;
|
|
791
|
+
if (node.type === "Identifier" && typeof node.name === "string") return node.name;
|
|
792
|
+
}
|
|
793
|
+
/** Deduplicate and resolve two file lists into a single array of absolute paths. */
|
|
794
|
+
function deduplicateAndResolve(filesToTransform, consumerPaths) {
|
|
795
|
+
const seen = /* @__PURE__ */ new Set();
|
|
796
|
+
const result = [];
|
|
797
|
+
for (const f of filesToTransform) {
|
|
798
|
+
const abs = resolve(f);
|
|
799
|
+
if (!seen.has(abs)) {
|
|
800
|
+
seen.add(abs);
|
|
801
|
+
result.push(abs);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
for (const f of consumerPaths) {
|
|
805
|
+
const abs = resolve(f);
|
|
806
|
+
if (!seen.has(abs)) {
|
|
807
|
+
seen.add(abs);
|
|
808
|
+
result.push(abs);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return result;
|
|
812
|
+
}
|
|
813
|
+
//#endregion
|
|
814
|
+
//#region src/internal/utilities/default-export-name.ts
|
|
815
|
+
/**
|
|
816
|
+
* Regex helpers for inspecting a module's default export by source text.
|
|
817
|
+
*
|
|
818
|
+
* These operate on raw source strings (not the AST) so they can be shared by
|
|
819
|
+
* both prepass and transform-step layers without import-graph coupling.
|
|
820
|
+
*/
|
|
821
|
+
/**
|
|
822
|
+
* Returns the local name of a PascalCase default export, supporting both
|
|
823
|
+
* `export default Name` and `export { Name as default }` forms. Returns
|
|
824
|
+
* `undefined` when no PascalCase default export is found.
|
|
825
|
+
*/
|
|
826
|
+
function findDefaultExportedLocalName(source) {
|
|
827
|
+
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];
|
|
828
|
+
}
|
|
829
|
+
//#endregion
|
|
525
830
|
//#region src/internal/utilities/ast-walk.ts
|
|
526
831
|
const SKIPPED_KEYS = new Set([
|
|
527
832
|
"loc",
|
|
@@ -547,4 +852,4 @@ function walkAst(root, visitor) {
|
|
|
547
852
|
visit(root);
|
|
548
853
|
}
|
|
549
854
|
//#endregion
|
|
550
|
-
export { findImportSource as a, resolveBarrelReExportBinding as c,
|
|
855
|
+
export { PARTIAL_MIGRATION_INCOMPLETE_WARNING as C, Logger as S, getCascadeDependedFilePath as T, findImportSource as _, buildImportMapFromNodes as a, resolveBarrelReExportBinding as b, deduplicateAndResolve as c, findStyledImportNameFromNodes as d, walkForImportsAndTemplates as f, fileImportsFrom as g, fileExports as h, applyBridgeFields as i, findComponentSelectorLocalsFromNodes as l, createPrepassParser as m, findDefaultExportedLocalName as n, categorizeSelectorUsages as o, addToSetMap as p, BARE_TEMPLATE_IDENTIFIER_RE as r, collectStyledLocalBindingNames as s, walkAst as t, findCssImportNamesFromNodes as u, getReExportedSourceName as v, UNSUPPORTED_SHOULD_FORWARD_PROP_WARNING as w, CASCADE_CONFLICT_WARNING as x, resolveBarrelReExport as y };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as
|
|
3
|
-
import {
|
|
1
|
+
import { t as isSelectorContext } from "./selector-context-heuristic-Dptd93Xe.mjs";
|
|
2
|
+
import { r as toRealPath } from "./path-utils-ByFNVtHo.mjs";
|
|
3
|
+
import { r as escapeRegex } from "./string-utils-4eeXGa48.mjs";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
//#region src/internal/bridge-consumer-patcher.ts
|
|
6
6
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as findImportSource, b as resolveBarrelReExportBinding, f as walkForImportsAndTemplates, n as findDefaultExportedLocalName, s as collectStyledLocalBindingNames } from "./ast-walk-CLvMH7Lm.mjs";
|
|
2
2
|
//#region src/internal/prepass/compute-leaf-set.ts
|
|
3
3
|
/**
|
|
4
4
|
* Computes which styled-component bindings are "leaves" for leaves-only mode:
|
|
@@ -44,6 +44,22 @@ function extractStyledDefBasesFromSource(filePath, source, into) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
+
* Regex baseline for styled defs, then an AST pass overrides/adds rows when the
|
|
48
|
+
* source parses. The AST pass understands aliased/named `styled` imports
|
|
49
|
+
* (`import { styled as sc }`) that the regexes (which assume the literal `styled`)
|
|
50
|
+
* miss, so callers that only ran the regex extractor under-report components.
|
|
51
|
+
*/
|
|
52
|
+
function extractStyledDefBases(filePath, source, parser, into) {
|
|
53
|
+
extractStyledDefBasesFromSource(filePath, source, into);
|
|
54
|
+
try {
|
|
55
|
+
const ast = parser.parse(source);
|
|
56
|
+
const program = ast.program ?? ast;
|
|
57
|
+
const importNodes = [];
|
|
58
|
+
walkForImportsAndTemplates(program, importNodes, []);
|
|
59
|
+
extractStyledDefBasesFromAstProgram(filePath, program, collectStyledLocalBindingNames(importNodes), into);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
47
63
|
* AST-based extraction: understands `let`/`var`, export blocks, named `styled` imports,
|
|
48
64
|
* and `.attrs` / `.withConfig` chains before the tagged template.
|
|
49
65
|
* Results merge into `into`; bindings found here override regex entries for the same name.
|
|
@@ -235,8 +251,5 @@ function leafKeyExists(defFile, exportedName, allowDefaultFallback, cachedRead,
|
|
|
235
251
|
const defaultLocalName = findDefaultExportedLocalName(cachedRead(defFile));
|
|
236
252
|
return defaultLocalName ? globalLeaves.get(defFile)?.has(defaultLocalName) ?? false : false;
|
|
237
253
|
}
|
|
238
|
-
function findDefaultExportedLocalName(source) {
|
|
239
|
-
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];
|
|
240
|
-
}
|
|
241
254
|
//#endregion
|
|
242
|
-
export {
|
|
255
|
+
export { extractStyledDefBases as n, computeGlobalLeafKeys as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as toRealPath } from "./path-utils-
|
|
2
|
-
import { r as escapeRegex } from "./string-utils-
|
|
1
|
+
import { r as toRealPath } from "./path-utils-ByFNVtHo.mjs";
|
|
2
|
+
import { r as escapeRegex } from "./string-utils-4eeXGa48.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
|
@@ -64,7 +64,8 @@ interface RunTransformOptions {
|
|
|
64
64
|
*/
|
|
65
65
|
maxExamples?: number;
|
|
66
66
|
/**
|
|
67
|
-
* Suppress
|
|
67
|
+
* Suppress console output: both the per-file runner messages and the final
|
|
68
|
+
* warning summary report. Warnings are still collected and returned.
|
|
68
69
|
* @default false
|
|
69
70
|
*/
|
|
70
71
|
silent?: boolean;
|
|
@@ -99,6 +100,13 @@ interface RunTransformOptions {
|
|
|
99
100
|
* @default false
|
|
100
101
|
*/
|
|
101
102
|
collectStandaloneFileResults?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Absolute paths of files to treat as already converted to StyleX, even though
|
|
105
|
+
* they are not. Cascade-conflict checks then "see past" these files so a
|
|
106
|
+
* consumer's own unsupported patterns surface instead of being masked by the
|
|
107
|
+
* cascade bail. Intended for analysis-only/dry runs (e.g. the migration plan).
|
|
108
|
+
*/
|
|
109
|
+
assumeConvertedFiles?: string[];
|
|
102
110
|
}
|
|
103
111
|
interface RunTransformResult {
|
|
104
112
|
/** Number of files that had errors */
|
|
@@ -113,6 +121,8 @@ interface RunTransformResult {
|
|
|
113
121
|
timeElapsed: number;
|
|
114
122
|
/** Warnings emitted during transformation */
|
|
115
123
|
warnings: CollectedWarning[];
|
|
124
|
+
/** Per-file outcomes from the dependency-ordered run. */
|
|
125
|
+
fileResults: TransformFileResult[];
|
|
116
126
|
/** Per-file outcomes from isolated transforms, populated when requested. */
|
|
117
127
|
standaloneFileResults?: TransformFileResult[];
|
|
118
128
|
/** Warnings from isolated transforms, populated when requested. */
|
|
@@ -162,4 +172,83 @@ type TransformFileResult = {
|
|
|
162
172
|
status: "error" | "skipped" | "unchanged" | "transformed";
|
|
163
173
|
};
|
|
164
174
|
//#endregion
|
|
165
|
-
|
|
175
|
+
//#region src/migration-plan.d.ts
|
|
176
|
+
interface MigrationPlanOptions {
|
|
177
|
+
/** Glob pattern(s) for the files the codemod would transform. */
|
|
178
|
+
files: string | string[];
|
|
179
|
+
/** Glob pattern(s) of additional files to scan for consumers, or `null`. */
|
|
180
|
+
consumerPaths: string | string[] | null;
|
|
181
|
+
/** Adapter for the transform (same adapter you would pass to `runTransform`). */
|
|
182
|
+
adapter: AdapterInput;
|
|
183
|
+
/** jscodeshift parser to use. @default "tsx" */
|
|
184
|
+
parser?: "babel" | "babylon" | "flow" | "ts" | "tsx";
|
|
185
|
+
/**
|
|
186
|
+
* Safety cap on fixpoint analysis passes used to reveal cascade-masked
|
|
187
|
+
* blockers. Analysis throws if it doesn't stabilize within this many passes
|
|
188
|
+
* (rather than returning a partial plan). @default 50
|
|
189
|
+
*/
|
|
190
|
+
maxAnalysisPasses?: number;
|
|
191
|
+
}
|
|
192
|
+
interface ImportedExportUsage {
|
|
193
|
+
/** Exported name consumers import (`"default"` for a default import, `"*"` for a namespace). */
|
|
194
|
+
exportName: string;
|
|
195
|
+
/** Number of distinct files importing this export. */
|
|
196
|
+
consumerCount: number;
|
|
197
|
+
}
|
|
198
|
+
interface ManualConversionReason {
|
|
199
|
+
/** The bail message explaining why the codemod cannot convert the file. */
|
|
200
|
+
message: string;
|
|
201
|
+
/** Source locations where the unsupported pattern occurs. */
|
|
202
|
+
locations: Array<{
|
|
203
|
+
filePath: string;
|
|
204
|
+
line: number;
|
|
205
|
+
column: number;
|
|
206
|
+
}>;
|
|
207
|
+
}
|
|
208
|
+
interface ManualConversionFile {
|
|
209
|
+
/** Path of the file that must be converted by hand. */
|
|
210
|
+
filePath: string;
|
|
211
|
+
/** 1-based position in the recommended bottom-up conversion order. */
|
|
212
|
+
order: number;
|
|
213
|
+
/** Number of distinct files that import from this file. */
|
|
214
|
+
consumerCount: number;
|
|
215
|
+
/**
|
|
216
|
+
* Number of files that auto-convert once this file is converted, because this
|
|
217
|
+
* file is their ONLY remaining blocker (single cascade blocker, no unsupported
|
|
218
|
+
* patterns of their own). Converting this file is sufficient for these.
|
|
219
|
+
*/
|
|
220
|
+
soleBlockerFileCount: number;
|
|
221
|
+
/**
|
|
222
|
+
* Number of files that cascade-bail on this file (raw chain involvement).
|
|
223
|
+
* Includes files that also have other blockers, so converting this file alone
|
|
224
|
+
* does not necessarily unblock all of them. Always ≥ `soleBlockerFileCount`.
|
|
225
|
+
*/
|
|
226
|
+
blockedFileCount: number;
|
|
227
|
+
/** Which exports consumers import, so you know what to convert first. */
|
|
228
|
+
importedExports: ImportedExportUsage[];
|
|
229
|
+
/** Why the codemod cannot convert this file automatically. */
|
|
230
|
+
reasons: ManualConversionReason[];
|
|
231
|
+
/** Other files in this plan that this file imports (must be converted first). */
|
|
232
|
+
dependsOn: string[];
|
|
233
|
+
}
|
|
234
|
+
interface MigrationPlan {
|
|
235
|
+
/** Files to convert by hand, ordered by unblock impact (dependencies first). */
|
|
236
|
+
manualConversionFiles: ManualConversionFile[];
|
|
237
|
+
/** Total number of files matched by the `files` glob. */
|
|
238
|
+
totalFiles: number;
|
|
239
|
+
/**
|
|
240
|
+
* Number of distinct files that auto-convert once ALL listed files are
|
|
241
|
+
* converted (cascade-blocked files that have no unsupported patterns of their
|
|
242
|
+
* own). This is the whole-plan payoff, not attributable to any single file.
|
|
243
|
+
*/
|
|
244
|
+
unlocksFileCount: number;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Run the codemod in analysis-only (dry) mode and compute the ordered list of
|
|
248
|
+
* files that block the rest of the migration and must be converted manually.
|
|
249
|
+
*/
|
|
250
|
+
declare function analyzeMigrationPlan(options: MigrationPlanOptions): Promise<MigrationPlan>;
|
|
251
|
+
/** Render a {@link MigrationPlan} as a human-readable, actionable report. */
|
|
252
|
+
declare function formatMigrationPlan(plan: MigrationPlan): string;
|
|
253
|
+
//#endregion
|
|
254
|
+
export { type AdapterInput, type ImportSource, type ImportedExportUsage, type ManualConversionFile, type ManualConversionReason, type MarkerFileContext, type MigrationPlan, type MigrationPlanOptions, analyzeMigrationPlan, defineAdapter, formatMigrationPlan, runTransform };
|