styled-components-to-stylex-codemod 0.0.16 → 0.0.17

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.
@@ -1,5 +1,6 @@
1
1
  import { t as isSelectorContext } from "./selector-context-heuristic-CGwiJ3HL.mjs";
2
- import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { readFileSync, realpathSync } from "node:fs";
3
4
 
4
5
  //#region src/internal/bridge-consumer-patcher.ts
5
6
  /**
@@ -13,8 +14,12 @@ import { readFileSync } from "node:fs";
13
14
  /**
14
15
  * Build a mapping from consumer file paths to their required replacements,
15
16
  * cross-referencing prepass selector usages with successful bridge results.
17
+ *
18
+ * @param transformedFiles Set of files that were actually transformed successfully.
19
+ * When provided, consumers in this set are skipped (they used the marker path).
20
+ * When absent, falls back to the prepass `consumerIsTransformed` flag.
16
21
  */
17
- function buildConsumerReplacements(selectorUsages, bridgeResults) {
22
+ function buildConsumerReplacements(selectorUsages, bridgeResults, transformedFiles) {
18
23
  const consumerReplacements = /* @__PURE__ */ new Map();
19
24
  const bridgeLookup = /* @__PURE__ */ new Map();
20
25
  for (const [targetPath, results] of bridgeResults) for (const result of results) {
@@ -22,7 +27,7 @@ function buildConsumerReplacements(selectorUsages, bridgeResults) {
22
27
  if (result.exportName && result.exportName !== result.componentName) bridgeLookup.set(`${targetPath}:${result.exportName}`, result);
23
28
  }
24
29
  for (const [consumerPath, usages] of selectorUsages) for (const usage of usages) {
25
- if (usage.consumerIsTransformed) continue;
30
+ if (transformedFiles ? transformedFiles.has(toRealPath(usage.consumerPath)) : usage.consumerIsTransformed) continue;
26
31
  const bridge = bridgeLookup.get(`${usage.resolvedPath}:${usage.importedName}`);
27
32
  if (!bridge) continue;
28
33
  let replacements = consumerReplacements.get(consumerPath);
@@ -117,6 +122,15 @@ function hasExactImportName(importSpecifiers, name) {
117
122
  function escapeRegExp(s) {
118
123
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
119
124
  }
125
+ /** Resolve symlinks so paths match the keys in transformedFiles (which uses realpathSync). */
126
+ function toRealPath(filePath) {
127
+ const resolved = resolve(filePath);
128
+ try {
129
+ return realpathSync(resolved);
130
+ } catch {
131
+ return resolved;
132
+ }
133
+ }
120
134
 
121
135
  //#endregion
122
136
  export { buildConsumerReplacements, patchConsumerFile };
package/dist/index.mjs CHANGED
@@ -194,7 +194,7 @@ async function runTransform(options) {
194
194
  ].join("\n"));
195
195
  const { createModuleResolver } = await import("./resolve-imports-BDk6Ms09.mjs");
196
196
  const sharedResolver = createModuleResolver();
197
- const { runPrepass } = await import("./run-prepass-DOMrzrA_.mjs");
197
+ const { runPrepass } = await import("./run-prepass-C4Lv1FHf.mjs");
198
198
  const absoluteFiles = filePaths.map((f) => resolve(f));
199
199
  const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
200
200
  let prepassResult;
@@ -264,6 +264,7 @@ async function runTransform(options) {
264
264
  })();
265
265
  const sidecarFiles = /* @__PURE__ */ new Map();
266
266
  const bridgeResults = /* @__PURE__ */ new Map();
267
+ const transformedFiles = /* @__PURE__ */ new Set();
267
268
  const result = await run(transformPath, filePaths, {
268
269
  parser,
269
270
  dry: dryRun,
@@ -272,12 +273,13 @@ async function runTransform(options) {
272
273
  crossFilePrepassResult,
273
274
  sidecarFiles,
274
275
  bridgeResults,
276
+ transformedFiles,
275
277
  runInBand: true
276
278
  });
277
279
  if (sidecarFiles.size > 0 && !dryRun) for (const [sidecarPath, content] of sidecarFiles) await writeFile(sidecarPath, mergeSidecarContent(sidecarPath, content), "utf-8");
278
280
  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 { buildConsumerReplacements, patchConsumerFile } = await import("./bridge-consumer-patcher-DhMoL4Od.mjs");
282
+ const consumerReplacements = buildConsumerReplacements(crossFilePrepassResult.selectorUsages, bridgeResults, transformedFiles);
281
283
  const patchedFiles = [];
282
284
  for (const [consumerPath, replacements] of consumerReplacements) {
283
285
  const patched = patchConsumerFile(consumerPath, replacements);
@@ -234,7 +234,7 @@ function categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, compon
234
234
  for (const usage of usages) {
235
235
  if (usage.bridgeComponentName) continue;
236
236
  if (usage.consumerIsTransformed) addToSetMap(componentsNeedingMarkerSidecar, usage.resolvedPath, usage.importedName);
237
- else addToSetMap(componentsNeedingGlobalSelectorBridge, usage.resolvedPath, usage.importedName);
237
+ addToSetMap(componentsNeedingGlobalSelectorBridge, usage.resolvedPath, usage.importedName);
238
238
  }
239
239
  }
240
240
  /**
@@ -5447,13 +5447,12 @@ function rewriteCssHelpersStep(ctx) {
5447
5447
  //#endregion
5448
5448
  //#region src/internal/emit-styles.ts
5449
5449
  function emitStylesAndImports(ctx) {
5450
- const { root, j, file, resolverImports, adapter } = ctx;
5450
+ const { root, j, file, resolverImports } = ctx;
5451
5451
  const filePath = file.path;
5452
5452
  const styledImports = ctx.styledImports;
5453
5453
  const resolvedStyleObjects = ctx.resolvedStyleObjects ?? /* @__PURE__ */ new Map();
5454
5454
  const styledDecls = ctx.styledDecls;
5455
5455
  const stylesIdentifier = ctx.stylesIdentifier ?? "styles";
5456
- const styleMerger = adapter.styleMerger;
5457
5456
  const stylesInsertPosition = ctx.stylesInsertPosition ?? "end";
5458
5457
  const preservedHeaderComments = [];
5459
5458
  const addHeaderComments = (comments) => {
@@ -5693,15 +5692,6 @@ function emitStylesAndImports(ctx) {
5693
5692
  insertImportDecl(j.importDeclaration(spec.names.map((n) => toImportSpecifier(n.imported, n.local)), j.literal(moduleSpecifier)));
5694
5693
  };
5695
5694
  for (const spec of resolverImports.values()) ensureImportDecl(spec);
5696
- const needsMergerImport = styledDecls.some((d) => {
5697
- if (!d.needsWrapperComponent) return false;
5698
- if (d.isPolymorphicIntrinsicWrapper) return d.supportsExternalStyles || d.usedAsValue || d.receivesClassNameOrStyleInJsx;
5699
- return d.supportsExternalStyles || d.usedAsValue || d.receivesClassNameOrStyleInJsx;
5700
- });
5701
- if (styleMerger && needsMergerImport) ensureImportDecl({
5702
- from: styleMerger.importSource,
5703
- names: [{ imported: styleMerger.functionName }]
5704
- });
5705
5695
  }
5706
5696
  const styleKeyToComments = /* @__PURE__ */ new Map();
5707
5697
  for (const decl of styledDecls) {
@@ -6158,7 +6148,7 @@ function emitStyleMerging(args) {
6158
6148
  classNameBeforeSpread: false,
6159
6149
  styleAttr: null
6160
6150
  };
6161
- if (styleMerger && (allowClassNameProp || allowStyleProp) && !staticClassNameExpr) return emitWithMerger({
6151
+ if (styleMerger && (allowClassNameProp || allowStyleProp)) return emitWithMerger({
6162
6152
  j,
6163
6153
  styleMerger,
6164
6154
  styleArgs,
@@ -6167,6 +6157,7 @@ function emitStyleMerging(args) {
6167
6157
  allowClassNameProp,
6168
6158
  allowStyleProp,
6169
6159
  inlineStyleProps,
6160
+ staticClassNameExpr,
6170
6161
  emitTypes
6171
6162
  });
6172
6163
  return emitVerbosePattern({
@@ -6215,11 +6206,17 @@ function emitWithoutStylex(args) {
6215
6206
  * Generates the merger function call pattern.
6216
6207
  */
6217
6208
  function emitWithMerger(args) {
6218
- const { j, styleMerger, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, emitTypes } = args;
6209
+ const { j, styleMerger, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, staticClassNameExpr, emitTypes } = args;
6219
6210
  const firstStyleArg = styleArgs[0];
6220
6211
  const mergerArgs = [styleArgs.length === 1 && firstStyleArg ? firstStyleArg : j.arrayExpression(styleArgs)];
6221
- if (allowClassNameProp || allowStyleProp) {
6222
- if (allowClassNameProp) mergerArgs.push(classNameId);
6212
+ const classNameArg = buildMergerClassNameArg({
6213
+ j,
6214
+ classNameId,
6215
+ allowClassNameProp,
6216
+ staticClassNameExpr
6217
+ });
6218
+ if (allowClassNameProp || allowStyleProp || classNameArg) {
6219
+ if (classNameArg) mergerArgs.push(classNameArg);
6223
6220
  else if (allowStyleProp) mergerArgs.push(j.identifier("undefined"));
6224
6221
  if (allowStyleProp) if (inlineStyleProps.length > 0) mergerArgs.push(maybeCastStyleForCustomProps(j, j.objectExpression([j.spreadElement(styleId), ...inlineStyleProps.map((p) => j.property("init", inlineStylePropKey(j, p.prop), p.expr))]), inlineStyleProps, emitTypes));
6225
6222
  else mergerArgs.push(styleId);
@@ -6237,6 +6234,15 @@ function emitWithMerger(args) {
6237
6234
  styleAttr: null
6238
6235
  };
6239
6236
  }
6237
+ function buildMergerClassNameArg(args) {
6238
+ const { j, classNameId, allowClassNameProp, staticClassNameExpr } = args;
6239
+ const parts = [];
6240
+ if (staticClassNameExpr) parts.push(staticClassNameExpr);
6241
+ if (allowClassNameProp) parts.push(classNameId);
6242
+ if (parts.length === 0) return null;
6243
+ if (parts.length === 1) return parts[0] ?? null;
6244
+ return j.callExpression(j.memberExpression(j.callExpression(j.memberExpression(j.arrayExpression(parts), j.identifier("filter")), [j.identifier("Boolean")]), j.identifier("join")), [j.literal(" ")]);
6245
+ }
6240
6246
  /**
6241
6247
  * Generates the verbose className/style merging pattern.
6242
6248
  */
@@ -10331,35 +10337,63 @@ function emitWrappersStep(ctx) {
10331
10337
  * Ensures the style merger import is present when the merger function is referenced.
10332
10338
  */
10333
10339
  function ensureMergerImportStep(ctx) {
10334
- const { root, j, adapter } = ctx;
10340
+ const { root, j, adapter, file } = ctx;
10335
10341
  if (adapter.styleMerger?.functionName && adapter.styleMerger.importSource) {
10336
10342
  const mergerName = adapter.styleMerger.functionName;
10337
- const hasMergerUsage = root.find(j.Identifier, { name: mergerName }).filter((p) => isIdentifierReference(p)).size() > 0;
10338
- const hasMergerImport = root.find(j.ImportSpecifier, { imported: {
10339
- type: "Identifier",
10340
- name: mergerName
10341
- } }).size() > 0;
10342
- const hasLocalBinding = root.find(j.FunctionDeclaration, { id: { name: mergerName } }).size() > 0 || root.find(j.VariableDeclarator, { id: {
10343
+ const hasMergerCall = root.find(j.CallExpression, { callee: {
10343
10344
  type: "Identifier",
10344
10345
  name: mergerName
10345
10346
  } }).size() > 0;
10346
- if (hasMergerUsage && !hasMergerImport && !hasLocalBinding) {
10347
+ const hasMergerImportBinding = root.find(j.ImportDeclaration).filter((p) => (p.node.specifiers ?? []).some((s) => {
10348
+ if (s?.type !== "ImportSpecifier") return false;
10349
+ return s.local?.type === "Identifier" && s.local.name === mergerName;
10350
+ })).size() > 0;
10351
+ const hasTopLevelBinding = hasTopLevelValueBinding(root, mergerName);
10352
+ if (hasMergerCall && !hasMergerImportBinding && !hasTopLevelBinding) {
10347
10353
  const source = adapter.styleMerger.importSource;
10348
- if (source.kind === "specifier") {
10349
- const decl = j.importDeclaration([j.importSpecifier(j.identifier(mergerName))], j.literal(source.value));
10350
- const stylexImport = root.find(j.ImportDeclaration, { source: { value: "@stylexjs/stylex" } });
10351
- if (stylexImport.size() > 0) stylexImport.at(stylexImport.size() - 1).insertAfter(decl);
10352
- else {
10353
- const firstImport = root.find(j.ImportDeclaration).at(0);
10354
- if (firstImport.size() > 0) firstImport.insertBefore(decl);
10355
- else root.get().node.program.body.unshift(decl);
10356
- }
10357
- ctx.markChanged();
10354
+ const moduleSpecifier = toModuleSpecifier(source, file.path);
10355
+ const decl = j.importDeclaration([j.importSpecifier(j.identifier(mergerName))], j.literal(moduleSpecifier));
10356
+ const stylexImport = root.find(j.ImportDeclaration, { source: { value: "@stylexjs/stylex" } });
10357
+ if (stylexImport.size() > 0) stylexImport.at(stylexImport.size() - 1).insertAfter(decl);
10358
+ else {
10359
+ const firstImport = root.find(j.ImportDeclaration).at(0);
10360
+ if (firstImport.size() > 0) firstImport.insertBefore(decl);
10361
+ else root.get().node.program.body.unshift(decl);
10358
10362
  }
10363
+ ctx.markChanged();
10359
10364
  }
10360
10365
  }
10361
10366
  return CONTINUE;
10362
10367
  }
10368
+ function toModuleSpecifier(from, filePath) {
10369
+ if (from.kind === "specifier") {
10370
+ if (typeof from.value !== "string" || from.value.trim() === "") throw new Error(`Invalid styleMerger import specifier: expected non-empty string, got ${JSON.stringify(from.value)}`);
10371
+ return from.value;
10372
+ }
10373
+ if (typeof from.value !== "string" || from.value.trim() === "") throw new Error(`Invalid styleMerger import absolutePath: expected non-empty string, got ${JSON.stringify(from.value)}`);
10374
+ if (!path.isAbsolute(from.value)) throw new Error(`Invalid styleMerger import absolutePath: expected absolute path, got ${JSON.stringify(from.value)}`);
10375
+ const baseDir = path.dirname(String(filePath));
10376
+ let rel = path.relative(baseDir, from.value);
10377
+ rel = rel.split(path.sep).join("/");
10378
+ if (!rel.startsWith(".")) rel = `./${rel}`;
10379
+ return rel;
10380
+ }
10381
+ function hasTopLevelValueBinding(root, localName) {
10382
+ const body = root.get().node.program.body;
10383
+ const hasBindingInDeclaration = (decl) => {
10384
+ if (!decl || typeof decl !== "object") return false;
10385
+ if ((decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") && decl.id?.type === "Identifier" && decl.id.name === localName) return true;
10386
+ if (decl.type === "VariableDeclaration") return (decl.declarations ?? []).some((d) => d?.id?.type === "Identifier" && d.id.name === localName);
10387
+ return false;
10388
+ };
10389
+ for (const stmt of body) {
10390
+ if (hasBindingInDeclaration(stmt)) return true;
10391
+ if (stmt?.type === "ExportNamedDeclaration" || stmt?.type === "ExportDefaultDeclaration") {
10392
+ if (hasBindingInDeclaration(stmt.declaration)) return true;
10393
+ }
10394
+ }
10395
+ return false;
10396
+ }
10363
10397
 
10364
10398
  //#endregion
10365
10399
  //#region src/internal/transform-steps/ensure-react-import.ts
@@ -20633,8 +20667,53 @@ function postProcessTransformedAst(args) {
20633
20667
  elementStyleKey,
20634
20668
  markerVarName: addedMarkerVarName
20635
20669
  }];
20636
- for (const c of node.children ?? []) if (c?.type === "JSXElement") visit(c, nextAncestors);
20670
+ for (const child of node.children ?? []) visitJsxChild(child, nextAncestors);
20637
20671
  };
20672
+ function visitJsxChild(node, ancestors) {
20673
+ if (!node || typeof node !== "object") return;
20674
+ if (node.type === "JSXElement") {
20675
+ visit(node, ancestors);
20676
+ return;
20677
+ }
20678
+ if (node.type === "JSXFragment") {
20679
+ visitJsxFragment(node, ancestors);
20680
+ return;
20681
+ }
20682
+ if (node.type === "JSXExpressionContainer") visitJsxInExpression(node.expression, ancestors);
20683
+ }
20684
+ function visitJsxFragment(node, ancestors) {
20685
+ for (const child of node.children ?? []) visitJsxChild(child, ancestors);
20686
+ }
20687
+ function visitJsxInExpression(node, ancestors) {
20688
+ if (!node || typeof node !== "object") return;
20689
+ if (node.type === "JSXElement") {
20690
+ visit(node, ancestors);
20691
+ return;
20692
+ }
20693
+ if (node.type === "JSXFragment") {
20694
+ visitJsxFragment(node, ancestors);
20695
+ return;
20696
+ }
20697
+ if (node.type === "ConditionalExpression") {
20698
+ visitJsxInExpression(node.consequent, ancestors);
20699
+ visitJsxInExpression(node.alternate, ancestors);
20700
+ return;
20701
+ }
20702
+ if (node.type === "LogicalExpression") {
20703
+ visitJsxInExpression(node.left, ancestors);
20704
+ visitJsxInExpression(node.right, ancestors);
20705
+ return;
20706
+ }
20707
+ if (node.type === "SequenceExpression") {
20708
+ for (const expr of node.expressions ?? []) visitJsxInExpression(expr, ancestors);
20709
+ return;
20710
+ }
20711
+ if (node.type === "ArrayExpression") {
20712
+ for (const expr of node.elements ?? []) visitJsxInExpression(expr, ancestors);
20713
+ return;
20714
+ }
20715
+ if (node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") visitJsxInExpression(node.expression, ancestors);
20716
+ }
20638
20717
  root.find(j.JSXElement).forEach((p) => {
20639
20718
  if (j(p).closest(j.JSXElement).size() > 0) return;
20640
20719
  visit(p.node, []);
@@ -21253,6 +21332,10 @@ function transform(file, api, options) {
21253
21332
  const bridgeResultsMap = options.bridgeResults;
21254
21333
  if (bridgeResultsMap) bridgeResultsMap.set(toRealPath(file.path), result.bridgeResults);
21255
21334
  }
21335
+ if (result.code !== null) {
21336
+ const transformedFiles = options.transformedFiles;
21337
+ if (transformedFiles) transformedFiles.add(toRealPath(file.path));
21338
+ }
21256
21339
  return result.code;
21257
21340
  } catch (e) {
21258
21341
  if (!Logger.isErrorLogged(e)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styled-components-to-stylex-codemod",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "Codemod to transform styled-components to StyleX",
5
5
  "keywords": [
6
6
  "codemod",