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

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) => {
@@ -3637,8 +3637,10 @@ function normalizeStylisAstToIR(stylisAst, slots, options = {}) {
3637
3637
  const propsArr = Array.isArray(node.props) ? node.props : null;
3638
3638
  const rule = ensureRule((() => {
3639
3639
  if (!stripFormFeedInSelectors || !selectorValue.includes("\f") || !propsArr?.length) return selectorRaw;
3640
- const stringProps = propsArr.filter((p) => typeof p === "string");
3641
- if (stringProps.some((p) => p.includes("::")) && !selectorRaw.includes("::")) return stringProps.map((p) => `&${p}`).join(",");
3640
+ const resolved = propsArr.filter((p) => typeof p === "string").map((p) => `&${p}`).join(",");
3641
+ const hasSiblingCombinator = (s) => /[+~]/.test(s.replace(/\[[^\]]*\]/g, ""));
3642
+ if (hasSiblingCombinator(selectorRaw) || hasSiblingCombinator(resolved)) return selectorRaw;
3643
+ if (resolved.length > selectorRaw.length) return resolved;
3642
3644
  return selectorRaw;
3643
3645
  })(), atRuleStack);
3644
3646
  const children = node.children;
@@ -9048,6 +9050,7 @@ function emitStylesAndImports(ctx) {
9048
9050
  const moduleSpecifier = toModuleSpecifier(spec.from);
9049
9051
  const existing = root.find(j.ImportDeclaration, { source: { value: moduleSpecifier } });
9050
9052
  const toImportSpecifier = (imported, local) => {
9053
+ if (imported === "default") return j.importDefaultSpecifier(j.identifier(local ?? "default"));
9051
9054
  const impId = j.identifier(imported);
9052
9055
  if (local && local !== imported) return j.importSpecifier(impId, j.identifier(local));
9053
9056
  return j.importSpecifier(impId);
@@ -10239,19 +10242,60 @@ function buildInterleavedExtraStyleArgs(j, stylesIdentifier, d, propsArgExprs) {
10239
10242
  *
10240
10243
  * Returns `undefined` when neither is provided.
10241
10244
  */
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({
10245
+ /** Escape characters that are special inside template literal quasi strings. */
10246
+ function escapeTemplateRaw(s) {
10247
+ return s.replace(/\\|`|\$\{/g, "\\$&");
10248
+ }
10249
+ function buildStaticClassNameExpr(j, staticClassName, bridgeClassVar, extraClassNames) {
10250
+ if (!(extraClassNames && extraClassNames.length > 0)) {
10251
+ if (staticClassName && bridgeClassVar) {
10252
+ const raw = escapeTemplateRaw(`${staticClassName} `);
10253
+ return j.templateLiteral([j.templateElement({
10254
+ raw,
10255
+ cooked: `${staticClassName} `
10256
+ }, false), j.templateElement({
10257
+ raw: "",
10258
+ cooked: ""
10259
+ }, true)], [j.identifier(bridgeClassVar)]);
10260
+ }
10261
+ if (bridgeClassVar) return j.identifier(bridgeClassVar);
10262
+ if (staticClassName) return j.literal(staticClassName);
10263
+ return;
10264
+ }
10265
+ const expressions = [];
10266
+ const quasis = [];
10267
+ const leadingText = staticClassName ? `${escapeTemplateRaw(staticClassName)} ` : "";
10268
+ quasis.push(j.templateElement({
10269
+ raw: leadingText,
10270
+ cooked: staticClassName ? `${staticClassName} ` : ""
10271
+ }, false));
10272
+ if (bridgeClassVar) {
10273
+ expressions.push(j.identifier(bridgeClassVar));
10274
+ quasis.push(j.templateElement({
10275
+ raw: " ",
10276
+ cooked: " "
10277
+ }, false));
10278
+ }
10279
+ for (let i = 0; i < extraClassNames.length; i++) {
10280
+ expressions.push(extraClassNames[i].expr);
10281
+ if (i === extraClassNames.length - 1) quasis.push(j.templateElement({
10249
10282
  raw: "",
10250
10283
  cooked: ""
10251
- }, true)], [j.identifier(bridgeClassVar)]);
10284
+ }, true));
10285
+ else quasis.push(j.templateElement({
10286
+ raw: " ",
10287
+ cooked: " "
10288
+ }, false));
10289
+ }
10290
+ if (expressions.length === 0) {
10291
+ if (staticClassName) return j.literal(staticClassName);
10292
+ return;
10252
10293
  }
10253
- if (bridgeClassVar) return j.identifier(bridgeClassVar);
10254
- if (staticClassName) return j.literal(staticClassName);
10294
+ if (!staticClassName && !bridgeClassVar) quasis[0] = j.templateElement({
10295
+ raw: "",
10296
+ cooked: ""
10297
+ }, false);
10298
+ return j.templateLiteral(quasis, expressions);
10255
10299
  }
10256
10300
  /**
10257
10301
  * Extracts a static className value (if present) from attrsInfo.staticAttrs
@@ -10261,12 +10305,15 @@ function buildStaticClassNameExpr(j, staticClassName, bridgeClassVar) {
10261
10305
  * When `bridgeClassVar` is provided, it is used as an identifier expression
10262
10306
  * for the bridge class name. If a static className also exists, a template
10263
10307
  * literal combining both is produced.
10308
+ *
10309
+ * When `extraClassNames` is provided, the expressions are merged into the
10310
+ * static className expression.
10264
10311
  */
10265
- function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
10312
+ function splitAttrsInfo(j, attrsInfo, bridgeClassVar, extraClassNames) {
10266
10313
  const className = attrsInfo?.staticAttrs?.className;
10267
10314
  if (!attrsInfo) return {
10268
10315
  attrsInfo,
10269
- staticClassNameExpr: buildStaticClassNameExpr(j, void 0, bridgeClassVar)
10316
+ staticClassNameExpr: buildStaticClassNameExpr(j, void 0, bridgeClassVar, extraClassNames)
10270
10317
  };
10271
10318
  const normalized = {
10272
10319
  ...attrsInfo,
@@ -10274,7 +10321,8 @@ function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
10274
10321
  conditionalAttrs: attrsInfo.conditionalAttrs ?? []
10275
10322
  };
10276
10323
  const hasStaticClassName = typeof className === "string";
10277
- if (!hasStaticClassName && !bridgeClassVar) return {
10324
+ const hasExtraClassNames = extraClassNames && extraClassNames.length > 0;
10325
+ if (!hasStaticClassName && !bridgeClassVar && !hasExtraClassNames) return {
10278
10326
  attrsInfo: normalized,
10279
10327
  staticClassNameExpr: void 0
10280
10328
  };
@@ -10286,7 +10334,7 @@ function splitAttrsInfo(j, attrsInfo, bridgeClassVar) {
10286
10334
  staticAttrs: rest
10287
10335
  };
10288
10336
  })() : normalized,
10289
- staticClassNameExpr: buildStaticClassNameExpr(j, hasStaticClassName ? className : void 0, bridgeClassVar)
10337
+ staticClassNameExpr: buildStaticClassNameExpr(j, hasStaticClassName ? className : void 0, bridgeClassVar, extraClassNames)
10290
10338
  };
10291
10339
  }
10292
10340
  /**
@@ -10590,10 +10638,6 @@ function buildVariantStyleExprs(opts) {
10590
10638
  }), when);
10591
10639
  }
10592
10640
  }
10593
- /** Escape characters that are special inside template literal quasi strings. */
10594
- function escapeTemplateRaw(s) {
10595
- return s.replace(/\\|`|\$\{/g, "\\$&");
10596
- }
10597
10641
  /**
10598
10642
  * Appends conditional style args driven by theme boolean props (e.g., `theme.isDark`).
10599
10643
  * Returns `true` if the hook is needed (and calls `markNeedsUseThemeImport`).
@@ -11611,8 +11655,8 @@ var WrapperEmitter = class {
11611
11655
  buildInterleavedExtraStyleArgs(d, propsArgExprs) {
11612
11656
  return buildInterleavedExtraStyleArgs(this.j, this.stylesIdentifier, d, propsArgExprs);
11613
11657
  }
11614
- splitAttrsInfo(attrsInfo, bridgeClassVar) {
11615
- return splitAttrsInfo(this.j, attrsInfo, bridgeClassVar);
11658
+ splitAttrsInfo(attrsInfo, bridgeClassVar, extraClassNames) {
11659
+ return splitAttrsInfo(this.j, attrsInfo, bridgeClassVar, extraClassNames);
11616
11660
  }
11617
11661
  buildVariantDimensionLookups(args) {
11618
11662
  buildVariantDimensionLookups(this.j, args);
@@ -12002,7 +12046,7 @@ function emitComponentWrappers(emitter) {
12002
12046
  if (!firstPart) jsxTagName = j.jsxIdentifier(renderedComponent);
12003
12047
  else jsxTagName = j.jsxMemberExpression(j.jsxIdentifier(firstPart), j.jsxIdentifier(parts.slice(1).join(".")));
12004
12048
  } else jsxTagName = j.jsxIdentifier(renderedComponent);
12005
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
12049
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
12006
12050
  const defaultAttrs = attrsInfo?.defaultAttrs ?? [];
12007
12051
  const staticAttrs = attrsInfo?.staticAttrs ?? {};
12008
12052
  const needsSxVar = allowClassNameProp || allowStyleProp || !!d.inlineStyleProps?.length || !!staticClassNameExpr;
@@ -12777,7 +12821,7 @@ function emitIntrinsicPolymorphicWrappers(ctx) {
12777
12821
  restId
12778
12822
  });
12779
12823
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
12780
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
12824
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
12781
12825
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
12782
12826
  attrsInfo,
12783
12827
  includeForwardedAs: includesForwardedAs
@@ -13132,7 +13176,7 @@ function emitShouldForwardPropWrappers(ctx) {
13132
13176
  });
13133
13177
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
13134
13178
  const cleanupPrefixStmt = dropPrefix && (isExportedComponent || (d.supportsExternalStyles ?? false) || shouldAllowAnyPrefixProps) && includeRest ? buildPrefixCleanupStatements(j, restId, dropPrefix) : null;
13135
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
13179
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
13136
13180
  let attrsInfoForJsx;
13137
13181
  let forwardedAsStaticFallback;
13138
13182
  if (!allowClassNameProp && !allowStyleProp) {
@@ -13365,7 +13409,7 @@ function emitSimpleWithConfigWrappers(ctx) {
13365
13409
  restId
13366
13410
  });
13367
13411
  const declStmt = j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
13368
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
13412
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
13369
13413
  const merging = emitStyleMerging({
13370
13414
  j,
13371
13415
  emitter,
@@ -13696,7 +13740,7 @@ function emitSimpleExportedIntrinsicWrappers(ctx) {
13696
13740
  });
13697
13741
  const usePropsChildrenDirectly = emitter.isChildrenOnlyDestructurePattern(patternProps);
13698
13742
  const declStmt = usePropsChildrenDirectly ? null : j.variableDeclaration("const", [j.variableDeclarator(j.objectPattern(patternProps), propsId)]);
13699
- const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d));
13743
+ const { attrsInfo, staticClassNameExpr } = emitter.splitAttrsInfo(d.attrsInfo, getBridgeClassVar(d), d.extraClassNames);
13700
13744
  const { attrsInfo: attrsInfoWithoutForwardedAsStatic, forwardedAsStaticFallback } = splitForwardedAsStaticAttrs({
13701
13745
  attrsInfo,
13702
13746
  includeForwardedAs: includesForwardedAs
@@ -14505,13 +14549,18 @@ function parseSingleSelector(selector) {
14505
14549
  kind: "unsupported",
14506
14550
  reason: "attribute selector"
14507
14551
  };
14508
- if (pseudoClasses.length > 0 || pseudoElements.length > 0) return {
14552
+ if (pseudoElements.length > 0) return {
14509
14553
  kind: "unsupported",
14510
- reason: "attribute selector with pseudo"
14554
+ reason: "attribute selector with pseudo-element"
14555
+ };
14556
+ const attrStr = attributes.map((a) => a.toString()).join("");
14557
+ if (pseudoClasses.length > 0) return {
14558
+ kind: "pseudo",
14559
+ pseudos: [`${buildPseudoString(pseudoClasses)}:is(${attrStr})`]
14511
14560
  };
14512
14561
  return {
14513
14562
  kind: "pseudo",
14514
- pseudos: [`:is(${attributes.map((a) => a.toString()).join("")})`]
14563
+ pseudos: [`:is(${attrStr})`]
14515
14564
  };
14516
14565
  }
14517
14566
  if (!hasNesting && (pseudoClasses.length > 0 || pseudoElements.length > 0)) {}
@@ -15739,12 +15788,21 @@ const createImportResolver = (args) => {
15739
15788
  };
15740
15789
  //#endregion
15741
15790
  //#region src/internal/lower-rules/state.ts
15791
+ /**
15792
+ * Builds shared state for the lower-rules pipeline.
15793
+ * Core concepts: resolver wiring, precomputed mixin values, and shared tracking maps.
15794
+ */
15742
15795
  function createLowerRulesState(ctx) {
15743
15796
  const { api, j, root, file, warnings, resolverImports, keyframesNames, parseExpr, rewriteCssVarsInStyleObject } = ctx;
15744
15797
  const filePath = file.path;
15745
15798
  const resolveValue = ctx.resolveValueSafe;
15746
15799
  const resolveValueDirectional = ctx.resolveValueDirectionalSafe;
15747
15800
  const resolveCall = ctx.resolveCallSafe;
15801
+ const resolveValueOptional = (rvCtx) => {
15802
+ const res = ctx.adapter.resolveValue(rvCtx);
15803
+ if (res && isDirectionalResult(res)) return;
15804
+ return res;
15805
+ };
15748
15806
  const resolveCallOptional = ctx.adapter.resolveCall.bind(ctx.adapter);
15749
15807
  const resolveSelector = ctx.resolveSelectorSafe;
15750
15808
  const importMap = ctx.importMap ?? /* @__PURE__ */ new Map();
@@ -15875,6 +15933,7 @@ function createLowerRulesState(ctx) {
15875
15933
  parseExpr,
15876
15934
  rewriteCssVarsInStyleObject,
15877
15935
  resolveValue,
15936
+ resolveValueOptional,
15878
15937
  resolveValueDirectional,
15879
15938
  resolveCall,
15880
15939
  resolveCallOptional,
@@ -16523,6 +16582,11 @@ function isAdapterResultCssValue(result, cssProperty) {
16523
16582
  * or "resolvedStyles" for StyleX references (to be used in stylex.props arguments).
16524
16583
  */
16525
16584
  function buildResolvedHandlerResult(result, cssProperty, payload) {
16585
+ if ("extraClassNames" in result && !("expr" in result)) return {
16586
+ type: "resolvedClassNames",
16587
+ extraClassNames: result.extraClassNames,
16588
+ ...payload
16589
+ };
16526
16590
  if (!("expr" in result)) return {
16527
16591
  type: "runtimeCallOnly",
16528
16592
  ...payload
@@ -16537,6 +16601,7 @@ function buildResolvedHandlerResult(result, cssProperty, payload) {
16537
16601
  expr: result.expr,
16538
16602
  imports: result.imports,
16539
16603
  ...result.cssText ? { cssText: result.cssText } : {},
16604
+ ...result.extraClassNames ? { extraClassNames: result.extraClassNames } : {},
16540
16605
  ...payload
16541
16606
  };
16542
16607
  }
@@ -16560,20 +16625,22 @@ function getArrowFnThemeParamInfo(fn) {
16560
16625
  propsName: p.name
16561
16626
  };
16562
16627
  if (p?.type !== "ObjectPattern" || !Array.isArray(p.properties)) return null;
16628
+ let themeName = null;
16629
+ const siblingBindings = [];
16563
16630
  for (const prop of p.properties) {
16564
16631
  if (!prop || prop.type !== "Property" && prop.type !== "ObjectProperty") continue;
16565
16632
  const key = prop.key;
16566
- if (!key || key.type !== "Identifier" || key.name !== "theme") continue;
16633
+ if (!key || key.type !== "Identifier") continue;
16567
16634
  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
- };
16635
+ const binding = extractBindingName(value) ?? key.name;
16636
+ if (key.name === "theme") themeName = binding;
16637
+ else siblingBindings.push(binding);
16576
16638
  }
16639
+ if (themeName) return {
16640
+ kind: "themeBinding",
16641
+ themeName,
16642
+ siblingBindings
16643
+ };
16577
16644
  return null;
16578
16645
  }
16579
16646
  /**
@@ -16782,6 +16849,17 @@ function callArgFromNode(node, propsParamName, themeBindingName) {
16782
16849
  }
16783
16850
  return { kind: "unknown" };
16784
16851
  }
16852
+ /**
16853
+ * Extracts the actual binding name from a destructured property value.
16854
+ * Handles: `{ x }` → "x", `{ x: alias }` → "alias", `{ x: alias = def }` → "alias"
16855
+ */
16856
+ function extractBindingName(value) {
16857
+ if (!value || typeof value !== "object") return null;
16858
+ const v = value;
16859
+ if (v.type === "Identifier" && typeof v.name === "string") return v.name;
16860
+ if (v.type === "AssignmentPattern" && v.left?.type === "Identifier" && typeof v.left.name === "string") return v.left.name;
16861
+ return null;
16862
+ }
16785
16863
  function callArgsFromNode(args, propsParamName, themeBindingName) {
16786
16864
  if (!Array.isArray(args)) return [];
16787
16865
  return args.map((arg) => callArgFromNode(arg, propsParamName, themeBindingName));
@@ -16807,7 +16885,7 @@ function tryResolveConditionalValue(node, ctx) {
16807
16885
  if (!body || body.type !== "ConditionalExpression") return null;
16808
16886
  const checkThemeBooleanTest = (test) => {
16809
16887
  const check = (node) => {
16810
- if (!node || typeof node !== "object" || node.type !== "MemberExpression") return null;
16888
+ if (!node || typeof node !== "object" || !isMemberExpression(node)) return null;
16811
16889
  if (info?.kind === "propsParam" && paramName) {
16812
16890
  const parts = getMemberPathFromIdentifier(node, paramName);
16813
16891
  const themeProp = parts?.[1];
@@ -16836,7 +16914,7 @@ function tryResolveConditionalValue(node, ctx) {
16836
16914
  return null;
16837
16915
  };
16838
16916
  const resolveThemeFromMemberExpr = (node) => {
16839
- if (!node || typeof node !== "object" || node.type !== "MemberExpression") return null;
16917
+ if (!node || typeof node !== "object" || !isMemberExpression(node)) return null;
16840
16918
  if (info?.kind === "propsParam" && paramName) {
16841
16919
  const parts = getMemberPathFromIdentifier(node, paramName);
16842
16920
  if (!parts || parts[0] !== "theme") return null;
@@ -16852,7 +16930,7 @@ function tryResolveConditionalValue(node, ctx) {
16852
16930
  const resolveThemeBranchValue = (branch) => {
16853
16931
  const themeInfo = resolveThemeFromMemberExpr(branch);
16854
16932
  if (!themeInfo) return null;
16855
- const res = ctx.resolveValue({
16933
+ const res = (ctx.resolveValueOptional ?? ctx.resolveValue)({
16856
16934
  kind: "theme",
16857
16935
  path: themeInfo.path,
16858
16936
  filePath: ctx.filePath,
@@ -16960,7 +17038,7 @@ function tryResolveConditionalValue(node, ctx) {
16960
17038
  const res = resolveImportedHelperCall(call, ctx, propsParamName, cssProperty, themeBindingName);
16961
17039
  if (res.kind === "resolved") {
16962
17040
  if ("expr" in res.result) return res.result;
16963
- if (res.result.preserveRuntimeCall) {
17041
+ if ("preserveRuntimeCall" in res.result && res.result.preserveRuntimeCall) {
16964
17042
  runtimeCallState.info = {
16965
17043
  resolveCallContext: res.resolveCallContext,
16966
17044
  resolveCallResult: res.resolveCallResult
@@ -16975,7 +17053,7 @@ function tryResolveConditionalValue(node, ctx) {
16975
17053
  const innerRes = resolveImportedHelperCall(inner, ctx, propsParamName, cssProperty, themeBindingName);
16976
17054
  if (innerRes.kind === "resolved") {
16977
17055
  if ("expr" in innerRes.result) return innerRes.result;
16978
- if (innerRes.result.preserveRuntimeCall) {
17056
+ if ("preserveRuntimeCall" in innerRes.result && innerRes.result.preserveRuntimeCall) {
16979
17057
  runtimeCallState.info = {
16980
17058
  resolveCallContext: innerRes.resolveCallContext,
16981
17059
  resolveCallResult: innerRes.resolveCallResult
@@ -17736,10 +17814,9 @@ function tryBuildThemeBooleanInlineStyleFallback(args) {
17736
17814
  const { trueValue, falseValue, trueImports, falseImports, trueBranch, falseBranch, themeBoolInfo, cssProp, paramName, info } = args;
17737
17815
  if (trueValue === null === (falseValue === null)) return null;
17738
17816
  const resolvedBranchIsTrue = trueValue !== null;
17739
- const unresolvableBranch = resolvedBranchIsTrue ? falseBranch : trueBranch;
17740
- if (!hasCallExpression(unresolvableBranch)) return null;
17741
- const transformed = replaceThemeRefsWithHookVar(unresolvableBranch, paramName, info);
17817
+ const transformed = replaceThemeRefsWithHookVar(resolvedBranchIsTrue ? falseBranch : trueBranch, paramName, info);
17742
17818
  if (!transformed) return null;
17819
+ if (!isFullyTransformedThemeExpr(transformed, paramName, info)) return null;
17743
17820
  return {
17744
17821
  type: "splitThemeBooleanWithInlineStyleFallback",
17745
17822
  cssProp,
@@ -17751,17 +17828,49 @@ function tryBuildThemeBooleanInlineStyleFallback(args) {
17751
17828
  inlineExpr: transformed
17752
17829
  };
17753
17830
  }
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;
17831
+ /**
17832
+ * Validates that a transformed expression has no dangling references to
17833
+ * the original arrow function parameter or theme binding name.
17834
+ * After `replaceThemeRefsWithHookVar`, all `<paramName>.theme.*` should
17835
+ * have been rewritten to `theme.*`. If the param name still appears,
17836
+ * the expression accesses non-theme props and can't be safely used
17837
+ * with `useTheme()` alone.
17838
+ */
17839
+ function isFullyTransformedThemeExpr(transformed, paramName, info) {
17840
+ const ids = /* @__PURE__ */ new Set();
17841
+ collectFreeIdentifiers(transformed, ids);
17842
+ if (paramName && ids.has(paramName)) return false;
17843
+ if (info?.kind === "themeBinding") {
17844
+ if (info.themeName !== "theme" && ids.has(info.themeName)) return false;
17845
+ if (info.siblingBindings.some((b) => ids.has(b))) return false;
17846
+ }
17847
+ return true;
17848
+ }
17849
+ /**
17850
+ * Collects free variable identifiers from an AST node, excluding
17851
+ * non-computed property names in member expressions (e.g., in
17852
+ * `theme.color.bgSub`, only `theme` is a free variable; `color`
17853
+ * and `bgSub` are property accesses, not variable references).
17854
+ */
17855
+ function collectFreeIdentifiers(node, out) {
17856
+ if (!node || typeof node !== "object") return;
17857
+ if (Array.isArray(node)) {
17858
+ for (const child of node) collectFreeIdentifiers(child, out);
17859
+ return;
17860
+ }
17861
+ const typed = node;
17862
+ const nodeType = typed.type;
17863
+ if (nodeType === "MemberExpression" || nodeType === "OptionalMemberExpression") {
17864
+ collectFreeIdentifiers(typed.object, out);
17865
+ if (typed.computed) collectFreeIdentifiers(typed.property, out);
17866
+ return;
17867
+ }
17868
+ if (nodeType === "Identifier" && typeof typed.name === "string") out.add(typed.name);
17869
+ for (const key of Object.keys(typed)) {
17870
+ if (key === "loc" || key === "comments" || key === "type") continue;
17871
+ const child = typed[key];
17872
+ if (child && typeof child === "object") collectFreeIdentifiers(child, out);
17763
17873
  }
17764
- return false;
17765
17874
  }
17766
17875
  /**
17767
17876
  * Deep-clones an expression and replaces `<paramName>.theme.*` or `theme.*`
@@ -20725,7 +20834,7 @@ const createValuePatternHandlers = (ctx) => {
20725
20834
  * Core concepts: per-component style buckets, helper factories, and resolver wiring.
20726
20835
  */
20727
20836
  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;
20837
+ 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
20838
  const styleObj = {};
20730
20839
  const perPropPseudo = {};
20731
20840
  const perPropMedia = {};
@@ -20839,6 +20948,7 @@ function createDeclProcessingState(state, decl) {
20839
20948
  api,
20840
20949
  filePath,
20841
20950
  resolveValue,
20951
+ resolveValueOptional,
20842
20952
  resolveValueDirectional,
20843
20953
  resolveCall,
20844
20954
  resolveCallOptional,
@@ -22350,6 +22460,15 @@ function handleInterpolatedDeclaration(args) {
22350
22460
  if (!imports) return;
22351
22461
  for (const imp of imports) resolverImports.set(JSON.stringify(imp), imp);
22352
22462
  };
22463
+ /** Parse and store extra className expressions (from CSS modules) on the decl. */
22464
+ const collectExtraClassNames = (entries) => {
22465
+ decl.extraClassNames ??= [];
22466
+ for (const cn of entries) {
22467
+ addResolverImports(cn.imports);
22468
+ const cnExpr = parseExpr(cn.expr);
22469
+ if (cnExpr) decl.extraClassNames.push({ expr: cnExpr });
22470
+ }
22471
+ };
22353
22472
  /**
22354
22473
  * Try to convert an identity prop with a finite string union type into static variant
22355
22474
  * buckets. Returns true if the optimization applied and the caller should `continue`.
@@ -22371,7 +22490,7 @@ function handleInterpolatedDeclaration(args) {
22371
22490
  };
22372
22491
  const maybeEmitPreservedRuntimeCallOverride = (args) => {
22373
22492
  const { resolveCallResult, originalExpr, loc } = args;
22374
- if (!resolveCallResult?.preserveRuntimeCall) return "not-requested";
22493
+ if (!resolveCallResult || !("preserveRuntimeCall" in resolveCallResult) || !resolveCallResult.preserveRuntimeCall) return "not-requested";
22375
22494
  if (!d.property) {
22376
22495
  warnings.push({
22377
22496
  severity: "error",
@@ -22861,10 +22980,16 @@ function handleInterpolatedDeclaration(args) {
22861
22980
  expr: exprAst,
22862
22981
  afterBase: hasStaticPropsBefore
22863
22982
  });
22983
+ if (res.extraClassNames) collectExtraClassNames(res.extraClassNames);
22864
22984
  notifyResolvedStylesArg();
22865
22985
  decl.needsWrapperComponent = true;
22866
22986
  continue;
22867
22987
  }
22988
+ if (res && res.type === "resolvedClassNames") {
22989
+ collectExtraClassNames(res.extraClassNames);
22990
+ decl.needsWrapperComponent = true;
22991
+ continue;
22992
+ }
22868
22993
  if (res && res.type === "resolvedDirectional") {
22869
22994
  let directionalFailed = false;
22870
22995
  for (const entry of res.directional) {
@@ -22973,6 +23098,14 @@ function handleInterpolatedDeclaration(args) {
22973
23098
  continue;
22974
23099
  }
22975
23100
  if (res && res.type === "splitThemeBooleanWithInlineStyleFallback") {
23101
+ if (pseudos?.length || media || pseudoElement) {
23102
+ bail = true;
23103
+ continue;
23104
+ }
23105
+ if (isCssShorthandProperty(res.cssProp)) {
23106
+ bail = true;
23107
+ continue;
23108
+ }
22976
23109
  addResolverImports(res.resolvedImports);
22977
23110
  if (!decl.needsUseThemeHook) decl.needsUseThemeHook = [];
22978
23111
  if (!decl.needsUseThemeHook.some((e) => e.themeProp === res.themeProp)) decl.needsUseThemeHook.push({
@@ -27514,15 +27647,22 @@ function rewriteJsxStep(ctx) {
27514
27647
  styleAttr = null;
27515
27648
  }
27516
27649
  if (promotedMerge) styleAttr = null;
27517
- const needsMerge = classNameAttr !== null || styleAttr !== null;
27650
+ const extraClassNameExpr = decl.extraClassNames && decl.extraClassNames.length > 0 ? buildExtraClassNameExpr(j, decl.extraClassNames) : void 0;
27518
27651
  const isIntrinsicTag = /^[a-z]/.test(finalTag) && !finalTag.includes(".");
27519
- const stylexAttr = ctx.adapter.useSxProp && !needsMerge && isIntrinsicTag ? (() => {
27652
+ let effectiveClassNameAttr = classNameAttr;
27653
+ if (extraClassNameExpr && !ctx.adapter.useSxProp) effectiveClassNameAttr = j.jsxAttribute(j.jsxIdentifier("className"), j.jsxExpressionContainer(extraClassNameExpr));
27654
+ const needsMerge = effectiveClassNameAttr !== null || styleAttr !== null;
27655
+ const useSxProp = ctx.adapter.useSxProp && !needsMerge && isIntrinsicTag;
27656
+ const stylexAttr = useSxProp ? (() => {
27520
27657
  const sxExpr = styleArgs.length === 1 && styleArgs[0] ? styleArgs[0] : j.arrayExpression([...styleArgs]);
27521
27658
  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]));
27659
+ })() : j.jsxSpreadAttribute(needsMerge ? buildInlineMergeCall(j, styleArgs, effectiveClassNameAttr, styleAttr, ctx.adapter.styleMerger?.functionName) : j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("props")), [...styleArgs]));
27660
+ const extraClassNameAttrs = [];
27661
+ if (extraClassNameExpr && useSxProp) extraClassNameAttrs.push(j.jsxAttribute(j.jsxIdentifier("className"), j.jsxExpressionContainer(extraClassNameExpr)));
27523
27662
  const finalRest = [
27524
27663
  ...keptRestAfterVariants.slice(0, finalInsertIndex),
27525
27664
  stylexAttr,
27665
+ ...extraClassNameAttrs,
27526
27666
  ...keptRestAfterVariants.slice(finalInsertIndex)
27527
27667
  ];
27528
27668
  opening.attributes = [
@@ -27575,6 +27715,25 @@ function extractJsxAttrValueExpr(j, attr) {
27575
27715
  if (a.value.type === "JSXExpressionContainer") return a.value.expression;
27576
27716
  }
27577
27717
  /**
27718
+ * Builds a single expression from extra className entries (CSS module classes).
27719
+ * Single entry: returns the expression directly.
27720
+ * Multiple entries: joins with a template literal `${a} ${b}`.
27721
+ */
27722
+ function buildExtraClassNameExpr(j, extraClassNames) {
27723
+ const exprs = extraClassNames.map((cn) => cn.expr);
27724
+ if (exprs.length === 1 && exprs[0]) return exprs[0];
27725
+ const qs = [];
27726
+ for (let i = 0; i <= exprs.length; i++) {
27727
+ const isLast = i === exprs.length;
27728
+ const raw = i === 0 || isLast ? "" : " ";
27729
+ qs.push(j.templateElement({
27730
+ raw,
27731
+ cooked: raw
27732
+ }, isLast));
27733
+ }
27734
+ return j.templateLiteral(qs, exprs);
27735
+ }
27736
+ /**
27578
27737
  * Finds the combined style key matching the consumed props at a JSX call site.
27579
27738
  * Returns the style key if a matching combination exists, or undefined otherwise.
27580
27739
  */
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.31",
4
4
  "description": "Codemod to transform styled-components to StyleX",
5
5
  "keywords": [
6
6
  "codemod",