styled-components-to-stylex-codemod 0.0.7 → 0.0.10
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 +82 -58
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +76 -21
- package/dist/logger-BLeJjMzG.mjs +306 -0
- package/dist/logger-DC-1uogs.d.mts +452 -0
- package/dist/transform.d.mts +1 -2
- package/dist/transform.mjs +18194 -7713
- package/package.json +37 -29
- package/dist/logger-BS4Evg0n.d.mts +0 -175
- package/dist/logger-Dlnt1fYP.mjs +0 -156
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/public-api-validation.ts
|
|
4
|
+
function describeValue(value) {
|
|
5
|
+
if (value === null) return "null";
|
|
6
|
+
if (value === void 0) return "undefined";
|
|
7
|
+
if (Array.isArray(value)) return `Array(${value.length})`;
|
|
8
|
+
if (typeof value === "string") return `"${value}"`;
|
|
9
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
10
|
+
if (typeof value === "bigint") return value.toString();
|
|
11
|
+
if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
|
|
12
|
+
if (typeof value === "function") return "[Function]";
|
|
13
|
+
if (typeof value === "object") {
|
|
14
|
+
const ctor = value?.constructor?.name ?? "Object";
|
|
15
|
+
let keys = [];
|
|
16
|
+
try {
|
|
17
|
+
keys = Object.keys(value);
|
|
18
|
+
} catch {}
|
|
19
|
+
const preview = keys.slice(0, 5).join(", ");
|
|
20
|
+
const suffix = keys.length > 5 ? ", ..." : "";
|
|
21
|
+
return keys.length ? `${ctor} { ${preview}${suffix} }` : ctor;
|
|
22
|
+
}
|
|
23
|
+
return "[Unknown]";
|
|
24
|
+
}
|
|
25
|
+
function assertValidAdapter(candidate, where) {
|
|
26
|
+
const obj = candidate;
|
|
27
|
+
const resolveValue = obj?.resolveValue;
|
|
28
|
+
const resolveCall = obj?.resolveCall;
|
|
29
|
+
const resolveSelector = obj?.resolveSelector;
|
|
30
|
+
const externalInterface = obj?.externalInterface;
|
|
31
|
+
if (!candidate || typeof candidate !== "object") throw new Error([
|
|
32
|
+
`${where}: expected an adapter object.`,
|
|
33
|
+
`Received: ${describeValue(candidate)}`,
|
|
34
|
+
"",
|
|
35
|
+
"Adapter requirements:",
|
|
36
|
+
" - adapter.resolveValue(context) is required",
|
|
37
|
+
" - adapter.resolveCall(context) is required",
|
|
38
|
+
" - adapter.resolveSelector(context) is required",
|
|
39
|
+
" - adapter.externalInterface(context) is required",
|
|
40
|
+
"",
|
|
41
|
+
"resolveValue(context) is called with one of these shapes:",
|
|
42
|
+
" - { kind: \"theme\", path }",
|
|
43
|
+
" - { kind: \"cssVariable\", name, fallback?, definedValue? }",
|
|
44
|
+
" - { kind: \"importedValue\", importedName, source, path? }",
|
|
45
|
+
"",
|
|
46
|
+
"resolveCall(context) is called with:",
|
|
47
|
+
" - { callSiteFilePath, calleeImportedName, calleeSource, args }",
|
|
48
|
+
"",
|
|
49
|
+
"resolveSelector(context) is called with:",
|
|
50
|
+
" - { kind: \"selectorInterpolation\", importedName, source, path? }",
|
|
51
|
+
"",
|
|
52
|
+
`Docs/examples: ${ADAPTER_DOCS_URL}`
|
|
53
|
+
].join("\n"));
|
|
54
|
+
if (typeof resolveValue !== "function") throw new Error([
|
|
55
|
+
`${where}: adapter.resolveValue must be a function.`,
|
|
56
|
+
`Received: resolveValue=${describeValue(resolveValue)}`,
|
|
57
|
+
"",
|
|
58
|
+
"Adapter shape:",
|
|
59
|
+
" {",
|
|
60
|
+
" resolveValue(context) {",
|
|
61
|
+
" // theme/cssVariable -> { expr, imports, dropDefinition? } | null",
|
|
62
|
+
" }",
|
|
63
|
+
" resolveCall(context) { return { expr, imports } | null }",
|
|
64
|
+
" }",
|
|
65
|
+
"",
|
|
66
|
+
`Docs/examples: ${ADAPTER_DOCS_URL}`
|
|
67
|
+
].join("\n"));
|
|
68
|
+
if (typeof resolveCall !== "function") throw new Error([
|
|
69
|
+
`${where}: adapter.resolveCall must be a function.`,
|
|
70
|
+
`Received: resolveCall=${describeValue(resolveCall)}`,
|
|
71
|
+
"",
|
|
72
|
+
"Adapter shape:",
|
|
73
|
+
" {",
|
|
74
|
+
" resolveCall(context) { return { expr: string, imports: ImportSpec[] } | null }",
|
|
75
|
+
" }",
|
|
76
|
+
"",
|
|
77
|
+
`Docs/examples: ${ADAPTER_DOCS_URL}`
|
|
78
|
+
].join("\n"));
|
|
79
|
+
if (typeof resolveSelector !== "function") throw new Error([
|
|
80
|
+
`${where}: adapter.resolveSelector must be a function.`,
|
|
81
|
+
`Received: resolveSelector=${describeValue(resolveSelector)}`,
|
|
82
|
+
"",
|
|
83
|
+
"Adapter shape:",
|
|
84
|
+
" {",
|
|
85
|
+
" resolveSelector(context) { return { kind: \"media\", expr: string, imports: ImportSpec[] } | undefined }",
|
|
86
|
+
" }",
|
|
87
|
+
"",
|
|
88
|
+
`Docs/examples: ${ADAPTER_DOCS_URL}`
|
|
89
|
+
].join("\n"));
|
|
90
|
+
if (typeof externalInterface !== "function") throw new Error([`${where}: adapter.externalInterface must be a function.`, `Received: externalInterface=${describeValue(externalInterface)}`].join("\n"));
|
|
91
|
+
const styleMerger = obj?.styleMerger;
|
|
92
|
+
if (styleMerger !== null && styleMerger !== void 0) {
|
|
93
|
+
if (typeof styleMerger !== "object") throw new Error([
|
|
94
|
+
`${where}: adapter.styleMerger must be null or an object.`,
|
|
95
|
+
`Received: styleMerger=${describeValue(styleMerger)}`,
|
|
96
|
+
"",
|
|
97
|
+
"Expected shape:",
|
|
98
|
+
" {",
|
|
99
|
+
" functionName: \"stylexProps\",",
|
|
100
|
+
" importSource: { kind: \"specifier\", value: \"@company/ui-utils\" }",
|
|
101
|
+
" }"
|
|
102
|
+
].join("\n"));
|
|
103
|
+
const { functionName, importSource } = styleMerger;
|
|
104
|
+
if (typeof functionName !== "string" || !functionName.trim()) throw new Error([`${where}: adapter.styleMerger.functionName must be a non-empty string.`, `Received: functionName=${describeValue(functionName)}`].join("\n"));
|
|
105
|
+
if (!importSource || typeof importSource !== "object") throw new Error([
|
|
106
|
+
`${where}: adapter.styleMerger.importSource must be an object.`,
|
|
107
|
+
`Received: importSource=${describeValue(importSource)}`,
|
|
108
|
+
"",
|
|
109
|
+
"Expected shape:",
|
|
110
|
+
" { kind: \"specifier\", value: \"@company/ui-utils\" }",
|
|
111
|
+
" or",
|
|
112
|
+
" { kind: \"absolutePath\", value: \"/path/to/module.ts\" }"
|
|
113
|
+
].join("\n"));
|
|
114
|
+
const { kind, value } = importSource;
|
|
115
|
+
if (kind !== "specifier" && kind !== "absolutePath") throw new Error([`${where}: adapter.styleMerger.importSource.kind must be "specifier" or "absolutePath".`, `Received: kind=${describeValue(kind)}`].join("\n"));
|
|
116
|
+
if (typeof value !== "string" || !value.trim()) throw new Error([`${where}: adapter.styleMerger.importSource.value must be a non-empty string.`, `Received: value=${describeValue(value)}`].join("\n"));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const ADAPTER_DOCS_URL = `https://github.com/skovhus/styled-components-to-stylex-codemod#adapter`;
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/internal/logger.ts
|
|
123
|
+
/**
|
|
124
|
+
* Logger and warning types for transform diagnostics.
|
|
125
|
+
* Core concepts: severity classification and source context reporting.
|
|
126
|
+
*/
|
|
127
|
+
var Logger = class Logger {
|
|
128
|
+
/**
|
|
129
|
+
* Log a warning message to stdout.
|
|
130
|
+
* All codemod warnings go through this so tests can mock it.
|
|
131
|
+
*/
|
|
132
|
+
static warn(message, context) {
|
|
133
|
+
Logger.writeWithSpacing(message, context);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Log an error message to stdout with file path and optional location.
|
|
137
|
+
* Formats like warnings: "Error filepath:line:column\nmessage"
|
|
138
|
+
*/
|
|
139
|
+
static logError(message, filePath, loc, context) {
|
|
140
|
+
const location = loc ? `${filePath}:${loc.line}:${loc.column}` : filePath;
|
|
141
|
+
const label = Logger.colorizeErrorLabel("Error");
|
|
142
|
+
Logger.writeWithSpacing(`${label} ${location}\n${message}`, context);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Log transform warnings to stdout and collect them.
|
|
146
|
+
*/
|
|
147
|
+
static logWarnings(warnings, filePath) {
|
|
148
|
+
for (const warning of warnings) {
|
|
149
|
+
Logger.collected.push({
|
|
150
|
+
...warning,
|
|
151
|
+
filePath
|
|
152
|
+
});
|
|
153
|
+
const location = warning.loc ? `${filePath}:${warning.loc.line}:${warning.loc.column}` : `${filePath}`;
|
|
154
|
+
const label = Logger.colorizeSeverityLabel(warning.severity);
|
|
155
|
+
Logger.writeWithSpacing(`${label} ${location}\n${warning.type}`, warning.context);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Create a report from all collected warnings.
|
|
160
|
+
*/
|
|
161
|
+
static createReport() {
|
|
162
|
+
return new LoggerReport([...Logger.collected]);
|
|
163
|
+
}
|
|
164
|
+
/** @internal - for testing only */
|
|
165
|
+
static _clearCollected() {
|
|
166
|
+
Logger.collected = [];
|
|
167
|
+
}
|
|
168
|
+
static collected = [];
|
|
169
|
+
static writeWithSpacing(message, context) {
|
|
170
|
+
const trimmed = message.replace(/\s+$/u, "");
|
|
171
|
+
const serialized = Logger.formatContext(context);
|
|
172
|
+
process.stdout.write(`${trimmed}${serialized ? `\n${serialized}` : ""}\n\n`);
|
|
173
|
+
}
|
|
174
|
+
static colorizeSeverityLabel(severity) {
|
|
175
|
+
if (severity === "error") return Logger.colorizeErrorLabel("Error");
|
|
176
|
+
if (severity === "info") return Logger.colorizeInfoLabel("Info");
|
|
177
|
+
return Logger.colorizeWarnLabel("Warning");
|
|
178
|
+
}
|
|
179
|
+
static colorizeWarnLabel(label) {
|
|
180
|
+
if (!process.stdout.isTTY) return label;
|
|
181
|
+
return `${WARN_BG_COLOR}${WARN_TEXT_COLOR}${label}${RESET_COLOR}`;
|
|
182
|
+
}
|
|
183
|
+
static colorizeErrorLabel(label) {
|
|
184
|
+
if (!process.stdout.isTTY) return label;
|
|
185
|
+
return `${ERROR_BG_COLOR}${ERROR_TEXT_COLOR}${label}${RESET_COLOR}`;
|
|
186
|
+
}
|
|
187
|
+
static colorizeInfoLabel(label) {
|
|
188
|
+
if (!process.stdout.isTTY) return label;
|
|
189
|
+
return `${INFO_BG_COLOR}${INFO_TEXT_COLOR}${label}${RESET_COLOR}`;
|
|
190
|
+
}
|
|
191
|
+
static formatContext(context) {
|
|
192
|
+
if (typeof context === "undefined") return null;
|
|
193
|
+
return JSON.stringify(context, null, 2);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var LoggerReport = class {
|
|
197
|
+
warnings;
|
|
198
|
+
fileCache = /* @__PURE__ */ new Map();
|
|
199
|
+
constructor(warnings) {
|
|
200
|
+
this.warnings = warnings;
|
|
201
|
+
}
|
|
202
|
+
getWarnings() {
|
|
203
|
+
return this.warnings;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get the formatted report as a string.
|
|
207
|
+
*/
|
|
208
|
+
toString() {
|
|
209
|
+
if (this.warnings.length === 0) return "";
|
|
210
|
+
const lines = [];
|
|
211
|
+
const groups = this.groupWarnings();
|
|
212
|
+
lines.push("");
|
|
213
|
+
lines.push("─".repeat(60));
|
|
214
|
+
lines.push(`Warning Summary: ${this.warnings.length} warning(s) in ${groups.length} category(s)`);
|
|
215
|
+
lines.push("─".repeat(60));
|
|
216
|
+
const MAX_EXAMPLES = 15;
|
|
217
|
+
for (const group of groups) {
|
|
218
|
+
lines.push("");
|
|
219
|
+
lines.push(`▸ ${group.message} (${group.warnings.length})`);
|
|
220
|
+
lines.push("");
|
|
221
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
222
|
+
const uniqueLocations = [];
|
|
223
|
+
for (const loc of group.warnings) if (!seenFiles.has(loc.filePath)) {
|
|
224
|
+
seenFiles.add(loc.filePath);
|
|
225
|
+
uniqueLocations.push(loc);
|
|
226
|
+
}
|
|
227
|
+
const displayed = uniqueLocations.slice(0, MAX_EXAMPLES);
|
|
228
|
+
for (const loc of displayed) {
|
|
229
|
+
const location = loc.loc ? `${loc.filePath}:${loc.loc.line}:${loc.loc.column}` : loc.filePath;
|
|
230
|
+
lines.push(` ${location}`);
|
|
231
|
+
if (loc.snippet) lines.push(loc.snippet);
|
|
232
|
+
lines.push("");
|
|
233
|
+
}
|
|
234
|
+
const remaining = uniqueLocations.length - MAX_EXAMPLES;
|
|
235
|
+
if (remaining > 0) {
|
|
236
|
+
lines.push(` ... and ${remaining} more file(s)`);
|
|
237
|
+
lines.push("");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Print the formatted warning report to stdout.
|
|
244
|
+
*/
|
|
245
|
+
print() {
|
|
246
|
+
const output = this.toString();
|
|
247
|
+
if (output) {
|
|
248
|
+
const colored = output.replace(/▸ (.+?) \((\d+)\)/g, `${SECTION_COLOR}▸ $1 ($2)${RESET_COLOR}`);
|
|
249
|
+
process.stdout.write(colored + "\n");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
groupWarnings() {
|
|
253
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
254
|
+
for (const warning of this.warnings) {
|
|
255
|
+
const enrichedWarning = {
|
|
256
|
+
...warning,
|
|
257
|
+
snippet: warning.loc ? this.getSnippet(warning.filePath, warning.loc) : void 0
|
|
258
|
+
};
|
|
259
|
+
const existing = groupMap.get(warning.type);
|
|
260
|
+
if (existing) existing.warnings.push(enrichedWarning);
|
|
261
|
+
else groupMap.set(warning.type, {
|
|
262
|
+
message: warning.type,
|
|
263
|
+
warnings: [enrichedWarning]
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return Array.from(groupMap.values()).sort((a, b) => b.warnings.length - a.warnings.length);
|
|
267
|
+
}
|
|
268
|
+
getSnippet(filePath, loc) {
|
|
269
|
+
if (!loc) return;
|
|
270
|
+
const lines = this.getFileLines(filePath);
|
|
271
|
+
if (!lines) return;
|
|
272
|
+
const lineIndex = loc.line - 1;
|
|
273
|
+
if (lineIndex < 0 || lineIndex >= lines.length) return;
|
|
274
|
+
const snippetLines = [];
|
|
275
|
+
const startLine = Math.max(0, lineIndex - 2);
|
|
276
|
+
const endLine = Math.min(lines.length - 1, lineIndex + 4);
|
|
277
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
278
|
+
const lineNum = String(i + 1).padStart(4, " ");
|
|
279
|
+
const marker = i === lineIndex ? ">" : " ";
|
|
280
|
+
snippetLines.push(` ${marker} ${lineNum} | ${lines[i]}`);
|
|
281
|
+
}
|
|
282
|
+
return snippetLines.join("\n");
|
|
283
|
+
}
|
|
284
|
+
getFileLines(filePath) {
|
|
285
|
+
if (this.fileCache.has(filePath)) return this.fileCache.get(filePath) ?? null;
|
|
286
|
+
try {
|
|
287
|
+
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
288
|
+
this.fileCache.set(filePath, lines);
|
|
289
|
+
return lines;
|
|
290
|
+
} catch {
|
|
291
|
+
this.fileCache.set(filePath, null);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const WARN_BG_COLOR = "\x1B[43m";
|
|
297
|
+
const WARN_TEXT_COLOR = "\x1B[30m";
|
|
298
|
+
const ERROR_BG_COLOR = "\x1B[41m";
|
|
299
|
+
const ERROR_TEXT_COLOR = "\x1B[37m";
|
|
300
|
+
const INFO_BG_COLOR = "\x1B[44m";
|
|
301
|
+
const INFO_TEXT_COLOR = "\x1B[37m";
|
|
302
|
+
const SECTION_COLOR = "\x1B[36m";
|
|
303
|
+
const RESET_COLOR = "\x1B[0m";
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
export { assertValidAdapter as n, describeValue as r, Logger as t };
|