styled-components-to-stylex-codemod 0.0.15 → 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-BcidTT9f.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,11 +234,16 @@ 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
- /** Regex matching `export const XGlobalSelector = ".sc2sx-` pattern (global for matchAll). */
241
- const BRIDGE_EXPORT_RE = /export\s+const\s+(\w+GlobalSelector)\s*=\s*["']\.sc2sx-/g;
240
+ /**
241
+ * Regex matching bridge GlobalSelector export patterns (global for matchAll).
242
+ * Matches both:
243
+ * - Old format: `export const XGlobalSelector = ".sc2sx-..."`
244
+ * - New format: `` export const XGlobalSelector = `.${xBridgeClass}` ``
245
+ */
246
+ const BRIDGE_EXPORT_RE = /export\s+const\s+(\w+GlobalSelector)\s*=\s*(?:["']\.sc2sx-|`\.\$\{)/g;
242
247
  /**
243
248
  * Detect whether an imported name is a bridge GlobalSelector from an
244
249
  * already-converted StyleX file.
@@ -302,10 +307,7 @@ function walkForImportsAndTemplates(node, imports, templates) {
302
307
  imports.push(n);
303
308
  return;
304
309
  }
305
- if (n.type === "TaggedTemplateExpression") {
306
- templates.push(n);
307
- return;
308
- }
310
+ if (n.type === "TaggedTemplateExpression") templates.push(n);
309
311
  for (const key of Object.keys(n)) {
310
312
  if (key === "type" || key === "start" || key === "end" || key === "loc") continue;
311
313
  const val = n[key];
@@ -352,13 +354,32 @@ function findStyledImportNameFromNodes(importNodes) {
352
354
  }
353
355
  }
354
356
  /**
357
+ * Find local names of `css` imported from styled-components.
358
+ * Handles aliased imports like `import { css as sc } from "styled-components"`.
359
+ */
360
+ function findCssImportNamesFromNodes(importNodes) {
361
+ const names = /* @__PURE__ */ new Set();
362
+ for (const node of importNodes) {
363
+ if (node.source?.value !== "styled-components") continue;
364
+ const specifiers = node.specifiers;
365
+ if (!specifiers) continue;
366
+ for (const spec of specifiers) if (spec.type === "ImportSpecifier") {
367
+ if (getNodeName(spec.imported) === "css") {
368
+ const localName = getNodeName(spec.local);
369
+ if (localName) names.add(localName);
370
+ }
371
+ }
372
+ }
373
+ return names;
374
+ }
375
+ /**
355
376
  * Find local names of imported components used as selectors inside
356
- * styled-components template literals.
377
+ * styled-components template literals (both `styled` and `css` tagged templates).
357
378
  */
358
- function findComponentSelectorLocalsFromNodes(templateNodes, styledImportName) {
379
+ function findComponentSelectorLocalsFromNodes(templateNodes, styledImportName, cssImportNames) {
359
380
  const selectorLocals = /* @__PURE__ */ new Set();
360
381
  for (const node of templateNodes) {
361
- if (!isStyledTag(node.tag, styledImportName)) continue;
382
+ if (!isStyledTag(node.tag, styledImportName) && !isCssTag(node.tag, cssImportNames)) continue;
362
383
  const quasi = node.quasi;
363
384
  if (!quasi) continue;
364
385
  const quasis = quasi.quasis;
@@ -399,6 +420,11 @@ function isStyledTag(tag, styledName) {
399
420
  }
400
421
  return false;
401
422
  }
423
+ /** Check if a template tag is the `css` helper from styled-components. */
424
+ function isCssTag(tag, cssImportNames) {
425
+ if (!tag || !cssImportNames || cssImportNames.size === 0) return false;
426
+ return tag.type === "Identifier" && typeof tag.name === "string" && cssImportNames.has(tag.name);
427
+ }
402
428
  /** Check if a placeholder at the given position is in a CSS selector context. */
403
429
  function isPlaceholderInSelectorContext(rawCss, pos, length) {
404
430
  const after = rawCss.slice(pos + length).trimStart();
@@ -759,10 +785,12 @@ function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parse
759
785
  const cached = cache && hash ? cache.get(hash) : void 0;
760
786
  let importMap;
761
787
  let styledImportName;
788
+ let cssImportNames;
762
789
  let selectorLocals;
763
790
  if (cached) {
764
791
  importMap = cached.importMap;
765
792
  styledImportName = cached.styledImportName;
793
+ cssImportNames = cached.cssImportNames;
766
794
  selectorLocals = cached.selectorLocals;
767
795
  } else {
768
796
  let ast;
@@ -781,14 +809,16 @@ function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parse
781
809
  walkForImportsAndTemplates(program, importNodes, taggedTemplateNodes);
782
810
  importMap = buildImportMapFromNodes(importNodes);
783
811
  styledImportName = findStyledImportNameFromNodes(importNodes);
784
- selectorLocals = styledImportName ? findComponentSelectorLocalsFromNodes(taggedTemplateNodes, styledImportName) : /* @__PURE__ */ new Set();
812
+ cssImportNames = findCssImportNamesFromNodes(importNodes);
813
+ selectorLocals = styledImportName || cssImportNames.size > 0 ? findComponentSelectorLocalsFromNodes(taggedTemplateNodes, styledImportName ?? "", cssImportNames) : /* @__PURE__ */ new Set();
785
814
  if (cache && hash) cache.set(hash, {
786
815
  importMap,
787
816
  styledImportName,
817
+ cssImportNames,
788
818
  selectorLocals
789
819
  });
790
820
  }
791
- if (importMap.size === 0 || !styledImportName || selectorLocals.size === 0) return [];
821
+ if (importMap.size === 0 || !styledImportName && cssImportNames.size === 0 || selectorLocals.size === 0) return [];
792
822
  const consumerIsTransformed = transformSet.has(filePath);
793
823
  const usages = [];
794
824
  for (const localName of selectorLocals) {
@@ -1148,6 +1148,20 @@ function generateBridgeClassName(filePath, componentName) {
1148
1148
  function bridgeExportName(componentName) {
1149
1149
  return `${componentName}GlobalSelector`;
1150
1150
  }
1151
+ /**
1152
+ * Generate the internal const variable name for the bridge class value.
1153
+ * E.g., "Foo" → "fooBridgeClass", "ScrollableDiv" → "scrollableDivBridgeClass"
1154
+ */
1155
+ function bridgeClassVarName(componentName) {
1156
+ return `${componentName.charAt(0).toLowerCase()}${componentName.slice(1)}BridgeClass`;
1157
+ }
1158
+ /**
1159
+ * If a declaration has a bridge className, return the internal const variable
1160
+ * name that references it. Returns `undefined` when no bridge is needed.
1161
+ */
1162
+ function getBridgeClassVar(decl) {
1163
+ return decl.bridgeClassName ? bridgeClassVarName(decl.localName) : void 0;
1164
+ }
1151
1165
 
1152
1166
  //#endregion
1153
1167
  //#region src/internal/transform-steps/analyze-before-emit.ts
@@ -1232,15 +1246,7 @@ function analyzeBeforeEmitStep(ctx) {
1232
1246
  if (decl.base.kind === "intrinsic" && decl.withConfig?.componentId) decl.needsWrapperComponent = true;
1233
1247
  if (extendedBy.has(decl.localName) && componentsWithStaticProps.has(decl.localName)) decl.needsWrapperComponent = true;
1234
1248
  if (exportedComponents.has(decl.localName)) decl.needsWrapperComponent = true;
1235
- if (ctx.bridgeComponentNames?.has(decl.localName) || ctx.bridgeComponentNames?.has("default") && exportedComponents.get(decl.localName)?.isDefault) {
1236
- decl.bridgeClassName = generateBridgeClassName(resolve(file.path), decl.localName);
1237
- if (!decl.attrsInfo) decl.attrsInfo = {
1238
- staticAttrs: {},
1239
- conditionalAttrs: []
1240
- };
1241
- const existing = typeof decl.attrsInfo.staticAttrs.className === "string" ? decl.attrsInfo.staticAttrs.className + " " : "";
1242
- decl.attrsInfo.staticAttrs.className = existing + decl.bridgeClassName;
1243
- }
1249
+ if (ctx.bridgeComponentNames?.has(decl.localName) || ctx.bridgeComponentNames?.has("default") && exportedComponents.get(decl.localName)?.isDefault) decl.bridgeClassName = generateBridgeClassName(resolve(file.path), decl.localName);
1244
1250
  }
1245
1251
  const isUsedInJsx = (name) => isComponentUsedInJsx(root, j, name);
1246
1252
  const canInlineImportedComponentWrapper = (decl) => {
@@ -5441,13 +5447,12 @@ function rewriteCssHelpersStep(ctx) {
5441
5447
  //#endregion
5442
5448
  //#region src/internal/emit-styles.ts
5443
5449
  function emitStylesAndImports(ctx) {
5444
- const { root, j, file, resolverImports, adapter } = ctx;
5450
+ const { root, j, file, resolverImports } = ctx;
5445
5451
  const filePath = file.path;
5446
5452
  const styledImports = ctx.styledImports;
5447
5453
  const resolvedStyleObjects = ctx.resolvedStyleObjects ?? /* @__PURE__ */ new Map();
5448
5454
  const styledDecls = ctx.styledDecls;
5449
5455
  const stylesIdentifier = ctx.stylesIdentifier ?? "styles";
5450
- const styleMerger = adapter.styleMerger;
5451
5456
  const stylesInsertPosition = ctx.stylesInsertPosition ?? "end";
5452
5457
  const preservedHeaderComments = [];
5453
5458
  const addHeaderComments = (comments) => {
@@ -5687,15 +5692,6 @@ function emitStylesAndImports(ctx) {
5687
5692
  insertImportDecl(j.importDeclaration(spec.names.map((n) => toImportSpecifier(n.imported, n.local)), j.literal(moduleSpecifier)));
5688
5693
  };
5689
5694
  for (const spec of resolverImports.values()) ensureImportDecl(spec);
5690
- const needsMergerImport = styledDecls.some((d) => {
5691
- if (!d.needsWrapperComponent) return false;
5692
- if (d.isPolymorphicIntrinsicWrapper) return d.supportsExternalStyles || d.usedAsValue || d.receivesClassNameOrStyleInJsx;
5693
- return d.supportsExternalStyles || d.usedAsValue || d.receivesClassNameOrStyleInJsx;
5694
- });
5695
- if (styleMerger && needsMergerImport) ensureImportDecl({
5696
- from: styleMerger.importSource,
5697
- names: [{ imported: styleMerger.functionName }]
5698
- });
5699
5695
  }
5700
5696
  const styleKeyToComments = /* @__PURE__ */ new Map();
5701
5697
  for (const decl of styledDecls) {
@@ -6077,17 +6073,28 @@ function emitBridgeExportsStep(ctx) {
6077
6073
  const { j, root } = ctx;
6078
6074
  const bridgeResults = [];
6079
6075
  for (const decl of bridgeDecls) {
6080
- const varName = bridgeExportName(decl.localName);
6076
+ const exportVarName = bridgeExportName(decl.localName);
6077
+ const internalVarName = bridgeClassVarName(decl.localName);
6081
6078
  const className = decl.bridgeClassName;
6082
- const declaration = j.variableDeclaration("const", [j.variableDeclarator(j.identifier(varName), j.stringLiteral(`.${className}`))]);
6083
- const exportDecl = j.exportNamedDeclaration(declaration);
6079
+ const internalDecl = j.variableDeclaration("const", [j.variableDeclarator(j.identifier(internalVarName), j.stringLiteral(className))]);
6080
+ const templateLiteral = j.templateLiteral([j.templateElement({
6081
+ raw: ".",
6082
+ cooked: "."
6083
+ }, false), j.templateElement({
6084
+ raw: "",
6085
+ cooked: ""
6086
+ }, true)], [j.identifier(internalVarName)]);
6087
+ const exportDeclaration = j.variableDeclaration("const", [j.variableDeclarator(j.identifier(exportVarName), templateLiteral)]);
6088
+ const exportDecl = j.exportNamedDeclaration(exportDeclaration);
6084
6089
  exportDecl.comments = [j.commentBlock("* @deprecated Migrate consumer to stylex.defineMarker() — bridge className for unconverted styled-components consumers ", true, false)];
6085
- root.find(j.Program).get().node.body.push(exportDecl);
6090
+ const body = root.find(j.Program).get().node.body;
6091
+ body.push(internalDecl);
6092
+ body.push(exportDecl);
6086
6093
  bridgeResults.push({
6087
6094
  componentName: decl.localName,
6088
6095
  exportName: ctx.exportedComponents?.get(decl.localName)?.exportName,
6089
6096
  className,
6090
- globalSelectorVarName: varName
6097
+ globalSelectorVarName: exportVarName
6091
6098
  });
6092
6099
  }
6093
6100
  ctx.bridgeResults = bridgeResults;
@@ -6141,7 +6148,7 @@ function emitStyleMerging(args) {
6141
6148
  classNameBeforeSpread: false,
6142
6149
  styleAttr: null
6143
6150
  };
6144
- if (styleMerger && (allowClassNameProp || allowStyleProp) && !staticClassNameExpr) return emitWithMerger({
6151
+ if (styleMerger && (allowClassNameProp || allowStyleProp)) return emitWithMerger({
6145
6152
  j,
6146
6153
  styleMerger,
6147
6154
  styleArgs,
@@ -6150,6 +6157,7 @@ function emitStyleMerging(args) {
6150
6157
  allowClassNameProp,
6151
6158
  allowStyleProp,
6152
6159
  inlineStyleProps,
6160
+ staticClassNameExpr,
6153
6161
  emitTypes
6154
6162
  });
6155
6163
  return emitVerbosePattern({
@@ -6198,11 +6206,17 @@ function emitWithoutStylex(args) {
6198
6206
  * Generates the merger function call pattern.
6199
6207
  */
6200
6208
  function emitWithMerger(args) {
6201
- const { j, styleMerger, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, emitTypes } = args;
6209
+ const { j, styleMerger, styleArgs, classNameId, styleId, allowClassNameProp, allowStyleProp, inlineStyleProps, staticClassNameExpr, emitTypes } = args;
6202
6210
  const firstStyleArg = styleArgs[0];
6203
6211
  const mergerArgs = [styleArgs.length === 1 && firstStyleArg ? firstStyleArg : j.arrayExpression(styleArgs)];
6204
- if (allowClassNameProp || allowStyleProp) {
6205
- 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);
6206
6220
  else if (allowStyleProp) mergerArgs.push(j.identifier("undefined"));
6207
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));
6208
6222
  else mergerArgs.push(styleId);
@@ -6220,6 +6234,15 @@ function emitWithMerger(args) {
6220
6234
  styleAttr: null
6221
6235
  };
6222
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
+ }
6223
6246
  /**
6224
6247
  * Generates the verbose className/style merging pattern.
6225
6248
  */
@@ -6836,7 +6859,8 @@ function emitSimpleWithConfigWrappers(ctx) {
6836
6859
  invertedBoolAttrs: d.attrsInfo?.invertedBoolAttrs ?? [],
6837
6860
  staticAttrs: d.attrsInfo?.staticAttrs ?? {},
6838
6861
  inlineStyleProps: d.inlineStyleProps ?? [],
6839
- attrsAsTag: d.attrsInfo?.attrsAsTag
6862
+ attrsAsTag: d.attrsInfo?.attrsAsTag,
6863
+ bridgeClassVar: getBridgeClassVar(d)
6840
6864
  }), d));
6841
6865
  continue;
6842
6866
  }
@@ -6852,7 +6876,7 @@ function emitSimpleWithConfigWrappers(ctx) {
6852
6876
  restId
6853
6877
  });
6854
6878
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
6855
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo);
6879
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
6856
6880
  const merging = emitStyleMerging({
6857
6881
  j,
6858
6882
  emitter,
@@ -7146,7 +7170,7 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
7146
7170
  restId: restId ?? void 0
7147
7171
  });
7148
7172
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
7149
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo);
7173
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
7150
7174
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
7151
7175
  attrsInfo,
7152
7176
  includeForwardedAs: includesForwardedAs
@@ -7207,7 +7231,8 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
7207
7231
  invertedBoolAttrs: d.attrsInfo?.invertedBoolAttrs ?? [],
7208
7232
  staticAttrs: d.attrsInfo?.staticAttrs ?? {},
7209
7233
  inlineStyleProps: d.inlineStyleProps ?? [],
7210
- attrsAsTag: d.attrsInfo?.attrsAsTag
7234
+ attrsAsTag: d.attrsInfo?.attrsAsTag,
7235
+ bridgeClassVar: getBridgeClassVar(d)
7211
7236
  }), d));
7212
7237
  }
7213
7238
  }
@@ -7621,7 +7646,7 @@ function emitComponentWrappers(emitter) {
7621
7646
  if (!firstPart) jsxTagName = j.jsxIdentifier(renderedComponent);
7622
7647
  else jsxTagName = j.jsxMemberExpression(j.jsxIdentifier(firstPart), j.jsxIdentifier(parts.slice(1).join(".")));
7623
7648
  } else jsxTagName = j.jsxIdentifier(renderedComponent);
7624
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo);
7649
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
7625
7650
  const defaultAttrs = attrsInfo?.defaultAttrs ?? [];
7626
7651
  const staticAttrs = attrsInfo?.staticAttrs ?? {};
7627
7652
  const needsSxVar = allowClassNameProp || allowStyleProp || !!d.inlineStyleProps?.length || !!staticClassNameExpr;
@@ -8566,7 +8591,7 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
8566
8591
  }),
8567
8592
  j.restElement(restId)
8568
8593
  ]), propsId)]);
