styled-components-to-stylex-codemod 0.0.9 → 0.0.11
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 +60 -34
- package/dist/index.d.mts +6 -1
- package/dist/index.mjs +51 -21
- package/dist/logger-BkySFMoy.d.mts +473 -0
- package/dist/logger-D3j-qxgZ.mjs +339 -0
- package/dist/transform.d.mts +1 -2
- package/dist/transform.mjs +19422 -10039
- package/package.json +34 -27
- package/dist/logger-CNmtK-uJ.d.mts +0 -250
- package/dist/logger-DKelw2HS.mjs +0 -177
|
@@ -0,0 +1,339 @@
|
|
|
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\" | \"pseudoAlias\", ... } | 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
|
+
/**
|
|
128
|
+
* When fileCount <= this threshold, warnings are printed per-file inline and
|
|
129
|
+
* the summary is skipped. Above the threshold only the summary is printed.
|
|
130
|
+
*/
|
|
131
|
+
const FILE_COUNT_INLINE_THRESHOLD = 10;
|
|
132
|
+
var Logger = class Logger {
|
|
133
|
+
/**
|
|
134
|
+
* Set the total number of files being transformed.
|
|
135
|
+
* Controls whether warnings are printed per-file or only in the summary.
|
|
136
|
+
*/
|
|
137
|
+
static setFileCount(count) {
|
|
138
|
+
Logger.fileCount = count;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set the maximum number of examples shown per warning category in the summary.
|
|
142
|
+
*/
|
|
143
|
+
static setMaxExamples(count) {
|
|
144
|
+
Logger.maxExamples = count;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Log a warning message to stdout.
|
|
148
|
+
* All codemod warnings go through this so tests can mock it.
|
|
149
|
+
*/
|
|
150
|
+
static warn(message, context) {
|
|
151
|
+
Logger.writeWithSpacing(message, context);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Log an error message to stdout with file path and optional location.
|
|
155
|
+
* Formats like warnings: "Error filepath:line:column\nmessage"
|
|
156
|
+
* Always prints regardless of file count.
|
|
157
|
+
*/
|
|
158
|
+
static logError(message, filePath, loc, context) {
|
|
159
|
+
const location = loc ? `${filePath}:${loc.line}:${loc.column}` : filePath;
|
|
160
|
+
const label = Logger.colorizeErrorLabel("Error");
|
|
161
|
+
Logger.writeWithSpacing(`${label} ${location}\n${message}`, context);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Collect transform warnings and optionally print them per-file.
|
|
165
|
+
* Per-file output is shown when fileCount is unknown or <= threshold.
|
|
166
|
+
* When fileCount > threshold, warnings are only collected for the summary.
|
|
167
|
+
*/
|
|
168
|
+
static logWarnings(warnings, filePath) {
|
|
169
|
+
const printInline = Logger.fileCount === null || Logger.fileCount <= FILE_COUNT_INLINE_THRESHOLD;
|
|
170
|
+
for (const warning of warnings) {
|
|
171
|
+
Logger.collected.push({
|
|
172
|
+
...warning,
|
|
173
|
+
filePath
|
|
174
|
+
});
|
|
175
|
+
if (printInline) {
|
|
176
|
+
const location = warning.loc ? `${filePath}:${warning.loc.line}:${warning.loc.column}` : `${filePath}`;
|
|
177
|
+
const label = Logger.colorizeSeverityLabel(warning.severity);
|
|
178
|
+
Logger.writeWithSpacing(`${label} ${location}\n${warning.type}`, warning.context);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Create a report from all collected warnings.
|
|
184
|
+
*/
|
|
185
|
+
static createReport() {
|
|
186
|
+
return new LoggerReport([...Logger.collected], Logger.fileCount, Logger.maxExamples);
|
|
187
|
+
}
|
|
188
|
+
/** @internal - for testing only */
|
|
189
|
+
static _clearCollected() {
|
|
190
|
+
Logger.collected = [];
|
|
191
|
+
Logger.fileCount = null;
|
|
192
|
+
}
|
|
193
|
+
static collected = [];
|
|
194
|
+
static fileCount = null;
|
|
195
|
+
static maxExamples = 15;
|
|
196
|
+
static writeWithSpacing(message, context) {
|
|
197
|
+
const trimmed = message.replace(/\s+$/u, "");
|
|
198
|
+
const serialized = Logger.formatContext(context);
|
|
199
|
+
process.stdout.write(`${trimmed}${serialized ? `\n${serialized}` : ""}\n\n`);
|
|
200
|
+
}
|
|
201
|
+
static colorizeSeverityLabel(severity) {
|
|
202
|
+
if (severity === "error") return Logger.colorizeErrorLabel("Error");
|
|
203
|
+
if (severity === "info") return Logger.colorizeInfoLabel("Info");
|
|
204
|
+
return Logger.colorizeWarnLabel("Warning");
|
|
205
|
+
}
|
|
206
|
+
static colorizeWarnLabel(label) {
|
|
207
|
+
if (!process.stdout.isTTY) return label;
|
|
208
|
+
return `${WARN_BG_COLOR}${WARN_TEXT_COLOR}${label}${RESET_COLOR}`;
|
|
209
|
+
}
|
|
210
|
+
static colorizeErrorLabel(label) {
|
|
211
|
+
if (!process.stdout.isTTY) return label;
|
|
212
|
+
return `${ERROR_BG_COLOR}${ERROR_TEXT_COLOR}${label}${RESET_COLOR}`;
|
|
213
|
+
}
|
|
214
|
+
static colorizeInfoLabel(label) {
|
|
215
|
+
if (!process.stdout.isTTY) return label;
|
|
216
|
+
return `${INFO_BG_COLOR}${INFO_TEXT_COLOR}${label}${RESET_COLOR}`;
|
|
217
|
+
}
|
|
218
|
+
static formatContext(context) {
|
|
219
|
+
if (typeof context === "undefined") return null;
|
|
220
|
+
return JSON.stringify(context, null, 2);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var LoggerReport = class {
|
|
224
|
+
warnings;
|
|
225
|
+
fileCount;
|
|
226
|
+
maxExamples;
|
|
227
|
+
fileCache = /* @__PURE__ */ new Map();
|
|
228
|
+
constructor(warnings, fileCount, maxExamples = 15) {
|
|
229
|
+
this.warnings = warnings;
|
|
230
|
+
this.fileCount = fileCount;
|
|
231
|
+
this.maxExamples = maxExamples;
|
|
232
|
+
}
|
|
233
|
+
getWarnings() {
|
|
234
|
+
return this.warnings;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get the formatted report as a string.
|
|
238
|
+
*/
|
|
239
|
+
toString() {
|
|
240
|
+
if (this.warnings.length === 0) return "";
|
|
241
|
+
const lines = [];
|
|
242
|
+
const groups = this.groupWarnings();
|
|
243
|
+
lines.push("");
|
|
244
|
+
lines.push("─".repeat(60));
|
|
245
|
+
lines.push(`Warning Summary: ${this.warnings.length} warning(s) in ${groups.length} category(s)`);
|
|
246
|
+
lines.push("─".repeat(60));
|
|
247
|
+
const MAX_EXAMPLES = this.maxExamples;
|
|
248
|
+
for (const group of groups) {
|
|
249
|
+
lines.push("");
|
|
250
|
+
lines.push(`▸ ${group.message} (${group.warnings.length})`);
|
|
251
|
+
lines.push("");
|
|
252
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
253
|
+
const uniqueLocations = [];
|
|
254
|
+
for (const loc of group.warnings) if (!seenFiles.has(loc.filePath)) {
|
|
255
|
+
seenFiles.add(loc.filePath);
|
|
256
|
+
uniqueLocations.push(loc);
|
|
257
|
+
}
|
|
258
|
+
const displayed = uniqueLocations.slice(0, MAX_EXAMPLES);
|
|
259
|
+
for (const loc of displayed) {
|
|
260
|
+
const location = loc.loc ? `${loc.filePath}:${loc.loc.line}:${loc.loc.column}` : loc.filePath;
|
|
261
|
+
lines.push(` ${location}`);
|
|
262
|
+
if (loc.snippet) lines.push(loc.snippet);
|
|
263
|
+
lines.push("");
|
|
264
|
+
}
|
|
265
|
+
const remaining = uniqueLocations.length - MAX_EXAMPLES;
|
|
266
|
+
if (remaining > 0) {
|
|
267
|
+
lines.push(` ... and ${remaining} more file(s)`);
|
|
268
|
+
lines.push("");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Print the formatted warning report to stdout.
|
|
275
|
+
* Skips the summary when fileCount <= threshold (warnings already shown inline).
|
|
276
|
+
*/
|
|
277
|
+
print() {
|
|
278
|
+
if (this.fileCount !== null && this.fileCount <= FILE_COUNT_INLINE_THRESHOLD) return;
|
|
279
|
+
const output = this.toString();
|
|
280
|
+
if (output) {
|
|
281
|
+
const colored = output.replace(/▸ (.+?) \((\d+)\)/g, `${SECTION_COLOR}▸ $1 ($2)${RESET_COLOR}`);
|
|
282
|
+
process.stdout.write(colored + "\n");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
groupWarnings() {
|
|
286
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
287
|
+
for (const warning of this.warnings) {
|
|
288
|
+
const enrichedWarning = {
|
|
289
|
+
...warning,
|
|
290
|
+
snippet: warning.loc ? this.getSnippet(warning.filePath, warning.loc) : void 0
|
|
291
|
+
};
|
|
292
|
+
const existing = groupMap.get(warning.type);
|
|
293
|
+
if (existing) existing.warnings.push(enrichedWarning);
|
|
294
|
+
else groupMap.set(warning.type, {
|
|
295
|
+
message: warning.type,
|
|
296
|
+
warnings: [enrichedWarning]
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return Array.from(groupMap.values()).sort((a, b) => b.warnings.length - a.warnings.length);
|
|
300
|
+
}
|
|
301
|
+
getSnippet(filePath, loc) {
|
|
302
|
+
if (!loc) return;
|
|
303
|
+
const lines = this.getFileLines(filePath);
|
|
304
|
+
if (!lines) return;
|
|
305
|
+
const lineIndex = loc.line - 1;
|
|
306
|
+
if (lineIndex < 0 || lineIndex >= lines.length) return;
|
|
307
|
+
const snippetLines = [];
|
|
308
|
+
const startLine = Math.max(0, lineIndex - 2);
|
|
309
|
+
const endLine = Math.min(lines.length - 1, lineIndex + 4);
|
|
310
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
311
|
+
const lineNum = String(i + 1).padStart(4, " ");
|
|
312
|
+
const marker = i === lineIndex ? ">" : " ";
|
|
313
|
+
snippetLines.push(` ${marker} ${lineNum} | ${lines[i]}`);
|
|
314
|
+
}
|
|
315
|
+
return snippetLines.join("\n");
|
|
316
|
+
}
|
|
317
|
+
getFileLines(filePath) {
|
|
318
|
+
if (this.fileCache.has(filePath)) return this.fileCache.get(filePath) ?? null;
|
|
319
|
+
try {
|
|
320
|
+
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
321
|
+
this.fileCache.set(filePath, lines);
|
|
322
|
+
return lines;
|
|
323
|
+
} catch {
|
|
324
|
+
this.fileCache.set(filePath, null);
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const WARN_BG_COLOR = "\x1B[43m";
|
|
330
|
+
const WARN_TEXT_COLOR = "\x1B[30m";
|
|
331
|
+
const ERROR_BG_COLOR = "\x1B[41m";
|
|
332
|
+
const ERROR_TEXT_COLOR = "\x1B[37m";
|
|
333
|
+
const INFO_BG_COLOR = "\x1B[44m";
|
|
334
|
+
const INFO_TEXT_COLOR = "\x1B[37m";
|
|
335
|
+
const SECTION_COLOR = "\x1B[36m";
|
|
336
|
+
const RESET_COLOR = "\x1B[0m";
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
export { assertValidAdapter as n, describeValue as r, Logger as t };
|
package/dist/transform.d.mts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { n as WarningLog, r as Adapter } from "./logger-
|
|
1
|
+
import { n as WarningLog, r as Adapter } from "./logger-BkySFMoy.mjs";
|
|
2
2
|
import "stylis";
|
|
3
3
|
import { API, FileInfo, Options } from "jscodeshift";
|
|
4
4
|
|
|
5
5
|
//#region src/internal/transform-types.d.ts
|
|
6
|
-
|
|
7
6
|
/**
|
|
8
7
|
* Result of the transform including any log entries
|
|
9
8
|
*/
|