styled-components-to-stylex-codemod 0.0.29 → 0.0.30

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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as defineAdapter, i as AdapterInput, t as CollectedWarning } from "./logger-Dx-dRi_p.mjs";
1
+ import { a as defineAdapter, i as AdapterInput, t as CollectedWarning } from "./logger-BQnWs1On.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
@@ -59,6 +59,11 @@ interface RunTransformOptions {
59
59
  * @default 3
60
60
  */
61
61
  maxExamples?: number;
62
+ /**
63
+ * Suppress jscodeshift runner output.
64
+ * @default false
65
+ */
66
+ silent?: boolean;
62
67
  }
63
68
  interface RunTransformResult {
64
69
  /** Number of files that had errors */
package/dist/index.mjs CHANGED
@@ -231,7 +231,7 @@ async function runTransform(options) {
231
231
  transformedFiles,
232
232
  transientPropRenames,
233
233
  runInBand: true,
234
- silent: true
234
+ silent: options.silent ?? false
235
235
  });
236
236
  if (sidecarFiles.size > 0 && !dryRun) for (const [sidecarPath, content] of sidecarFiles) await writeFile(sidecarPath, mergeSidecarContent(sidecarPath, content), "utf-8");
237
237
  if (bridgeResults.size > 0 && !dryRun) {
@@ -260,6 +260,19 @@ type CallResolveResultWithExpr = {
260
260
  * - Theme access in the original call is rewritten to use the wrapper `useTheme()` value.
261
261
  */
262
262
  preserveRuntimeCall?: boolean;
263
+ /**
264
+ * Additional className expressions to merge into the component's className attribute.
265
+ *
266
+ * Used for CSS modules or other class-based styles that StyleX cannot express
267
+ * (child selectors like `& > *`, ancestor selectors like `html:not(.class) &`, etc.).
268
+ *
269
+ * Each entry provides an expression string (e.g., `cssModuleStyles.myClass`)
270
+ * and its required imports.
271
+ *
272
+ * When present, the codemod merges these expressions into the rendered element's
273
+ * className alongside any existing static className from `.attrs()` or bridge classes.
274
+ */
275
+ extraClassNames?: ExprWithImports[];
263
276
  };
264
277
  type CallResolveRuntimeOnlyResult = {
265
278
  /**
@@ -275,7 +288,17 @@ type CallResolveRuntimeOnlyResult = {
275
288
  */
276
289
  usage?: "create";
277
290
  };
278
- type CallResolveResult = CallResolveResultWithExpr | CallResolveRuntimeOnlyResult;
291
+ /**
292
+ * Resolved result containing only className expressions (no StyleX style object).
293
+ * Used for CSS modules or other class-based styles that StyleX cannot express.
294
+ */
295
+ type CallResolveClassNamesResult = {
296
+ /**
297
+ * className expressions to merge into the component's className attribute.
298
+ */
299
+ extraClassNames: ExprWithImports[];
300
+ };
301
+ type CallResolveResult = CallResolveResultWithExpr | CallResolveRuntimeOnlyResult | CallResolveClassNamesResult;
279
302
  type ImportSource = {
280
303
  kind: "absolutePath";
281
304
  value: string;
@@ -290,6 +313,11 @@ type ImportSpec = {
290
313
  local?: string;
291
314
  }>;
292
315
  };
316
+ /** An expression string with its required imports, used for className emission. */
317
+ type ExprWithImports = {
318
+ expr: string;
319
+ imports: ImportSpec[];
320
+ };
293
321
  type ResolveBaseComponentStaticValue = string | number | boolean;
294
322
  interface ResolveBaseComponentContext {
295
323
  /**
@@ -1,4 +1,4 @@
1
- import { n as WarningLog, r as Adapter } from "./logger-Dx-dRi_p.mjs";
1
+ import { n as WarningLog, r as Adapter } from "./logger-BQnWs1On.mjs";
2
2
  import { API, FileInfo, Options } from "jscodeshift";
3
3
 
4
4
  //#region src/internal/transform-types.d.ts
@@ -2229,7 +2229,7 @@ function analyzeBeforeEmitStep(ctx) {
2229
2229
  if (!hadWrapperBeforePrepass && decl.needsWrapperComponent) wrapperForcedByPrepass.add(decl.localName);
2230
2230
  if (ctx.bridgeComponentNames?.has(decl.localName) || ctx.bridgeComponentNames?.has("default") && exportedComponents.get(decl.localName)?.isDefault) decl.bridgeClassName = generateBridgeClassName(resolve(file.path), decl.localName);
2231
2231
  }
2232
- for (const decl of styledDecls) if (decl.needsWrapperComponent && !wrapperForcedByPrepass.has(decl.localName) && !decl.isCssHelper && !decl.isDirectJsxResolution && decl.base.kind === "intrinsic" && !decl.bridgeClassName && !decl.attrWrapper && (decl.styleFnFromProps ?? []).length === 0 && !decl.needsUseThemeHook?.length && Object.keys(decl.variantStyleKeys ?? {}).length === 0 && !decl.enumVariant && !decl.inlineStyleProps?.length && (decl.attrsInfo?.conditionalAttrs?.length ?? 0) === 0 && (decl.attrsInfo?.defaultAttrs?.length ?? 0) === 0 && (decl.attrsInfo?.invertedBoolAttrs?.length ?? 0) === 0 && !(decl.extraStylexPropsArgs ?? []).some((arg) => arg.when) && ((decl.extraStylexPropsArgs ?? []).length > 0 || (decl.extraStyleKeys ?? []).length > 0)) decl.needsWrapperComponent = false;
2232
+ for (const decl of styledDecls) if (decl.needsWrapperComponent && !wrapperForcedByPrepass.has(decl.localName) && !decl.isCssHelper && !decl.isDirectJsxResolution && decl.base.kind === "intrinsic" && !decl.bridgeClassName && !decl.attrWrapper && (decl.styleFnFromProps ?? []).length === 0 && !decl.needsUseThemeHook?.length && Object.keys(decl.variantStyleKeys ?? {}).length === 0 && !decl.enumVariant && !decl.inlineStyleProps?.length && (decl.attrsInfo?.conditionalAttrs?.length ?? 0) === 0 && (decl.attrsInfo?.defaultAttrs?.length ?? 0) === 0 && (decl.attrsInfo?.invertedBoolAttrs?.length ?? 0) === 0 && !(decl.extraStylexPropsArgs ?? []).some((arg) => arg.when) && ((decl.extraStylexPropsArgs ?? []).length > 0 || (decl.extraStyleKeys ?? []).length > 0 || (decl.extraClassNames ?? []).length > 0)) decl.needsWrapperComponent = false;
2233
2233
  const jsxUsageCountCache = /* @__PURE__ */ new Map();
2234
2234
  const relationChildStyleKeys = new Set((ctx.relationOverrides ?? []).map((o) => o.childStyleKey));
2235
2235
  const getJsxUsageCount = (name) => {
@@ -9048,6 +9048,7 @@ function emitStylesAndImports(ctx) {
9048
9048
  const moduleSpecifier = toModuleSpecifier(spec.from);
9049
9049
  const existing = root.find(j.ImportDeclaration, { source: { value: moduleSpecifier } });
9050
9050
  const toImportSpecifier = (imported, local) => {
9051
+ if (imported === "default") return j.importDefaultSpecifier(j.identifier(local ?? "default"));
9051
9052
  const impId = j.identifier(imported);
9052
9053
  if (local && local !== imported) return j.importSpecifier(impId, j.identifier(local));
9053
9054
  return j.importSpecifier(impId);
@@ -10239,19 +10240,60 @@ function buildInterleavedExtraStyleArgs(j, stylesIdentifier, d, propsArgExprs) {
10239
10240
  *
10240
10241
  * Returns `undefined` when neither is provided.
10241
10242
  */
10242
- function buildStaticClassNameExpr(j, staticClassName, bridgeClassVar) {
10243
- if (staticClassName && bridgeClassVar) {
10244
- const raw = escapeTemplateRaw(`${staticClassName} `);
10245
- return j.templateLiteral([j.templateElement({
10246
- raw,
10247
- cooked: `${staticClassName} `
10248
- }, false), j.templateElement({
10243
+ /** Escape characters that are special inside template literal quasi strings. */
10244
+ function escapeTemplateRaw(s) {
10245
+ return s.replace(/\\|`|\$\{/g, "\\$&");
10246
+ }
10247
+ function buildStaticClassNameExpr(j, staticClassName, bridgeClassVar, extraClassNames) {
10248
+ if (!(extraClassNames && extraClassNames.length > 0)) {
10249
+ if (staticClassName && bridgeClassVar) {
10250
+ const raw = escapeTemplateRaw(`${staticClassName} `);
10251
+ return j.templateLiteral([j.templateElement({
10252
+ raw,
10253
+ cooked: `${staticClassName} `
10254
+ }, false), j.templateElement({
10255
+ raw: "",
10256
+ cooked: ""
10257
+ }, true)], [j.identifier(bridgeClassVar)]);
10258
+ }
10259
+ if (bridgeClassVar) return j.identifier(bridgeClassVar);
10260
+ if (staticClassName) return j.literal(staticClassName);
10261
+ return;
10262
+ }
10263
+ const expressions = [];
10264
+ const quasis = [];
10265
+ const leadingText = staticClassName ? `${escapeTemplateRaw(staticClassName)} ` : "";
10266
+ quasis.push(j.templateElement({
10267
+ raw: leadingText,
10268
+ cooked: staticClassName ? `${staticClassName} ` : ""
10269
+ }, false));
10270
+ if (bridgeClassVar) {
10271
+ expressions.push(j.identifier(bridgeClassVar));
10272
+ quasis.push(j.templateElement({
10273
+ raw: " ",
10274
+ cooked: " "
10275
+ }, false));
10276
+ }
10277
+ for (let i = 0; i < extraClassNames.length; i++) {
10278
+ expressions.push(extraClassNames[i].expr);
10279
+ if (i === extraClassNames.length - 1) quasis.push(j.templateElement({
10249
10280
  raw: "",
10250
10281
  cooked: ""
10251
- }, true)], [j.identifier(bridgeClassVar)]);
10282
+ }, true));
10283
+ else quasis.push(j.templateElement({
10284
+ raw: " ",
10285
+ cooked: " "
10286
+ }, false));
10252
10287
  }
10253
- if (bridgeClassVar) return j.identifier(bridgeClassVar);
10254
- if (staticClassName) return j.literal(staticClassName);
10288
+ if (expressions.length === 0) {
10289
+ if (staticClassName) return j.literal(staticClassName);
10290
+ return;
10291
+ }
10292
+ if (!staticClassName && !bridgeClassVar) quasis[0] = j.templateElement({
10293
+ raw: "",
10294
+ cooked: ""
10295
+ }, false);
10296
+ return j.templateLiteral(quasis, expressions);
10255
10297
  }
10256
10298
  /**
10257
10299
  * Extracts a static className value (if present) from attrsInfo.staticAttrs
@@ -10261,12 +10303,15 @@ function buildStaticClassNameExpr(j, staticClassName, bridgeClassVar) {
10261
10303
  * When `bridgeClassVar` is provided, it is used as an identifier expression
10262
10304
  * for the bridge class name. If a static className also exists, a template
10263
10305
  * literal combining both is produced.
10306
+ *
10307
+ * When `extraClassNames` is provided, the expressions are merged into the
10308
+ * static className expression.
10264
10309
  */
10265
- function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
10310
+ function splitAttrsInfo(j, attrsInfo, bridgeClassVar, extraClassNames) {
10266
10311
  const className = attrsInfo?.staticAttrs?.className;
10267
10312
  if (!attrsInfo) return {
10268
10313
  attrsInfo,
10269
- staticClassNameExpr: buildStaticClassNameExpr(j, void 0, bridgeClassVar)
10314
+ staticClassNameExpr: buildStaticClassNameExpr(j, void 0, bridgeClassVar, extraClassNames)
10270
10315
  };
10271
10316
  const normalized = {
10272
10317
  ...attrsInfo,
@@ -10274,7 +10319,8 @@ function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
10274
10319
  conditionalAttrs: attrsInfo.conditionalAttrs ?? []
10275
10320
  };
10276
10321
  const hasStaticClassName = typeof className === "string";
10277
- if (!hasStaticClassName && !bridgeClassVar) return {
10322
+ const hasExtraClassNames = extraClassNames && extraClassNames.length > 0;
10323
+ if (!hasStaticClassName && !bridgeClassVar && !hasExtraClassNames) return {
10278
10324
  attrsInfo: normalized,
10279
10325
  staticClassNameExpr: void 0
10280
10326
  };
@@ -10286,7 +10332,7 @@ function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
10286
10332
  staticAttrs: rest
10287
10333
  };
10288
10334
  })() : normalized,
10289
- staticClassNameExpr: buildStaticClassNameExpr(j, hasStaticClassName ? className : void 0, bridgeClassVar)
10335
+ staticClassNameExpr: buildStaticClassNameExpr(j, hasStaticClassName ? className : void 0, bridgeClassVar, extraClassNames)
10290
10336
  };
10291
10337
  }
10292
10338
  /**
@@ -10590,10 +10636,6 @@ function buildVariantStyleExprs(opts) {
10590
10636
  }), when);
10591
10637
  }
10592
10638
  }
10593
- /** Escape characters that are special inside template literal quasi strings. */
10594
- function escapeTemplateRaw(s) {
10595
- return s.replace(/\\|`|\$\{/g, "\\$&");
10596
- }
10597
10639
  /**
10598
10640
  * Appends conditional style args driven by theme boolean props (e.g., `theme.isDark`).
10599
10641
  * Returns `true` if the hook is needed (and calls `markNeedsUseThemeImport`).
@@ -11611,8 +11653,8 @@ var WrapperEmitter = class {
11611
11653
  buildInterleavedExtraStyleArgs(d, propsArgExprs) {
11612
11654
  return buildInterleavedExtraStyleArgs(this.j, this.stylesIdentifier, d, propsArgExprs);
11613
11655
  }
11614
- splitAttrsInfo(attrsInfo, bridgeClassVar) {
11615
- return splitAttrsInfo(this.j, attrsInfo, bridgeClassVar);
11656
+ splitAttrsInfo(attrsInfo, bridgeClassVar, extraClassNames) {
11657
+ return splitAttrsInfo(this.j, attrsInfo, bridgeClassVar, extraClassNames);
11616
11658
  }
11617
11659
  buildVariantDimensionLookups(args) {
11618
11660
  buildVariantDimensionLookups(this.j, args);
@@ -12002,7 +12044,7 @@ function emitComponentWrappers(emitter) {
12002
12044
  if (!firstPart) jsxTagName = j.jsxIdentifier(renderedComponent);
12003
12045
  else jsxTagName = j.jsxMemberExpression(j.jsxIdentifier(firstPart), j.jsxIdentifier(parts.slice(1).join(".")));
12004
12046
  } else jsxTagName = j.jsxIdentifier(renderedComponent);
12005
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
12047
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
12006
12048
  const defaultAttrs = attrsInfo?.defaultAttrs ?? [];
12007
12049
  const staticAttrs = attrsInfo?.staticAttrs ?? {};
12008
12050
  const needsSxVar = allowClassNameProp || allowStyleProp || !!d.inlineStyleProps?.length || !!staticClassNameExpr;
@@ -12777,7 +12819,7 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
12777
12819
  restId
12778
12820
  });
12779
12821
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
12780
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
12822
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
12781
12823
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
12782
12824
  attrsInfo,
12783
12825
  includeForwardedAs: includesForwardedAs
@@ -13132,7 +13174,7 @@ function emitShouldForwardPropWrappers(ctx) {
13132
13174
  });
13133
13175
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
13134
13176
  const cleanupPrefixStmt = dropPrefix && (isExportedComponent || (d.supportsExternalStyles ?? false) || shouldAllowAnyPrefixProps) && includeRest ? buildPrefixCleanupStatements(j, restId, dropPrefix) : null;
13135
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
13177
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
13136
13178
  let attrsInfoForJsx;
13137
13179
  let forwardedAsStaticFallback;
13138
13180
  if (!allowClassNameProp && !allowStyleProp) {
@@ -13365,7 +13407,7 @@ function emitSimpleWithConfigWrappers(ctx) {
13365
13407
  restId
13366
13408
  });
13367
13409
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
13368
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
13410
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
13369
13411
  const merging = emitStyleMerging({
13370
13412
  j,
13371
13413
  emitter,
@@ -13696,7 +13738,7 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
13696
13738
  });
13697
13739
  const usePropsChildrenDirectly = emitter.isChildrenOnlyDestructurePattern(patternProps);
13698
13740
  const declStmt = usePropsChildrenDirectly ? null : j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
13699
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
13741
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
13700
13742
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
13701
13743
  attrsInfo,
13702
13744
  includeForwardedAs: includesForwardedAs
@@ -15739,12 +15781,21 @@ const createImportResolver = (args) => {
15739
15781
  };
15740
15782
  //#endregion
15741
15783
  //#region src/internal/lower-rules/state.ts
15784
+ /**
15785
+ * Builds shared state for the lower-rules pipeline.
15786
+ * Core concepts: resolver wiring, precomputed mixin values, and shared tracking maps.
15787
+ */
15742
15788
  function createLowerRulesState(ctx) {
15743
15789
  const { api, j, root, file, warnings, resolverImports, keyframesNames, parseExpr, rewriteCssVarsInStyleObject } = ctx;
15744
15790
  const filePath = file.path;
15745
15791
  const resolveValue = ctx.resolveValueSafe;
15746
15792
  const resolveValueDirectional = ctx.resolveValueDirectionalSafe;
15747
15793
  const resolveCall = ctx.resolveCallSafe;
15794
+ const resolveValueOptional = (rvCtx) => {
15795
+ const res = ctx.adapter.resolveValue(rvCtx);
15796
+ if (res && isDirectionalResult(res)) return;
15797
+ return res;
15798
+ };
15748
15799
  const resolveCallOptional = ctx.adapter.resolveCall.bind(ctx.adapter);
15749
15800
  const resolveSelector = ctx.resolveSelectorSafe;
15750
15801
  const importMap = ctx.importMap ?? /* @__PURE__ */ new Map();
@@ -15875,6 +15926,7 @@ function createLowerRulesState(ctx) {
15875
15926
  parseExpr,
15876
15927
  rewriteCssVarsInStyleObject,
15877
15928
  resolveValue,
15929
+ resolveValueOptional,
15878
15930
  resolveValueDirectional,
15879
15931
  resolveCall,
15880
15932
  resolveCallOptional,
@@ -16523,6 +16575,11 @@ function isAdapterResultCssValue(result, cssProperty) {
16523
16575
  * or "resolvedStyles" for StyleX references (to be used in stylex.props arguments).
16524
16576
  */
16525
16577
  function buildResolvedHandlerResult(result, cssProperty, payload) {
16578
+ if ("extraClassNames" in result && !("expr" in result)) return {
16579
+ type: "resolvedClassNames",
16580
+ extraClassNames: result.extraClassNames,
16581
+ ...payload
16582
+ };
16526
16583
  if (!("expr" in result)) return {
16527
16584
  type: "runtimeCallOnly",
16528
16585
  ...payload
@@ -16537,6 +16594,7 @@ function buildResolvedHandlerResult(result, cssProperty, payload) {
16537
16594
  expr: result.expr,
16538
16595
  imports: result.imports,
16539
16596
  ...result.cssText ? { cssText: result.cssText } : {},
16597
+ ...result.extraClassNames ? { extraClassNames: result.extraClassNames } : {},
16540
16598
  ...payload
16541
16599
  };
16542
16600
  }
@@ -16560,20 +16618,22 @@ function getArrowFnThemeParamInfo(fn) {
16560
16618
  propsName: p.name
16561
16619
  };
16562
16620
  if (p?.type !== "ObjectPattern" || !Array.isArray(p.properties)) return null;
16621
+ let themeName = null;
16622
+ const siblingBindings = [];
16563
16623
  for (const prop of p.properties) {
16564
16624
  if (!prop || prop.type !== "Property" && prop.type !== "ObjectProperty") continue;
16565
16625
  const key = prop.key;
16566
- if (!key || key.type !== "Identifier" || key.name !== "theme") continue;
16626
+ if (!key || key.type !== "Identifier") continue;
16567
16627
  const value = prop.value;
16568
- if (value?.type === "Identifier" && typeof value.name === "string") return {
16569
- kind: "themeBinding",
16570
- themeName: value.name
16571
- };
16572
- if (value?.type === "AssignmentPattern" && value.left?.type === "Identifier" && typeof value.left.name === "string") return {
16573
- kind: "themeBinding",
16574
- themeName: value.left.name
16575
- };
16628
+ const binding = extractBindingName(value) ?? key.name;
16629
+ if (key.name === "theme") themeName = binding;
16630
+ else siblingBindings.push(binding);
16576
16631
  }
16632
+ if (themeName) return {
16633
+ kind: "themeBinding",
16634
+ themeName,
16635
+ siblingBindings
16636
+ };
16577
16637
  return null;
16578
16638
  }
16579
16639
  /**
@@ -16782,6 +16842,17 @@ function callArgFromNode(node, propsParamName, themeBindingName) {
16782
16842
  }
16783
16843
  return { kind: "unknown" };
16784
16844
  }
16845
+ /**
16846
+ * Extracts the actual binding name from a destructured property value.
16847
+ * Handles: `{ x }` → "x", `{ x: alias }` → "alias", `{ x: alias = def }` → "alias"
16848
+ */
16849
+ function extractBindingName(value) {
16850
+ if (!value || typeof value !== "object") return null;
16851
+ const v = value;
16852
+ if (v.type === "Identifier" && typeof v.name === "string") return v.name;
16853
+ if (v.type === "AssignmentPattern" && v.left?.type === "Identifier" && typeof v.left.name === "string") return v.left.name;
16854
+ return null;
16855
+ }
16785
16856
  function callArgsFromNode(args, propsParamName, themeBindingName) {
16786
16857
  if (!Array.isArray(args)) return [];
16787
16858
  return args.map((arg) => callArgFromNode(arg, propsParamName, themeBindingName));
@@ -16807,7 +16878,7 @@ function tryResolveConditionalValue(node, ctx) {
16807
16878
  if (!body || body.type !== "ConditionalExpression") return null;
16808
16879
  const checkThemeBooleanTest = (test) => {
16809
16880
  const check = (node) => {
16810
- if (!node || typeof node !== "object" || node.type !== "MemberExpression") return null;
16881
+ if (!node || typeof node !== "object" || !isMemberExpression(node)) return null;
16811
16882
  if (info?.kind === "propsParam" && paramName) {
16812
16883
  const parts = getMemberPathFromIdentifier(node, paramName);
16813
16884
  const themeProp = parts?.[1];
@@ -16836,7 +16907,7 @@ function tryResolveConditionalValue(node, ctx) {
16836
16907
  return null;
16837
16908
  };
16838
16909
  const resolveThemeFromMemberExpr = (node) => {
16839
- if (!node || typeof node !== "object" || node.type !== "MemberExpression") return null;
16910
+ if (!node || typeof node !== "object" || !isMemberExpression(node)) return null;
16840
16911
  if (info?.kind === "propsParam" && paramName) {
16841
16912
  const parts = getMemberPathFromIdentifier(node, paramName);
16842
16913
  if (!parts || parts[0] !== "theme") return null;
@@ -16852,7 +16923,7 @@ function tryResolveConditionalValue(node, ctx) {
16852
16923
  const resolveThemeBranchValue = (branch) => {
16853
16924
  const themeInfo = resolveThemeFromMemberExpr(branch);
16854
16925
  if (!themeInfo) return null;
16855
- const res = ctx.resolveValue({
16926
+ const res = (ctx.resolveValueOptional ?? ctx.resolveValue)({
16856
16927
  kind: "theme",
16857
16928
  path: themeInfo.path,
16858
16929
  filePath: ctx.filePath,
@@ -16960,7 +17031,7 @@ function tryResolveConditionalValue(node, ctx) {
16960
17031
  const res = resolveImportedHelperCall(call, ctx, propsParamName, cssProperty, themeBindingName);
16961
17032
  if (res.kind === "resolved") {
16962
17033
  if ("expr" in res.result) return res.result;
16963
- if (res.result.preserveRuntimeCall) {
17034
+ if ("preserveRuntimeCall" in res.result && res.result.preserveRuntimeCall) {
16964
17035
  runtimeCallState.info = {
16965
17036
  resolveCallContext: res.resolveCallContext,
16966
17037
  resolveCallResult: res.resolveCallResult
@@ -16975,7 +17046,7 @@ function tryResolveConditionalValue(node, ctx) {
16975
17046
  const innerRes = resolveImportedHelperCall(inner, ctx, propsParamName, cssProperty, themeBindingName);
16976
17047
  if (innerRes.kind === "resolved") {
16977
17048
  if ("expr" in innerRes.result) return innerRes.result;
16978
- if (innerRes.result.preserveRuntimeCall) {
17049
+ if ("preserveRuntimeCall" in innerRes.result && innerRes.result.preserveRuntimeCall) {
16979
17050
  runtimeCallState.info = {
16980
17051
  resolveCallContext: innerRes.resolveCallContext,
16981
17052
  resolveCallResult: innerRes.resolveCallResult
@@ -17736,10 +17807,9 @@ function tryBuildThemeBooleanInlineStyleFallback(args) {
17736
17807
  const { trueValue, falseValue, trueImports, falseImports, trueBranch, falseBranch, themeBoolInfo, cssProp, paramName, info } = args;
17737
17808
  if (trueValue === null === (falseValue === null)) return null;
17738
17809
  const resolvedBranchIsTrue = trueValue !== null;
17739
- const unresolvableBranch = resolvedBranchIsTrue ? falseBranch : trueBranch;
17740
- if (!hasCallExpression(unresolvableBranch)) return null;
17741
- const transformed = replaceThemeRefsWithHookVar(unresolvableBranch, paramName, info);
17810
+ const transformed = replaceThemeRefsWithHookVar(resolvedBranchIsTrue ? falseBranch : trueBranch, paramName, info);
17742
17811
  if (!transformed) return null;
17812
+ if (!isFullyTransformedThemeExpr(transformed, paramName, info)) return null;
17743
17813
  return {
17744
17814
  type: "splitThemeBooleanWithInlineStyleFallback",
17745
17815
  cssProp,
@@ -17751,17 +17821,49 @@ function tryBuildThemeBooleanInlineStyleFallback(args) {
17751
17821
  inlineExpr: transformed
17752
17822
  };
17753
17823
  }
17754
- /** Check if an expression tree contains any call expressions. */
17755
- function hasCallExpression(node) {
17756
- if (!node || typeof node !== "object") return false;
17757
- const n = node;
17758
- if (n.type === "CallExpression") return true;
17759
- for (const key of Object.keys(n)) {
17760
- if (key === "loc" || key === "comments") continue;
17761
- const child = n[key];
17762
- if (child && typeof child === "object" && hasCallExpression(child)) return true;
17824
+ /**
17825
+ * Validates that a transformed expression has no dangling references to
17826
+ * the original arrow function parameter or theme binding name.
17827
+ * After `replaceThemeRefsWithHookVar`, all `<paramName>.theme.*` should
17828
+ * have been rewritten to `theme.*`. If the param name still appears,
17829
+ * the expression accesses non-theme props and can't be safely used
17830
+ * with `useTheme()` alone.
17831
+ */
17832
+ function isFullyTransformedThemeExpr(transformed, paramName, info) {
17833
+ const ids = /* @__PURE__ */ new Set();
17834
+ collectFreeIdentifiers(transformed, ids);
17835
+ if (paramName && ids.has(paramName)) return false;
17836
+ if (info?.kind === "themeBinding") {
17837
+ if (info.themeName !== "theme" && ids.has(info.themeName)) return false;
17838
+ if (info.siblingBindings.some((b) => ids.has(b))) return false;
17839
+ }
17840
+ return true;
17841
+ }
17842
+ /**
17843
+ * Collects free variable identifiers from an AST node, excluding
17844
+ * non-computed property names in member expressions (e.g., in
17845
+ * `theme.color.bgSub`, only `theme` is a free variable; `color`
17846
+ * and `bgSub` are property accesses, not variable references).
17847
+ */
17848
+ function collectFreeIdentifiers(node, out) {
17849
+ if (!node || typeof node !== "object") return;
17850
+ if (Array.isArray(node)) {
17851
+ for (const child of node) collectFreeIdentifiers(child, out);
17852
+ return;
17853
+ }
17854
+ const typed = node;
17855
+ const nodeType = typed.type;
17856
+ if (nodeType === "MemberExpression" || nodeType === "OptionalMemberExpression") {
17857
+ collectFreeIdentifiers(typed.object, out);
17858
+ if (typed.computed) collectFreeIdentifiers(typed.property, out);
17859
+ return;
17860
+ }
17861
+ if (nodeType === "Identifier" && typeof typed.name === "string") out.add(typed.name);
17862
+ for (const key of Object.keys(typed)) {
17863
+ if (key === "loc" || key === "comments" || key === "type") continue;
17864
+ const child = typed[key];
17865
+ if (child && typeof child === "object") collectFreeIdentifiers(child, out);
17763
17866
  }
17764
- return false;
17765
17867
  }
17766
17868
  /**
17767
17869
  * Deep-clones an expression and replaces `<paramName>.theme.*` or `theme.*`
@@ -20725,7 +20827,7 @@ const createValuePatternHandlers = (ctx) => {
20725
20827
  * Core concepts: per-component style buckets, helper factories, and resolver wiring.
20726
20828
  */
20727
20829
  function createDeclProcessingState(state, decl) {
20728
- const { api, j, root, filePath, warnings, resolverImports, parseExpr, resolveValue, resolveValueDirectional, resolveCall, resolveCallOptional, resolveSelector, importMap, cssHelperFunctions, stringMappingFns, hasLocalThemeBinding, isCssHelperTaggedTemplate, resolveCssHelperTemplate, resolveImportInScope, usedCssHelperFunctions, enumValueMap, markBail } = state;
20830
+ const { api, j, root, filePath, warnings, resolverImports, parseExpr, resolveValue, resolveValueOptional, resolveValueDirectional, resolveCall, resolveCallOptional, resolveSelector, importMap, cssHelperFunctions, stringMappingFns, hasLocalThemeBinding, isCssHelperTaggedTemplate, resolveCssHelperTemplate, resolveImportInScope, usedCssHelperFunctions, enumValueMap, markBail } = state;
20729
20831
  const styleObj = {};
20730
20832
  const perPropPseudo = {};
20731
20833
  const perPropMedia = {};
@@ -20839,6 +20941,7 @@ function createDeclProcessingState(state, decl) {
20839
20941
  api,
20840
20942
  filePath,
20841
20943
  resolveValue,
20944
+ resolveValueOptional,
20842
20945
  resolveValueDirectional,
20843
20946
  resolveCall,
20844
20947
  resolveCallOptional,
@@ -22350,6 +22453,15 @@ function handleInterpolatedDeclaration(args) {
22350
22453
  if (!imports) return;
22351
22454
  for (const imp of imports) resolverImports.set(JSON.stringify(imp), imp);
22352
22455
  };
22456
+ /** Parse and store extra className expressions (from CSS modules) on the decl. */
22457
+ const collectExtraClassNames = (entries) => {
22458
+ decl.extraClassNames ??= [];
22459
+ for (const cn of entries) {
22460
+ addResolverImports(cn.imports);
22461
+ const cnExpr = parseExpr(cn.expr);
22462
+ if (cnExpr) decl.extraClassNames.push({ expr: cnExpr });
22463
+ }
22464
+ };
22353
22465
  /**
22354
22466
  * Try to convert an identity prop with a finite string union type into static variant
22355
22467
  * buckets. Returns true if the optimization applied and the caller should `continue`.
@@ -22371,7 +22483,7 @@ function handleInterpolatedDeclaration(args) {
22371
22483
  };
22372
22484
  const maybeEmitPreservedRuntimeCallOverride = (args) => {
22373
22485
  const { resolveCallResult, originalExpr, loc } = args;
22374
- if (!resolveCallResult?.preserveRuntimeCall) return "not-requested";
22486
+ if (!resolveCallResult || !("preserveRuntimeCall" in resolveCallResult) || !resolveCallResult.preserveRuntimeCall) return "not-requested";
22375
22487
  if (!d.property) {
22376
22488
  warnings.push({
22377
22489
  severity: "error",
@@ -22861,10 +22973,16 @@ function handleInterpolatedDeclaration(args) {
22861
22973
  expr: exprAst,
22862
22974
  afterBase: hasStaticPropsBefore
22863
22975
  });
22976
+ if (res.extraClassNames) collectExtraClassNames(res.extraClassNames);
22864
22977
  notifyResolvedStylesArg();
22865
22978
  decl.needsWrapperComponent = true;
22866
22979
  continue;
22867
22980
  }
22981
+ if (res && res.type === "resolvedClassNames") {
22982
+ collectExtraClassNames(res.extraClassNames);
22983
+ decl.needsWrapperComponent = true;
22984
+ continue;
22985
+ }
22868
22986
  if (res && res.type === "resolvedDirectional") {
22869
22987
  let directionalFailed = false;
22870
22988
  for (const entry of res.directional) {
@@ -22973,6 +23091,14 @@ function handleInterpolatedDeclaration(args) {
22973
23091
  continue;
22974
23092
  }
22975
23093
  if (res && res.type === "splitThemeBooleanWithInlineStyleFallback") {
23094
+ if (pseudos?.length || media || pseudoElement) {
23095
+ bail = true;
23096
+ continue;
23097
+ }
23098
+ if (isCssShorthandProperty(res.cssProp)) {
23099
+ bail = true;
23100
+ continue;
23101
+ }
22976
23102
  addResolverImports(res.resolvedImports);
22977
23103
  if (!decl.needsUseThemeHook) decl.needsUseThemeHook = [];
22978
23104
  if (!decl.needsUseThemeHook.some((e) => e.themeProp === res.themeProp)) decl.needsUseThemeHook.push({
@@ -27514,15 +27640,22 @@ function rewriteJsxStep(ctx) {
27514
27640
  styleAttr = null;
27515
27641
  }
27516
27642
  if (promotedMerge) styleAttr = null;
27517
- const needsMerge = classNameAttr !== null || styleAttr !== null;
27643
+ const extraClassNameExpr = decl.extraClassNames && decl.extraClassNames.length > 0 ? buildExtraClassNameExpr(j, decl.extraClassNames) : void 0;
27518
27644
  const isIntrinsicTag = /^[a-z]/.test(finalTag) && !finalTag.includes(".");
27519
- const stylexAttr = ctx.adapter.useSxProp && !needsMerge && isIntrinsicTag ? (() => {
27645
+ let effectiveClassNameAttr = classNameAttr;
27646
+ if (extraClassNameExpr && !ctx.adapter.useSxProp) effectiveClassNameAttr = j.jsxAttribute(j.jsxIdentifier("className"), j.jsxExpressionContainer(extraClassNameExpr));
27647
+ const needsMerge = effectiveClassNameAttr !== null || styleAttr !== null;
27648
+ const useSxProp = ctx.adapter.useSxProp && !needsMerge && isIntrinsicTag;
27649
+ const stylexAttr = useSxProp ? (() => {
27520
27650
  const sxExpr = styleArgs.length === 1 && styleArgs[0] ? styleArgs[0] : j.arrayExpression([...styleArgs]);
27521
27651
  return j.jsxAttribute(j.jsxIdentifier("sx"), j.jsxExpressionContainer(sxExpr));
27522
- })() : j.jsxSpreadAttribute(needsMerge ? buildInlineMergeCall(j, styleArgs, classNameAttr, styleAttr, ctx.adapter.styleMerger?.functionName) : j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("props")), [...styleArgs]));
27652
+ })() : j.jsxSpreadAttribute(needsMerge ? buildInlineMergeCall(j, styleArgs, effectiveClassNameAttr, styleAttr, ctx.adapter.styleMerger?.functionName) : j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("props")), [...styleArgs]));
27653
+ const extraClassNameAttrs = [];
27654
+ if (extraClassNameExpr && useSxProp) extraClassNameAttrs.push(j.jsxAttribute(j.jsxIdentifier("className"), j.jsxExpressionContainer(extraClassNameExpr)));
27523
27655
  const finalRest = [
27524
27656
  ...keptRestAfterVariants.slice(0, finalInsertIndex),
27525
27657
  stylexAttr,
27658
+ ...extraClassNameAttrs,
27526
27659
  ...keptRestAfterVariants.slice(finalInsertIndex)
27527
27660
  ];
27528
27661
  opening.attributes = [
@@ -27575,6 +27708,25 @@ function extractJsxAttrValueExpr(j, attr) {
27575
27708
  if (a.value.type === "JSXExpressionContainer") return a.value.expression;
27576
27709
  }
27577
27710
  /**
27711
+ * Builds a single expression from extra className entries (CSS module classes).
27712
+ * Single entry: returns the expression directly.
27713
+ * Multiple entries: joins with a template literal `${a} ${b}`.
27714
+ */
27715
+ function buildExtraClassNameExpr(j, extraClassNames) {
27716
+ const exprs = extraClassNames.map((cn) => cn.expr);
27717
+ if (exprs.length === 1 && exprs[0]) return exprs[0];
27718
+ const qs = [];
27719
+ for (let i = 0; i <= exprs.length; i++) {
27720
+ const isLast = i === exprs.length;
27721
+ const raw = i === 0 || isLast ? "" : " ";
27722
+ qs.push(j.templateElement({
27723
+ raw,
27724
+ cooked: raw
27725
+ }, isLast));
27726
+ }
27727
+ return j.templateLiteral(qs, exprs);
27728
+ }
27729
+ /**
27578
27730
  * Finds the combined style key matching the consumed props at a JSX call site.
27579
27731
  * Returns the style key if a matching combination exists, or undefined otherwise.
27580
27732
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styled-components-to-stylex-codemod",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "Codemod to transform styled-components to StyleX",
5
5
  "keywords": [
6
6
  "codemod",