8569
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo);
8594
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
8570
8595
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
8571
8596
  attrsInfo,
8572
8597
  includeForwardedAs: includesForwardedAs
@@ -8840,7 +8865,7 @@ function emitShouldForwardPropWrappers(ctx) {
8840
8865
  });
8841
8866
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
8842
8867
  const cleanupPrefixStmt = dropPrefix && (isExportedComponent || shouldAllowAnyPrefixProps) && includeRest ? buildPrefixCleanupStatements(j, restId, dropPrefix) : null;
8843
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo);
8868
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
8844
8869
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
8845
8870
  attrsInfo,
8846
8871
  includeForwardedAs: includesForwardedAs
@@ -9280,32 +9305,59 @@ function splitExtraStyleArgs(j, stylesIdentifier, d) {
9280
9305
  };
9281
9306
  }
9282
9307
  /**
9308
+ * Build a static className expression combining an optional literal string
9309
+ * and/or a bridge class variable identifier.
9310
+ *
9311
+ * Returns `undefined` when neither is provided.
9312
+ */
9313
+ function buildStaticClassNameExpr(j, staticClassName, bridgeClassVar) {
9314
+ if (staticClassName && bridgeClassVar) {
9315
+ const raw = escapeTemplateRaw(`${staticClassName} `);
9316
+ return j.templateLiteral([j.templateElement({
9317
+ raw,
9318
+ cooked: `${staticClassName} `
9319
+ }, false), j.templateElement({
9320
+ raw: "",
9321
+ cooked: ""
9322
+ }, true)], [j.identifier(bridgeClassVar)]);
9323
+ }
9324
+ if (bridgeClassVar) return j.identifier(bridgeClassVar);
9325
+ if (staticClassName) return j.literal(staticClassName);
9326
+ }
9327
+ /**
9283
9328
  * Extracts a static className value (if present) from attrsInfo.staticAttrs
9284
9329
  * so it can be passed to the style merger separately, and returns the
9285
9330
  * remaining attrsInfo without that className entry.
9331
+ *
9332
+ * When `bridgeClassVar` is provided, it is used as an identifier expression
9333
+ * for the bridge class name. If a static className also exists, a template
9334
+ * literal combining both is produced.
9286
9335
  */
