styled-components-to-stylex-codemod 0.0.13 → 0.0.15
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 +136 -143
- package/dist/bridge-consumer-patcher-D3fRIEkZ.mjs +122 -0
- package/dist/index.d.mts +26 -4
- package/dist/index.mjs +178 -39
- package/dist/{logger-kU4pnRpt.d.mts → logger-B7SOfCti.d.mts} +41 -3
- package/dist/{logger-D3j-qxgZ.mjs → logger-D-R2KB6I.mjs} +29 -4
- package/dist/resolve-imports-BDk6Ms09.mjs +66 -0
- package/dist/run-prepass-BcidTT9f.mjs +885 -0
- package/dist/selector-context-heuristic-CGwiJ3HL.mjs +39 -0
- package/dist/styled-css-DBryFqQM.mjs +38 -0
- package/dist/transform.d.mts +43 -2
- package/dist/transform.mjs +1404 -300
- package/package.json +17 -15
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as describeValue, r as assertValidAdapterInput, t as Logger } from "./logger-D-R2KB6I.mjs";
|
|
2
2
|
import { run } from "jscodeshift/src/Runner.js";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
|
-
import { glob } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
6
|
+
import { glob, writeFile } from "node:fs/promises";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
|
|
9
9
|
//#region src/adapter.ts
|
|
@@ -63,7 +63,7 @@ import { spawn } from "node:child_process";
|
|
|
63
63
|
* });
|
|
64
64
|
*/
|
|
65
65
|
function defineAdapter(adapter) {
|
|
66
|
-
|
|
66
|
+
assertValidAdapterInput(adapter, "defineAdapter(adapter)");
|
|
67
67
|
return adapter;
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -94,6 +94,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
94
94
|
*
|
|
95
95
|
* await runTransform({
|
|
96
96
|
* files: 'src/**\/*.tsx',
|
|
97
|
+
* consumerPaths: null,
|
|
97
98
|
* adapter,
|
|
98
99
|
* dryRun: true,
|
|
99
100
|
* });
|
|
@@ -108,7 +109,7 @@ async function runTransform(options) {
|
|
|
108
109
|
"Example (plain JS):",
|
|
109
110
|
" import { runTransform, defineAdapter } from \"styled-components-to-stylex-codemod\";",
|
|
110
111
|
" const adapter = defineAdapter({ resolveValue() { return null; } });",
|
|
111
|
-
" await runTransform({ files: \"src/**/*.tsx\", adapter });"
|
|
112
|
+
" await runTransform({ files: \"src/**/*.tsx\", consumerPaths: null, adapter });"
|
|
112
113
|
].join("\n"));
|
|
113
114
|
const filesValue = options.files;
|
|
114
115
|
if (typeof filesValue !== "string" && !Array.isArray(filesValue)) throw new Error([
|
|
@@ -121,47 +122,52 @@ async function runTransform(options) {
|
|
|
121
122
|
if (filesValue.length === 0) throw new Error(["runTransform(options): `files` must not be an empty array.", "Example: files: [\"src/**/*.ts\", \"src/**/*.tsx\"]"].join("\n"));
|
|
122
123
|
if (filesValue.find((p) => typeof p !== "string" || p.trim() === "") !== void 0) throw new Error(["runTransform(options): `files` array must contain non-empty strings.", `Received: files=${describeValue(filesValue)}`].join("\n"));
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
+
if (options.consumerPaths === void 0) throw new Error([
|
|
126
|
+
"runTransform(options): `consumerPaths` is required.",
|
|
127
|
+
"Pass a glob pattern to enable cross-file selector scanning, or `null` to opt out.",
|
|
128
|
+
"Example: consumerPaths: \"src/**/*.tsx\" // scan for cross-file usage",
|
|
129
|
+
"Example: consumerPaths: null // opt out"
|
|
130
|
+
].join("\n"));
|
|
131
|
+
const { files, consumerPaths: consumerPathsOption, dryRun = false, print = false, parser = "tsx", formatterCommands, maxExamples } = options;
|
|
125
132
|
if (maxExamples !== void 0) Logger.setMaxExamples(maxExamples);
|
|
126
|
-
const
|
|
127
|
-
|
|
133
|
+
const adapterInput = options.adapter;
|
|
134
|
+
assertValidAdapterInput(adapterInput, "runTransform(options)");
|
|
135
|
+
if (adapterInput.externalInterface === "auto" && consumerPathsOption === null) throw new Error([
|
|
136
|
+
"runTransform(options): externalInterface is \"auto\" but consumerPaths is null.",
|
|
137
|
+
"Auto-detection needs consumer file globs to scan for styled(Component) and as-prop usage.",
|
|
138
|
+
"Example: consumerPaths: \"src/**/*.tsx\""
|
|
139
|
+
].join("\n"));
|
|
128
140
|
const resolveValueWithLogging = (ctx) => {
|
|
129
141
|
try {
|
|
130
|
-
return
|
|
142
|
+
return adapterInput.resolveValue(ctx);
|
|
131
143
|
} catch (e) {
|
|
132
144
|
const msg = `adapter.resolveValue threw an error: ${e instanceof Error ? e.message : String(e)}`;
|
|
133
145
|
const filePath = ctx.filePath ?? "<unknown>";
|
|
134
146
|
Logger.logError(msg, filePath, ctx.loc, ctx);
|
|
147
|
+
Logger.markErrorAsLogged(e);
|
|
135
148
|
throw e;
|
|
136
149
|
}
|
|
137
150
|
};
|
|
138
151
|
const resolveCallWithLogging = (ctx) => {
|
|
139
152
|
try {
|
|
140
|
-
return
|
|
153
|
+
return adapterInput.resolveCall(ctx);
|
|
141
154
|
} catch (e) {
|
|
142
155
|
const msg = `adapter.resolveCall threw an error: ${e instanceof Error ? e.message : String(e)}`;
|
|
143
156
|
Logger.logError(msg, ctx.callSiteFilePath, ctx.loc, ctx);
|
|
157
|
+
Logger.markErrorAsLogged(e);
|
|
144
158
|
throw e;
|
|
145
159
|
}
|
|
146
160
|
};
|
|
147
161
|
const resolveSelectorWithLogging = (ctx) => {
|
|
148
162
|
try {
|
|
149
|
-
return
|
|
163
|
+
return adapterInput.resolveSelector(ctx);
|
|
150
164
|
} catch (e) {
|
|
151
165
|
const msg = `adapter.resolveSelector threw an error: ${e instanceof Error ? e.message : String(e)}`;
|
|
152
166
|
Logger.logError(msg, ctx.filePath, ctx.loc, ctx);
|
|
167
|
+
Logger.markErrorAsLogged(e);
|
|
153
168
|
throw e;
|
|
154
169
|
}
|
|
155
170
|
};
|
|
156
|
-
const adapterWithLogging = {
|
|
157
|
-
styleMerger: adapter.styleMerger,
|
|
158
|
-
externalInterface(ctx) {
|
|
159
|
-
return adapter.externalInterface(ctx);
|
|
160
|
-
},
|
|
161
|
-
resolveValue: resolveValueWithLogging,
|
|
162
|
-
resolveCall: resolveCallWithLogging,
|
|
163
|
-
resolveSelector: resolveSelectorWithLogging
|
|
164
|
-
};
|
|
165
171
|
const patterns = Array.isArray(files) ? files : [files];
|
|
166
172
|
const filePaths = [];
|
|
167
173
|
const cwd = process.cwd();
|
|
@@ -178,7 +184,73 @@ async function runTransform(options) {
|
|
|
178
184
|
};
|
|
179
185
|
}
|
|
180
186
|
Logger.setFileCount(filePaths.length);
|
|
181
|
-
const
|
|
187
|
+
const consumerPatterns = consumerPathsOption ? Array.isArray(consumerPathsOption) ? consumerPathsOption : [consumerPathsOption] : [];
|
|
188
|
+
const consumerFilePaths = [];
|
|
189
|
+
for (const pattern of consumerPatterns) for await (const file of glob(pattern, { cwd })) consumerFilePaths.push(file);
|
|
190
|
+
if (consumerPatterns.length > 0 && consumerFilePaths.length === 0) throw new Error([
|
|
191
|
+
"runTransform(options): consumerPaths matched no files.",
|
|
192
|
+
`Pattern(s): ${consumerPatterns.join(", ")}`,
|
|
193
|
+
"Check that the glob pattern is correct and files exist."
|
|
194
|
+
].join("\n"));
|
|
195
|
+
const { createModuleResolver } = await import("./resolve-imports-BDk6Ms09.mjs");
|
|
196
|
+
const sharedResolver = createModuleResolver();
|
|
197
|
+
const { runPrepass } = await import("./run-prepass-BcidTT9f.mjs");
|
|
198
|
+
const absoluteFiles = filePaths.map((f) => resolve(f));
|
|
199
|
+
const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
|
|
200
|
+
let prepassResult;
|
|
201
|
+
try {
|
|
202
|
+
prepassResult = await runPrepass({
|
|
203
|
+
filesToTransform: absoluteFiles,
|
|
204
|
+
consumerPaths: absoluteConsumers,
|
|
205
|
+
resolver: sharedResolver,
|
|
206
|
+
parserName: parser,
|
|
207
|
+
createExternalInterface: adapterInput.externalInterface === "auto",
|
|
208
|
+
enableAstCache: true
|
|
209
|
+
});
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (adapterInput.externalInterface === "auto") throw createAutoPrepassFailureError(err, consumerPatterns, parser);
|
|
212
|
+
Logger.warn(`Prepass failed, continuing without cross-file analysis: ${err instanceof Error ? err.message : String(err)}`);
|
|
213
|
+
prepassResult = {
|
|
214
|
+
crossFileInfo: {
|
|
215
|
+
selectorUsages: /* @__PURE__ */ new Map(),
|
|
216
|
+
componentsNeedingMarkerSidecar: /* @__PURE__ */ new Map(),
|
|
217
|
+
componentsNeedingGlobalSelectorBridge: /* @__PURE__ */ new Map()
|
|
218
|
+
},
|
|
219
|
+
consumerAnalysis: void 0
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const crossFilePrepassResult = prepassResult.crossFileInfo;
|
|
223
|
+
const resolvedAdapter = (() => {
|
|
224
|
+
if (adapterInput.externalInterface === "auto" && prepassResult.consumerAnalysis) {
|
|
225
|
+
const analysisMap = prepassResult.consumerAnalysis;
|
|
226
|
+
return {
|
|
227
|
+
...adapterInput,
|
|
228
|
+
externalInterface: (ctx) => {
|
|
229
|
+
let realPath;
|
|
230
|
+
try {
|
|
231
|
+
realPath = realpathSync(resolve(ctx.filePath));
|
|
232
|
+
} catch {
|
|
233
|
+
realPath = resolve(ctx.filePath);
|
|
234
|
+
}
|
|
235
|
+
return analysisMap.get(`${realPath}:${ctx.componentName}`) ?? {
|
|
236
|
+
styles: false,
|
|
237
|
+
as: false
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return adapterInput;
|
|
243
|
+
})();
|
|
244
|
+
const adapterWithLogging = {
|
|
245
|
+
styleMerger: resolvedAdapter.styleMerger,
|
|
246
|
+
externalInterface(ctx) {
|
|
247
|
+
return resolvedAdapter.externalInterface(ctx);
|
|
248
|
+
},
|
|
249
|
+
resolveValue: resolveValueWithLogging,
|
|
250
|
+
resolveCall: resolveCallWithLogging,
|
|
251
|
+
resolveSelector: resolveSelectorWithLogging
|
|
252
|
+
};
|
|
253
|
+
const transformPath = (() => {
|
|
182
254
|
const adjacent = join(__dirname, "transform.mjs");
|
|
183
255
|
if (existsSync(adjacent)) return adjacent;
|
|
184
256
|
const distSibling = join(__dirname, "..", "dist", "transform.mjs");
|
|
@@ -189,31 +261,34 @@ async function runTransform(options) {
|
|
|
189
261
|
` ${distSibling}`,
|
|
190
262
|
"Run `pnpm build` to generate dist artifacts."
|
|
191
263
|
].join("\n"));
|
|
192
|
-
})()
|
|
264
|
+
})();
|
|
265
|
+
const sidecarFiles = /* @__PURE__ */ new Map();
|
|
266
|
+
const bridgeResults = /* @__PURE__ */ new Map();
|
|
267
|
+
const result = await run(transformPath, filePaths, {
|
|
193
268
|
parser,
|
|
194
269
|
dry: dryRun,
|
|
195
270
|
print,
|
|
196
271
|
adapter: adapterWithLogging,
|
|
272
|
+
crossFilePrepassResult,
|
|
273
|
+
sidecarFiles,
|
|
274
|
+
bridgeResults,
|
|
197
275
|
runInBand: true
|
|
198
276
|
});
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
});
|
|
211
|
-
proc.on("error", reject);
|
|
212
|
-
});
|
|
213
|
-
} catch (e) {
|
|
214
|
-
Logger.warn(`Formatter command failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
277
|
+
if (sidecarFiles.size > 0 && !dryRun) for (const [sidecarPath, content] of sidecarFiles) await writeFile(sidecarPath, mergeSidecarContent(sidecarPath, content), "utf-8");
|
|
278
|
+
if (bridgeResults.size > 0 && !dryRun) {
|
|
279
|
+
const { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-D3fRIEkZ.mjs");
|
|
280
|
+
const consumerReplacements = buildConsumerReplacements(crossFilePrepassResult.selectorUsages, bridgeResults);
|
|
281
|
+
const patchedFiles = [];
|
|
282
|
+
for (const [consumerPath, replacements] of consumerReplacements) {
|
|
283
|
+
const patched = patchConsumerFile(consumerPath, replacements);
|
|
284
|
+
if (patched !== null) {
|
|
285
|
+
await writeFile(consumerPath, patched, "utf-8");
|
|
286
|
+
patchedFiles.push(consumerPath);
|
|
287
|
+
}
|
|
215
288
|
}
|
|
289
|
+
if (formatterCommands && patchedFiles.length > 0) await runFormatters(formatterCommands, patchedFiles);
|
|
216
290
|
}
|
|
291
|
+
if (formatterCommands && formatterCommands.length > 0 && result.ok > 0 && !dryRun) await runFormatters(formatterCommands, filePaths);
|
|
217
292
|
const report = Logger.createReport();
|
|
218
293
|
report.print();
|
|
219
294
|
return {
|
|
@@ -225,6 +300,70 @@ async function runTransform(options) {
|
|
|
225
300
|
warnings: report.getWarnings()
|
|
226
301
|
};
|
|
227
302
|
}
|
|
303
|
+
function createAutoPrepassFailureError(err, consumerPatterns, parser) {
|
|
304
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
305
|
+
return new Error([
|
|
306
|
+
"runTransform(options): prepass failed while using externalInterface: \"auto\".",
|
|
307
|
+
"\"auto\" depends on successful prepass scanning and cannot continue without it.",
|
|
308
|
+
`Underlying error: ${reason}`,
|
|
309
|
+
"",
|
|
310
|
+
"Troubleshooting:",
|
|
311
|
+
" - Verify `consumerPaths` glob(s) and file syntax.",
|
|
312
|
+
` - Confirm parser setting matches your code (current parser: ${JSON.stringify(parser)}).`,
|
|
313
|
+
" - Check module resolution inputs (tsconfig paths / imports).",
|
|
314
|
+
" - Use a manual `externalInterface(ctx)` function to continue without auto-detection.",
|
|
315
|
+
"",
|
|
316
|
+
`consumerPaths: ${consumerPatterns.length > 0 ? consumerPatterns.join(", ") : "(none)"}`
|
|
317
|
+
].join("\n"));
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Merge new sidecar marker content into an existing .stylex.ts file, preserving
|
|
321
|
+
* user-owned exports (e.g. defineVars). If the file doesn't exist, returns content as-is.
|
|
322
|
+
*
|
|
323
|
+
* New marker declarations (`export const XMarker = stylex.defineMarker()`) are
|
|
324
|
+
* appended only if they don't already exist in the file. The stylex import is
|
|
325
|
+
* ensured at the top.
|
|
326
|
+
*/
|
|
327
|
+
function mergeSidecarContent(sidecarPath, newContent) {
|
|
328
|
+
let existing;
|
|
329
|
+
try {
|
|
330
|
+
existing = readFileSync(sidecarPath, "utf-8");
|
|
331
|
+
} catch {
|
|
332
|
+
return newContent;
|
|
333
|
+
}
|
|
334
|
+
const markerLineRe = /^export const \w+ = stylex\.defineMarker\(\);$/gm;
|
|
335
|
+
const newMarkers = [];
|
|
336
|
+
for (const m of newContent.matchAll(markerLineRe)) newMarkers.push(m[0]);
|
|
337
|
+
if (newMarkers.length === 0) return newContent;
|
|
338
|
+
const markersToAdd = newMarkers.filter((line) => !existing.includes(line));
|
|
339
|
+
if (markersToAdd.length === 0) return existing;
|
|
340
|
+
let merged = existing;
|
|
341
|
+
if (!merged.includes("@stylexjs/stylex")) merged = `import * as stylex from "@stylexjs/stylex";\n\n${merged}`;
|
|
342
|
+
const trailingNewline = merged.endsWith("\n") ? "" : "\n";
|
|
343
|
+
merged = merged + trailingNewline + markersToAdd.join("\n") + "\n";
|
|
344
|
+
return merged;
|
|
345
|
+
}
|
|
346
|
+
/** Run formatter commands on a list of files, logging warnings on failure. */
|
|
347
|
+
async function runFormatters(commands, files) {
|
|
348
|
+
for (const formatterCommand of commands) {
|
|
349
|
+
const [cmd, ...cmdArgs] = formatterCommand.split(/\s+/);
|
|
350
|
+
if (cmd) try {
|
|
351
|
+
await new Promise((res, rej) => {
|
|
352
|
+
const proc = spawn(cmd, [...cmdArgs, ...files], {
|
|
353
|
+
stdio: "inherit",
|
|
354
|
+
shell: true
|
|
355
|
+
});
|
|
356
|
+
proc.on("close", (code) => {
|
|
357
|
+
if (code === 0) res();
|
|
358
|
+
else rej(/* @__PURE__ */ new Error(`Formatter command exited with code ${code}`));
|
|
359
|
+
});
|
|
360
|
+
proc.on("error", rej);
|
|
361
|
+
});
|
|
362
|
+
} catch (e) {
|
|
363
|
+
Logger.warn(`Formatter command failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
228
367
|
|
|
229
368
|
//#endregion
|
|
230
369
|
export { defineAdapter, runTransform };
|
|
@@ -80,6 +80,12 @@ type CallResolveContext = {
|
|
|
80
80
|
* Example: `import { transitionSpeed as ts } ...; ts("x")` -> "transitionSpeed"
|
|
81
81
|
*/
|
|
82
82
|
calleeImportedName: string;
|
|
83
|
+
/**
|
|
84
|
+
* Member path segments on the callee (e.g., for `ColorConverter.cssWithAlpha()`, `["cssWithAlpha"]`).
|
|
85
|
+
* Only present when the callee is a member expression. The root object's import info
|
|
86
|
+
* is in `calleeImportedName`/`calleeSource`.
|
|
87
|
+
*/
|
|
88
|
+
calleeMemberPath?: string[];
|
|
83
89
|
/**
|
|
84
90
|
* Import source for this call: either an absolute file path (relative imports)
|
|
85
91
|
* or the module specifier (package imports).
|
|
@@ -190,6 +196,16 @@ type CallResolveResult = {
|
|
|
190
196
|
* returns a StyleX styles object even when used with a CSS property like `border:`.
|
|
191
197
|
*/
|
|
192
198
|
usage?: "create" | "props";
|
|
199
|
+
/**
|
|
200
|
+
* When the resolved expression is combined with a dynamic argument (e.g., from a prop),
|
|
201
|
+
* this field controls how they are joined:
|
|
202
|
+
* - `"call"` (default): `resolvedExpr(arg)` — treat as a function call
|
|
203
|
+
* - `"memberAccess"`: `resolvedExpr[arg]` — treat as a computed member access
|
|
204
|
+
*
|
|
205
|
+
* Only relevant when the original code uses a pattern like `${(props) => helper(props.x)}`
|
|
206
|
+
* and the adapter returns a resolved expression for the helper.
|
|
207
|
+
*/
|
|
208
|
+
dynamicArgUsage?: "call" | "memberAccess";
|
|
193
209
|
/**
|
|
194
210
|
* Optional raw CSS text for helpers that return CSS declaration blocks.
|
|
195
211
|
*
|
|
@@ -396,6 +412,28 @@ interface Adapter {
|
|
|
396
412
|
*/
|
|
397
413
|
styleMerger: StyleMergerConfig | null;
|
|
398
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* User-facing adapter input type accepted by `defineAdapter()`.
|
|
417
|
+
*
|
|
418
|
+
* Same as `Adapter` except `externalInterface` may also be the string `"auto"`.
|
|
419
|
+
* When `"auto"` is used, `runTransform()` automatically scans consumer code
|
|
420
|
+
* (using `consumerPaths` / `files` globs) to detect which exported components
|
|
421
|
+
* are re-styled or used with the `as` prop.
|
|
422
|
+
*/
|
|
423
|
+
interface AdapterInput {
|
|
424
|
+
resolveValue: Adapter["resolveValue"];
|
|
425
|
+
resolveCall: Adapter["resolveCall"];
|
|
426
|
+
resolveSelector: Adapter["resolveSelector"];
|
|
427
|
+
/**
|
|
428
|
+
* Called for exported styled components to determine their external interface.
|
|
429
|
+
*
|
|
430
|
+
* - Pass a function for manual control.
|
|
431
|
+
* - Pass `"auto"` to auto-detect usage from consumer code (requires `consumerPaths`
|
|
432
|
+
* in `runTransform()`).
|
|
433
|
+
*/
|
|
434
|
+
externalInterface: "auto" | Adapter["externalInterface"];
|
|
435
|
+
styleMerger: Adapter["styleMerger"];
|
|
436
|
+
}
|
|
399
437
|
/**
|
|
400
438
|
* Helper for nicer user authoring + type inference.
|
|
401
439
|
*
|
|
@@ -447,11 +485,11 @@ interface Adapter {
|
|
|
447
485
|
* styleMerger: null,
|
|
448
486
|
* });
|
|
449
487
|
*/
|
|
450
|
-
declare function defineAdapter(adapter:
|
|
488
|
+
declare function defineAdapter<T extends AdapterInput>(adapter: T): T;
|
|
451
489
|
//#endregion
|
|
452
490
|
//#region src/internal/logger.d.ts
|
|
453
491
|
type Severity = "info" | "warning" | "error";
|
|
454
|
-
type WarningType = "`css` helper function switch must return css templates in all branches" | "`css` helper usage as a function call (css(...)) is not supported" | "`css` helper used outside of a styled component template cannot be statically transformed" | "Adapter helper call in border interpolation did not resolve to a single CSS value" | "Adapter resolveCall returned an unparseable styles expression" | "Adapter resolveCall returned an unparseable value expression" | "Adapter resolveCall returned StyleX styles for helper call where a CSS value was expected" | "Adapter resolveCall returned undefined for helper call" | "Adapter resolved StyleX styles cannot be applied under nested selectors/at-rules" | "Adapter resolved StyleX styles inside pseudo selector but did not provide cssText for property expansion — add cssText to resolveCall result to enable pseudo-wrapping" | 'Adapter resolveCall cssText could not be parsed as CSS declarations — expected semicolon-separated property: value pairs (e.g. "white-space: nowrap; overflow: hidden;")' | "Adapter resolveValue returned an unparseable value expression" | "Adapter resolveValue returned undefined for imported value" | "Arrow function: body is not a recognized pattern (expected ternary, logical, call, or member expression)" | "Arrow function: conditional branches could not be resolved to static or theme values" | "Arrow function: helper call body is not supported" | "Arrow function: indexed theme lookup pattern not matched" | "Arrow function: logical expression pattern not supported" | "Arrow function: prop access cannot be converted to style function for this CSS property" | "Arrow function: theme access path could not be resolved" | "Component selectors like `${OtherComponent}:hover &` are not directly representable in StyleX. Manual refactor is required" | "Conditional `css` block: !important is not supported in StyleX" | "Conditional `css` block: @-rules (e.g., @media, @supports) are not supported" | "Conditional `css` block: failed to parse expression" | "Conditional `css` block: missing CSS property name" | "Conditional `css` block: missing interpolation expression" | "Conditional `css` block: mixed static/dynamic values with non-theme expressions cannot be safely transformed" | "Conditional `css` block: multiple interpolation slots in a single property value" | "Conditional `css` block: ternary branch value could not be resolved (imported values require adapter support)" | "Conditional `css` block: ternary expressions inside pseudo selectors are not supported" | "Conditional `css` block: unsupported selector" | "Directional border helper styles are not supported" | "createGlobalStyle is not supported in StyleX. Global styles should be handled separately (e.g., in a CSS file or using CSS reset libraries)" | "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396" | "Failed to parse theme expressions" | "Heterogeneous background values (mix of gradients and colors) not currently supported" | "Higher-order styled factory wrappers (e.g. hoc(styled)) are not supported" | "Imported CSS helper mixins: cannot determine inherited properties for correct pseudo selector handling" | "Styled-components specificity hacks like `&&` / `&&&` are not representable in StyleX" | "Theme-dependent block-level conditional could not be fully resolved (branches may contain dynamic interpolations)" | "Theme-dependant call expression could not be resolved (e.g. theme helper calls like theme.highlight() are not supported)" | "Theme value with fallback (props.theme.X ?? / || default) cannot be resolved statically — use adapter.resolveValue to map theme paths to StyleX tokens" | "Theme-dependent nested prop access requires a project-specific theme source (e.g. useTheme())" | "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())" | "Theme prop overrides on styled components are not supported" | "Universal selectors (`*`) are currently unsupported" | "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))" | "Unsupported conditional test in shouldForwardProp" | "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)" | "Unsupported interpolation: arrow function" | "Unsupported interpolation: call expression" | "Unsupported interpolation: identifier" | "Unsupported interpolation: member expression" | "Unsupported interpolation: property" | "Unsupported interpolation: unknown" | "Unsupported nested conditional interpolation" | "Unsupported prop-based inline style expression cannot be safely inlined" | "Unsupported prop-based inline style props.theme access is not supported" | "Unsupported selector interpolation: imported value in selector position" | "Unsupported selector: class selector" | "Unsupported selector: comma-separated selectors must all be simple pseudos" | "Unsupported selector: descendant pseudo selector (space before pseudo)" | "Unsupported selector: descendant/child/sibling selector" | "Unsupported selector: interpolated pseudo selector" | "Unsupported selector: sibling combinator" | "Unsupported selector: ambiguous element selector" | "Unsupported selector: attribute selector on unsupported element" | "Unsupported selector: element selector on exported component" | "Unsupported selector: element selector with combined ancestor and child pseudos" | "Unsupported selector: element selector with dynamic children" | "Unsupported selector: element selector with plain intrinsic children" | "Unsupported selector: element selector pseudo collision" | "Unsupported selector: unresolved interpolation in descendant component selector" | "Unsupported selector: unresolved interpolation in element selector" | "Unsupported selector: unresolved interpolation in reverse component selector" | "Unsupported selector: unknown component selector" | "Unsupported css`` mixin: after-base mixin style is not a plain object" | "Unsupported css`` mixin: nested contextual conditions in after-base mixin" | "Unsupported css`` mixin: cannot infer base default for after-base contextual override (base value is non-literal)" | "css`` helper function interpolation references closure variable that cannot be hoisted" | "Using styled-components components as mixins is not supported; use css`` mixins or strings instead";
|
|
492
|
+
type WarningType = "`css` helper function switch must return css templates in all branches" | "`css` helper usage as a function call (css(...)) is not supported" | "`css` helper used outside of a styled component template cannot be statically transformed" | "Adapter helper call in border interpolation did not resolve to a single CSS value" | "Adapter resolveCall returned an unparseable styles expression" | "Adapter resolveCall returned an unparseable value expression" | "Adapter resolveCall returned StyleX styles for helper call where a CSS value was expected" | "Adapter resolveCall returned undefined for helper call" | "Adapter resolved StyleX styles cannot be applied under nested selectors/at-rules" | "Adapter resolved StyleX styles inside pseudo selector but did not provide cssText for property expansion — add cssText to resolveCall result to enable pseudo-wrapping" | 'Adapter resolveCall cssText could not be parsed as CSS declarations — expected semicolon-separated property: value pairs (e.g. "white-space: nowrap; overflow: hidden;")' | "Adapter resolveValue returned an unparseable value expression" | "Adapter resolveValue returned undefined for imported value" | "Arrow function: body is not a recognized pattern (expected ternary, logical, call, or member expression)" | "Arrow function: conditional branches could not be resolved to static or theme values" | "Arrow function: helper call body is not supported" | "Arrow function: indexed theme lookup pattern not matched" | "Arrow function: logical expression pattern not supported" | "Arrow function: prop access cannot be converted to style function for this CSS property" | "Arrow function: theme access path could not be resolved" | "Component selectors like `${OtherComponent}:hover &` are not directly representable in StyleX. Manual refactor is required" | "Conditional `css` block: !important is not supported in StyleX" | "Conditional `css` block: @-rules (e.g., @media, @supports) are not supported" | "Conditional `css` block: failed to parse expression" | "Conditional `css` block: missing CSS property name" | "Conditional `css` block: missing interpolation expression" | "Conditional `css` block: mixed static/dynamic values with non-theme expressions cannot be safely transformed" | "Conditional `css` block: multiple interpolation slots in a single property value" | "Conditional `css` block: ternary branch value could not be resolved (imported values require adapter support)" | "Conditional `css` block: ternary expressions inside pseudo selectors are not supported" | "Conditional `css` block: unsupported selector" | "Directional border helper styles are not supported" | "Multi-slot border interpolation could not be resolved" | "createGlobalStyle is not supported in StyleX. Global styles should be handled separately (e.g., in a CSS file or using CSS reset libraries)" | "Dynamic styles inside pseudo elements (::before/::after) are not supported by StyleX. See https://github.com/facebook/stylex/issues/1396" | "Failed to parse theme expressions" | "Heterogeneous background values (mix of gradients and colors) not currently supported" | "Higher-order styled factory wrappers (e.g. hoc(styled)) are not supported" | "Imported CSS helper mixins: cannot determine inherited properties for correct pseudo selector handling" | "Styled-components specificity hacks like `&&` / `&&&` are not representable in StyleX" | "Theme-dependent block-level conditional could not be fully resolved (branches may contain dynamic interpolations)" | "Theme-dependant call expression could not be resolved (e.g. theme helper calls like theme.highlight() are not supported)" | "Theme value with fallback (props.theme.X ?? / || default) cannot be resolved statically — use adapter.resolveValue to map theme paths to StyleX tokens" | "Theme-dependent nested prop access requires a project-specific theme source (e.g. useTheme())" | "Theme-dependent template literals require a project-specific theme source (e.g. useTheme())" | "Theme prop overrides on styled components are not supported" | "Universal selectors (`*`) are currently unsupported" | "Unsupported call expression (expected imported helper(...) or imported helper(...)(...))" | "Unsupported conditional test in shouldForwardProp" | "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)" | "Unsupported interpolation: arrow function" | "Unsupported interpolation: call expression" | "Unsupported interpolation: identifier" | "Unsupported interpolation: member expression" | "Unsupported interpolation: property" | "Unsupported interpolation: unknown" | "Unsupported nested conditional interpolation" | "Unsupported prop-based inline style expression cannot be safely inlined" | "Unsupported prop-based inline style props.theme access is not supported" | "Unsupported selector interpolation: imported value in selector position" | "Unsupported selector: class selector" | "Unsupported selector: comma-separated selectors must all be simple pseudos or pseudo-elements" | "Unsupported selector: descendant pseudo selector (space before pseudo)" | "Unsupported selector: descendant/child/sibling selector" | "Unsupported selector: interpolated pseudo selector" | "Unsupported selector: sibling combinator" | "Unsupported selector: unresolved interpolation in sibling selector" | "Unsupported selector: ambiguous element selector" | "Unsupported selector: attribute selector on unsupported element" | "Unsupported selector: element selector on exported component" | "Unsupported selector: element selector with combined ancestor and child pseudos" | "Unsupported selector: element selector with dynamic children" | "Unsupported selector: element selector with plain intrinsic children" | "Unsupported selector: element selector pseudo collision" | "Unsupported selector: unresolved interpolation in cross-file component selector" | "Unsupported selector: unresolved interpolation in descendant component selector" | "Unsupported selector: unresolved interpolation in element selector" | "Unsupported selector: unresolved interpolation in reverse component selector" | "Unsupported selector: grouped reverse selector references different components" | "Unsupported selector: unknown component selector" | "Unsupported css`` mixin: after-base mixin style is not a plain object" | "Unsupported css`` mixin: nested contextual conditions in after-base mixin" | "Unsupported css`` mixin: cannot infer base default for after-base contextual override (base value is non-literal)" | "css`` helper function interpolation references closure variable that cannot be hoisted" | "Sibling selector broadened: & + & (adjacent) becomes general sibling (~) in StyleX — interleaved non-matching elements will no longer block the match" | "Using styled-components components as mixins is not supported; use css`` mixins or strings instead";
|
|
455
493
|
interface WarningLog {
|
|
456
494
|
severity: Severity;
|
|
457
495
|
type: WarningType;
|
|
@@ -465,4 +503,4 @@ interface CollectedWarning extends WarningLog {
|
|
|
465
503
|
filePath: string;
|
|
466
504
|
}
|
|
467
505
|
//#endregion
|
|
468
|
-
export { defineAdapter as i, WarningLog as n, Adapter as r, CollectedWarning as t };
|
|
506
|
+
export { defineAdapter as a, AdapterInput as i, WarningLog as n, Adapter as r, CollectedWarning as t };
|
|
@@ -22,7 +22,15 @@ function describeValue(value) {
|
|
|
22
22
|
}
|
|
23
23
|
return "[Unknown]";
|
|
24
24
|
}
|
|
25
|
+
/** Validates that the candidate is a fully-resolved `Adapter` (externalInterface must be a function). */
|
|
25
26
|
function assertValidAdapter(candidate, where) {
|
|
27
|
+
assertAdapterShape(candidate, where, false);
|
|
28
|
+
}
|
|
29
|
+
/** Validates that the candidate is a valid `AdapterInput` (externalInterface may be `"auto"` or a function). */
|
|
30
|
+
function assertValidAdapterInput(candidate, where) {
|
|
31
|
+
assertAdapterShape(candidate, where, true);
|
|
32
|
+
}
|
|
33
|
+
function assertAdapterShape(candidate, where, allowAutoExtIf) {
|
|
26
34
|
const obj = candidate;
|
|
27
35
|
const resolveValue = obj?.resolveValue;
|
|
28
36
|
const resolveCall = obj?.resolveCall;
|
|
@@ -87,7 +95,10 @@ function assertValidAdapter(candidate, where) {
|
|
|
87
95
|
"",
|
|
88
96
|
`Docs/examples: ${ADAPTER_DOCS_URL}`
|
|
89
97
|
].join("\n"));
|
|
90
|
-
if (typeof externalInterface
|
|
98
|
+
if (!(typeof externalInterface === "function" || allowAutoExtIf && externalInterface === "auto")) {
|
|
99
|
+
const expected = allowAutoExtIf ? "adapter.externalInterface must be a function or \"auto\"." : "adapter.externalInterface must be a function.";
|
|
100
|
+
throw new Error([`${where}: ${expected}`, `Received: externalInterface=${describeValue(externalInterface)}`].join("\n"));
|
|
101
|
+
}
|
|
91
102
|
const styleMerger = obj?.styleMerger;
|
|
92
103
|
if (styleMerger !== null && styleMerger !== void 0) {
|
|
93
104
|
if (typeof styleMerger !== "object") throw new Error([
|
|
@@ -151,6 +162,18 @@ var Logger = class Logger {
|
|
|
151
162
|
Logger.writeWithSpacing(message, context);
|
|
152
163
|
}
|
|
153
164
|
/**
|
|
165
|
+
* Mark an Error instance as already logged so downstream catch blocks can skip it.
|
|
166
|
+
*/
|
|
167
|
+
static markErrorAsLogged(e) {
|
|
168
|
+
if (e instanceof Error) Logger.loggedErrors.add(e);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check whether an error was already logged via `markErrorAsLogged`.
|
|
172
|
+
*/
|
|
173
|
+
static isErrorLogged(e) {
|
|
174
|
+
return e instanceof Error && Logger.loggedErrors.has(e);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
154
177
|
* Log an error message to stdout with file path and optional location.
|
|
155
178
|
* Formats like warnings: "Error filepath:line:column\nmessage"
|
|
156
179
|
* Always prints regardless of file count.
|
|
@@ -189,10 +212,12 @@ var Logger = class Logger {
|
|
|
189
212
|
static _clearCollected() {
|
|
190
213
|
Logger.collected = [];
|
|
191
214
|
Logger.fileCount = null;
|
|
215
|
+
Logger.loggedErrors = /* @__PURE__ */ new WeakSet();
|
|
192
216
|
}
|
|
193
217
|
static collected = [];
|
|
194
218
|
static fileCount = null;
|
|
195
|
-
static maxExamples =
|
|
219
|
+
static maxExamples = 3;
|
|
220
|
+
static loggedErrors = /* @__PURE__ */ new WeakSet();
|
|
196
221
|
static writeWithSpacing(message, context) {
|
|
197
222
|
const trimmed = message.replace(/\s+$/u, "");
|
|
198
223
|
const serialized = Logger.formatContext(context);
|
|
@@ -225,7 +250,7 @@ var LoggerReport = class {
|
|
|
225
250
|
fileCount;
|
|
226
251
|
maxExamples;
|
|
227
252
|
fileCache = /* @__PURE__ */ new Map();
|
|
228
|
-
constructor(warnings, fileCount, maxExamples =
|
|
253
|
+
constructor(warnings, fileCount, maxExamples = 3) {
|
|
229
254
|
this.warnings = warnings;
|
|
230
255
|
this.fileCount = fileCount;
|
|
231
256
|
this.maxExamples = maxExamples;
|
|
@@ -336,4 +361,4 @@ const SECTION_COLOR = "\x1B[36m";
|
|
|
336
361
|
const RESET_COLOR = "\x1B[0m";
|
|
337
362
|
|
|
338
363
|
//#endregion
|
|
339
|
-
export { assertValidAdapter as n,
|
|
364
|
+
export { describeValue as i, assertValidAdapter as n, assertValidAdapterInput as r, Logger as t };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ResolverFactory } from "oxc-resolver";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/prepass/resolve-imports.ts
|
|
4
|
+
/**
|
|
5
|
+
* Module resolution using oxc-resolver.
|
|
6
|
+
* Resolves import specifiers to absolute file paths, handling:
|
|
7
|
+
* - Extension probing (.ts, .tsx, .js, .jsx, /index.*)
|
|
8
|
+
* - tsconfig.json paths aliases (auto-discovered)
|
|
9
|
+
* - tsconfig.json project references
|
|
10
|
+
* - .js → .ts remapping (ESM with moduleResolution: "bundler")
|
|
11
|
+
* - Package exports field
|
|
12
|
+
* - Symlink resolution (pnpm/Yarn workspaces)
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Shared resolver config for TypeScript projects.
|
|
16
|
+
*
|
|
17
|
+
* - `.tsx` before `.ts` so React component files win when both exist
|
|
18
|
+
* - `"types"` condition for type-aware resolution; `"default"` as fallback
|
|
19
|
+
* - `tsconfig: "auto"` auto-discovers the nearest tsconfig.json per file
|
|
20
|
+
* for path alias resolution
|
|
21
|
+
* - `extensionAlias` handles ESM `.js`→`.ts` remapping AND `.ts`→`.tsx`
|
|
22
|
+
* fallback for package.json `"exports"` wildcards
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_CONFIG = {
|
|
25
|
+
extensions: [
|
|
26
|
+
".tsx",
|
|
27
|
+
".ts",
|
|
28
|
+
".jsx",
|
|
29
|
+
".js"
|
|
30
|
+
],
|
|
31
|
+
conditionNames: [
|
|
32
|
+
"import",
|
|
33
|
+
"types",
|
|
34
|
+
"default"
|
|
35
|
+
],
|
|
36
|
+
mainFields: ["module", "main"],
|
|
37
|
+
extensionAlias: {
|
|
38
|
+
".js": [
|
|
39
|
+
".ts",
|
|
40
|
+
".tsx",
|
|
41
|
+
".js"
|
|
42
|
+
],
|
|
43
|
+
".jsx": [".tsx", ".jsx"],
|
|
44
|
+
".ts": [".ts", ".tsx"]
|
|
45
|
+
},
|
|
46
|
+
tsconfig: "auto"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create a module resolver with configurable options.
|
|
50
|
+
*
|
|
51
|
+
* The returned `resolve` function resolves a specifier relative to
|
|
52
|
+
* a source file path, returning the absolute path or `undefined` on failure.
|
|
53
|
+
*/
|
|
54
|
+
function createModuleResolver(config = DEFAULT_CONFIG) {
|
|
55
|
+
const resolver = new ResolverFactory(config);
|
|
56
|
+
return { resolve(fromFile, specifier) {
|
|
57
|
+
try {
|
|
58
|
+
return resolver.resolveFileSync(fromFile, specifier).path ?? void 0;
|
|
59
|
+
} catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
} };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { createModuleResolver };
|