styled-components-to-stylex-codemod 0.0.21 → 0.0.23
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 +8 -0
- package/dist/{forwarded-as-consumer-patcher-GPnjkzc_.mjs → forwarded-as-consumer-patcher-CIOAJ-51.mjs} +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +20 -3
- package/dist/{logger-tKRY0yOA.d.mts → logger-Cml101qs.d.mts} +36 -1
- package/dist/{logger-Bi5-MGBs.mjs → logger-StI3W9gi.mjs} +4 -1
- package/dist/{run-prepass-BuYfEK4M.mjs → run-prepass-DV-4A_Ci.mjs} +19 -9
- package/dist/{string-utils-Dmym5IkG.mjs → string-utils-J3iaDsXd.mjs} +9 -1
- package/dist/transform.d.mts +8 -1
- package/dist/transform.mjs +7633 -6951
- package/dist/transient-prop-consumer-patcher-iofVVX40.mjs +156 -0
- package/package.json +5 -5
- /package/dist/{styled-css-DBryFqQM.mjs → styled-css-BJH7gntu.mjs} +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { n as escapeRegex } from "./string-utils-J3iaDsXd.mjs";
|
|
2
|
+
import { basename, dirname, relative } from "node:path";
|
|
3
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/transient-prop-consumer-patcher.ts
|
|
6
|
+
/**
|
|
7
|
+
* Post-transform consumer patching for transient prop renames.
|
|
8
|
+
*
|
|
9
|
+
* When a component (e.g., `CollapseArrowIcon`) is converted from styled-components
|
|
10
|
+
* to a plain function, its `$`-prefixed props are renamed (e.g., `$isOpen` → `isOpen`).
|
|
11
|
+
* Unconverted consumer files that use `<CollapseArrowIcon $isOpen={...} />` must be
|
|
12
|
+
* patched to use the new prop names.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Scan all consumer files and patch any that import renamed components.
|
|
16
|
+
*
|
|
17
|
+
* Iterates consumers once (outer) and targets (inner) to minimise I/O.
|
|
18
|
+
* Returns the list of consumer file paths that were patched.
|
|
19
|
+
*/
|
|
20
|
+
function collectTransientPropPatches(args) {
|
|
21
|
+
const { transientPropRenames, consumerFilePaths, resolver } = args;
|
|
22
|
+
const results = [];
|
|
23
|
+
const allConsumerFiles = new Set(consumerFilePaths.map(toRealPath));
|
|
24
|
+
for (const consumerPath of allConsumerFiles) {
|
|
25
|
+
let consumerSource;
|
|
26
|
+
try {
|
|
27
|
+
consumerSource = readFileSync(consumerPath, "utf-8");
|
|
28
|
+
} catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const allEntries = [];
|
|
32
|
+
for (const [targetPath, renames] of transientPropRenames) {
|
|
33
|
+
if (consumerPath === targetPath) continue;
|
|
34
|
+
const importSources = buildPossibleImportSources(consumerPath, targetPath, consumerSource, resolver);
|
|
35
|
+
const entries = findImportedRenamedComponents(consumerSource, importSources, renames);
|
|
36
|
+
allEntries.push(...entries);
|
|
37
|
+
}
|
|
38
|
+
if (allEntries.length > 0) {
|
|
39
|
+
const patched = patchSourceTransientProps(consumerSource, allEntries);
|
|
40
|
+
if (patched !== null) results.push({
|
|
41
|
+
consumerPath,
|
|
42
|
+
patched
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Scan a consumer file's imports to find which renamed components it uses.
|
|
50
|
+
*/
|
|
51
|
+
function findImportedRenamedComponents(consumerSource, targetImportSources, componentRenames) {
|
|
52
|
+
const entries = [];
|
|
53
|
+
for (const { exportName, renames } of componentRenames) {
|
|
54
|
+
const localName = findLocalImportName(consumerSource, targetImportSources, exportName);
|
|
55
|
+
if (localName) entries.push({
|
|
56
|
+
localComponentName: localName,
|
|
57
|
+
renames
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return entries;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Patch source code: rename `$prop` → `prop` in JSX attributes.
|
|
64
|
+
* Returns the patched source or `null` if unchanged.
|
|
65
|
+
*/
|
|
66
|
+
function patchSourceTransientProps(source, entries) {
|
|
67
|
+
if (entries.length === 0) return null;
|
|
68
|
+
let modified = source;
|
|
69
|
+
for (const { localComponentName, renames } of entries) modified = patchJsxTransientProps(modified, localComponentName, renames);
|
|
70
|
+
return modified !== source ? modified : null;
|
|
71
|
+
}
|
|
72
|
+
function toRealPath(filePath) {
|
|
73
|
+
try {
|
|
74
|
+
return realpathSync(filePath);
|
|
75
|
+
} catch {
|
|
76
|
+
return filePath;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build import source strings that a consumer might use to import from `targetPath`.
|
|
81
|
+
* Generates relative paths (with/without extension, /index), and — when a resolver
|
|
82
|
+
* is provided — also probes the consumer's actual import specifiers to catch
|
|
83
|
+
* tsconfig path aliases.
|
|
84
|
+
*/
|
|
85
|
+
function buildPossibleImportSources(consumerPath, targetPath, consumerSource, resolver) {
|
|
86
|
+
const sources = /* @__PURE__ */ new Set();
|
|
87
|
+
const dir = dirname(consumerPath);
|
|
88
|
+
let rel = relative(dir, targetPath).replace(/\\/g, "/");
|
|
89
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
90
|
+
sources.add(rel);
|
|
91
|
+
for (const ext of [
|
|
92
|
+
".tsx",
|
|
93
|
+
".ts",
|
|
94
|
+
".jsx",
|
|
95
|
+
".js"
|
|
96
|
+
]) if (rel.endsWith(ext)) {
|
|
97
|
+
sources.add(rel.slice(0, -ext.length));
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (basename(targetPath).replace(/\.\w+$/, "") === "index") {
|
|
101
|
+
const dirRel = relative(dir, dirname(targetPath)).replace(/\\/g, "/");
|
|
102
|
+
sources.add(dirRel.startsWith(".") ? dirRel : `./${dirRel}`);
|
|
103
|
+
}
|
|
104
|
+
if (resolver) for (const m of consumerSource.matchAll(/from\s+["']([^"']+)["']/g)) {
|
|
105
|
+
const specifier = m[1];
|
|
106
|
+
if (sources.has(specifier)) continue;
|
|
107
|
+
try {
|
|
108
|
+
const resolved = resolver.resolve(consumerPath, specifier);
|
|
109
|
+
if (resolved && toRealPath(resolved) === targetPath) sources.add(specifier);
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
return sources;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Find the local name for an imported component.
|
|
116
|
+
* Checks named imports (`import { X }`, `import { X as Y }`),
|
|
117
|
+
* default imports (`import X`).
|
|
118
|
+
*/
|
|
119
|
+
function findLocalImportName(source, targetImportSources, exportName) {
|
|
120
|
+
for (const match of source.matchAll(/import\s+(type\s+)?(?:({[^}]+})|(\w+)(?:\s*,\s*({[^}]+}))?)\s+from\s+["']([^"']+)["']/g)) {
|
|
121
|
+
if (!!match[1]) continue;
|
|
122
|
+
const importSource = match[5];
|
|
123
|
+
if (!importSource || !targetImportSources.has(importSource)) continue;
|
|
124
|
+
const defaultImport = match[3];
|
|
125
|
+
const namedImports = match[2] ?? match[4];
|
|
126
|
+
if (exportName === "default" && defaultImport) return defaultImport;
|
|
127
|
+
if (namedImports) {
|
|
128
|
+
const specifierRegex = new RegExp(`\\b${escapeRegex(exportName)}\\s+as\\s+(\\w+)|\\b(${escapeRegex(exportName)})\\b`, "g");
|
|
129
|
+
for (const specMatch of namedImports.matchAll(specifierRegex)) {
|
|
130
|
+
const aliased = specMatch[1];
|
|
131
|
+
const direct = specMatch[2];
|
|
132
|
+
if (aliased) return aliased;
|
|
133
|
+
if (direct) return direct;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Rename `$prop` → `prop` in JSX attributes for a specific component.
|
|
141
|
+
* Matches `<ComponentName ... $propName=` and `<ComponentName ... $propName>`
|
|
142
|
+
* (shorthand boolean).
|
|
143
|
+
*/
|
|
144
|
+
function patchJsxTransientProps(source, componentName, renames) {
|
|
145
|
+
let result = source;
|
|
146
|
+
for (const [original, renamed] of Object.entries(renames)) {
|
|
147
|
+
const escapedComponent = escapeRegex(componentName);
|
|
148
|
+
const escapedProp = escapeRegex(original);
|
|
149
|
+
const tagRegex = new RegExp(`(<${escapedComponent}\\b[^<>]*?\\s)${escapedProp}(?=[\\s=/>])`, "g");
|
|
150
|
+
result = result.replace(tagRegex, `$1${renamed}`);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { collectTransientPropPatches };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styled-components-to-stylex-codemod",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "Codemod to transform styled-components to StyleX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codemod",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"@codemirror/lang-javascript": "^6.2.4",
|
|
41
41
|
"@emotion/is-prop-valid": "^1.4.0",
|
|
42
42
|
"@storybook/react-vite": "^10.2.10",
|
|
43
|
-
"@stylexjs/babel-plugin": "^0.
|
|
44
|
-
"@stylexjs/eslint-plugin": "^0.
|
|
45
|
-
"@stylexjs/stylex": "^0.
|
|
46
|
-
"@stylexjs/unplugin": "^0.
|
|
43
|
+
"@stylexjs/babel-plugin": "^0.18.1",
|
|
44
|
+
"@stylexjs/eslint-plugin": "^0.18.1",
|
|
45
|
+
"@stylexjs/stylex": "^0.18.1",
|
|
46
|
+
"@stylexjs/unplugin": "^0.18.1",
|
|
47
47
|
"@types/jscodeshift": "^17.3.0",
|
|
48
48
|
"@types/node": "^25.3.0",
|
|
49
49
|
"@types/react": "^19.2.14",
|
|
File without changes
|