9287
- function splitAttrsInfo(j, attrsInfo) {
9336
+ function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
9288
9337
  const className = attrsInfo?.staticAttrs?.className;
9289
9338
  if (!attrsInfo) return {
9290
9339
  attrsInfo,
9291
- staticClassNameExpr: void 0
9340
+ staticClassNameExpr: buildStaticClassNameExpr(j, void 0, bridgeClassVar)
9292
9341
  };
9293
9342
  const normalized = {
9294
9343
  ...attrsInfo,
9295
9344
  staticAttrs: attrsInfo.staticAttrs ?? {},
9296
9345
  conditionalAttrs: attrsInfo.conditionalAttrs ?? []
9297
9346
  };
9298
- if (typeof className !== "string") return {
9347
+ const hasStaticClassName = typeof className === "string";
9348
+ if (!hasStaticClassName && !bridgeClassVar) return {
9299
9349
  attrsInfo: normalized,
9300
9350
  staticClassNameExpr: void 0
9301
9351
  };
9302
- const { className: _omit, ...rest } = normalized.staticAttrs;
9303
9352
  return {
9304
- attrsInfo: {
9305
- ...normalized,
9306
- staticAttrs: rest
9307
- },
9308
- staticClassNameExpr: j.literal(className)
9353
+ attrsInfo: hasStaticClassName ? (() => {
9354
+ const { className: _omit, ...rest } = normalized.staticAttrs;
9355
+ return {
9356
+ ...normalized,
9357
+ staticAttrs: rest
9358
+ };
9359
+ })() : normalized,
9360
+ staticClassNameExpr: buildStaticClassNameExpr(j, hasStaticClassName ? className : void 0, bridgeClassVar)
9309
9361
  };
9310
9362
  }
9311
9363
  /**
@@ -9499,6 +9551,10 @@ function collectDestructurePropsFromStyleFns(emitter, args) {
9499
9551
  }
9500
9552
  for (const prop of d.shouldForwardProp?.dropProps ?? []) if (prop && !destructureProps.includes(prop)) destructureProps.push(prop);
9501
9553
  }
9554
+ /** Escape characters that are special inside template literal quasi strings. */
9555
+ function escapeTemplateRaw(s) {
9556
+ return s.replace(/\\|`|\$\{/g, "\\$&");
9557
+ }
9502
9558
 
9503
9559
  //#endregion
9504
9560
  //#region src/internal/emit-wrappers/wrapper-emitter.ts
@@ -10014,7 +10070,7 @@ var WrapperEmitter = class {
10014
10070
  return false;
10015
10071
  }
10016
10072
  emitMinimalWrapper(args) {
10017
- const { localName, tagName, propsTypeName, inlineTypeText, styleArgs, destructureProps, propDefaults, allowClassNameProp = false, allowStyleProp = false, allowAsProp = false, includeRest = true, defaultAttrs = [], conditionalAttrs = [], invertedBoolAttrs = [], staticAttrs = {}, inlineStyleProps = [], attrsAsTag } = args;
10073
+ const { localName, tagName, propsTypeName, inlineTypeText, styleArgs, destructureProps, propDefaults, allowClassNameProp = false, allowStyleProp = false, allowAsProp = false, includeRest = true, defaultAttrs = [], conditionalAttrs = [], invertedBoolAttrs = [], staticAttrs = {}, inlineStyleProps = [], attrsAsTag, bridgeClassVar } = args;
10018
10074
  const { j } = this;
10019
10075
  const expandedDestructureProps = new Set(destructureProps.filter(Boolean));
10020
10076
  const collectCondIdentifiers = (node) => {
@@ -10097,6 +10153,7 @@ var WrapperEmitter = class {
10097
10153
  const { className: _omitClassName, ...rest } = staticAttrs;
10098
10154
  return rest;
10099
10155
  })();
10156
+ const staticClassNameExpr = buildStaticClassNameExpr(j, staticClassName, bridgeClassVar);
10100
10157
  const merging = emitStyleMerging({
10101
10158
  j,
10102
10159
  emitter: this,
@@ -10106,7 +10163,7 @@ var WrapperEmitter = class {
10106
10163
  allowClassNameProp,
10107
10164
  allowStyleProp,
10108
10165
  inlineStyleProps,
10109
- staticClassNameExpr: staticClassName ? j.literal(staticClassName) : void 0
10166
+ staticClassNameExpr
10110
10167
  });
10111
10168
  const jsxAttrs = [];
10112
10169
  for (const a of defaultAttrs) jsxAttrs.push(j.jsxAttribute(j.jsxIdentifier(a.attrName), j.jsxExpressionContainer(j.logicalExpression("??", j.identifier(a.jsxProp), this.literalExpr(a.value)))));
@@ -10187,8 +10244,8 @@ var WrapperEmitter = class {
10187
10244
  splitExtraStyleArgs(d) {
10188
10245
  return splitExtraStyleArgs(this.j, this.stylesIdentifier, d);
10189
10246
  }
10190
- splitAttrsInfo(attrsInfo) {
10191
- return splitAttrsInfo(this.j, attrsInfo);
10247
+ splitAttrsInfo(attrsInfo, bridgeClassVar) {
10248
+ return splitAttrsInfo(this.j, attrsInfo, bridgeClassVar);
10192
10249
  }
10193
10250
  buildVariantDimensionLookups(args) {
10194
10251
  buildVariantDimensionLookups(this.j, args);
@@ -10280,35 +10337,63 @@ function emitWrappersStep(ctx) {
10280
10337
  * Ensures the style merger import is present when the merger function is referenced.
10281
10338
  */
10282
10339
  function ensureMergerImportStep(ctx) {
10283
- const { root, j, adapter } = ctx;
10340
+ const { root, j, adapter, file } = ctx;
10284
10341
  if (adapter.styleMerger?.functionName && adapter.styleMerger.importSource) {
10285
10342
  const mergerName = adapter.styleMerger.functionName;
10286
- const hasMergerUsage = root.find(j.Identifier, { name: mergerName }).filter((p) => isIdentifierReference(p)).size() > 0;
10287
- const hasMergerImport = root.find(j.ImportSpecifier, { imported: {
10343
+ const hasMergerCall = root.find(j.CallExpression, { callee: {
10288
10344
  type: "Identifier",
10289
10345
  name: mergerName
10290
10346
  } }).size() > 0;
10291
- const hasLocalBinding = root.find(j.FunctionDeclaration, { id: { name: mergerName } }).size() > 0 || root.find(j.VariableDeclarator, { id: {
10292
- type: "Identifier",
10293
- name: mergerName
10294
- } }).size() > 0;
10295
- 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) {
10296
10353
  const source = adapter.styleMerger.importSource;
10297
- if (source.kind === "specifier") {
10298
- const decl = j.importDeclaration([j.importSpecifier(j.identifier(mergerName))], j.literal(source.value));
10299
- const stylexImport = root.find(j.ImportDeclaration, { source: { value: "@stylexjs/stylex" } });
10300
- if (stylexImport.size() > 0) stylexImport.at(stylexImport.size() - 1).insertAfter(decl);
10301
- else {
10302
- const firstImport = root.find(j.ImportDeclaration).at(0);
10303
- if (firstImport.size() > 0) firstImport.insertBefore(decl);
10304
- else root.get().node.program.body.unshift(decl);
10305
- }
10306
- 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);
10307
10362
  }
10363
+ ctx.markChanged();
10308
10364
  }
10309
10365
  }
10310
10366
  return CONTINUE;
10311
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
+ }
10312
10397
 
10313
10398
  //#endregion
10314
10399
  //#region src/internal/transform-steps/ensure-react-import.ts
@@ -20582,8 +20667,53 @@ function postProcessTransformedAst(args) {
20582
20667
  elementStyleKey,
20583
20668
  markerVarName: addedMarkerVarName
20584
20669
  }];
20585
- for (const c of node.children ?? []) if (c?.type === "JSXElement") visit(c, nextAncestors);
20670
+ for (const child of node.children ?? []) visitJsxChild(child, nextAncestors);
20586
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
+ }
20587
20717
  root.find(j.JSXElement).forEach((p) => {
20588
20718
  if (j(p).closest(j.JSXElement).size() > 0) return;
20589
20719
  visit(p.node, []);
@@ -21202,6 +21332,10 @@ function transform(file, api, options) {
21202
21332
  const bridgeResultsMap = options.bridgeResults;
21203
21333
  if (bridgeResultsMap) bridgeResultsMap.set(toRealPath(file.path), result.bridgeResults);
21204
21334
  }
21335
+ if (result.code !== null) {
21336
+ const transformedFiles = options.transformedFiles;
21337
+ if (transformedFiles) transformedFiles.add(toRealPath(file.path));
21338
+ }
21205
21339
  return result.code;
21206
21340
  } catch (e) {
21207
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.15",
3
+ "version": "0.0.17",
4
4
  "description": "Codemod to transform styled-components to StyleX",
5
5
  "keywords": [
6
6
  "codemod",