styled-components-to-stylex-codemod 0.0.33 → 0.0.34

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-C-Mherh5.mjs";
1
+ import { a as ImportSource, i as AdapterInput, o as MarkerFileContext, s as defineAdapter, t as CollectedWarning } from "./logger-xD1SimCA.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
@@ -107,4 +107,4 @@ interface RunTransformResult {
107
107
  */
108
108
  declare function runTransform(options: RunTransformOptions): Promise<RunTransformResult>;
109
109
  //#endregion
110
- export { type AdapterInput, defineAdapter, runTransform };
110
+ export { type AdapterInput, type ImportSource, type MarkerFileContext, defineAdapter, runTransform };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as assertValidAdapterInput, n as defineAdapter, o as describeValue } from "./adapter-Cu-LRuPc.mjs";
1
+ import { o as assertValidAdapterInput, r as defineAdapter, s as describeValue, t as mergeMarkerDeclarations } from "./merge-markers-B58_cRdA.mjs";
2
2
  import { t as Logger } from "./logger-C2O81VeU.mjs";
3
3
  import { run } from "jscodeshift/src/Runner.js";
4
4
  import { fileURLToPath } from "node:url";
@@ -204,7 +204,8 @@ async function runTransform(options) {
204
204
  resolveCall: resolveCallWithLogging,
205
205
  resolveSelector: resolveSelectorWithLogging,
206
206
  resolveBaseComponent: adapterInput.resolveBaseComponent ? resolveBaseComponentWithLogging : void 0,
207
- resolveThemeCall: resolvedAdapter.resolveThemeCall
207
+ resolveThemeCall: resolvedAdapter.resolveThemeCall,
208
+ markerFile: resolvedAdapter.markerFile
208
209
  };
209
210
  const transformPath = (() => {
210
211
  const adjacent = join(__dirname, "transform.mjs");
@@ -307,10 +308,6 @@ function createAutoPrepassFailureError(err, consumerPatterns, parser) {
307
308
  /**
308
309
  * Merge new sidecar marker content into an existing .stylex.ts file, preserving
309
310
  * user-owned exports (e.g. defineVars). If the file doesn't exist, returns content as-is.
310
- *
311
- * New marker declarations (`export const XMarker = stylex.defineMarker()`) are
312
- * appended only if they don't already exist in the file. The stylex import is
313
- * ensured at the top.
314
311
  */
315
312
  function mergeSidecarContent(sidecarPath, newContent) {
316
313
  let existing;
@@ -319,17 +316,7 @@ function mergeSidecarContent(sidecarPath, newContent) {
319
316
  } catch {
320
317
  return newContent;
321
318
  }
322
- const markerLineRe = /^export const \w+ = stylex\.defineMarker\(\);$/gm;
323
- const newMarkers = [];
324
- for (const m of newContent.matchAll(markerLineRe)) newMarkers.push(m[0]);
325
- if (newMarkers.length === 0) return newContent;
326
- const markersToAdd = newMarkers.filter((line) => !existing.includes(line));
327
- if (markersToAdd.length === 0) return existing;
328
- let merged = existing;
329
- if (!merged.includes("@stylexjs/stylex")) merged = `import * as stylex from "@stylexjs/stylex";\n\n${merged}`;
330
- const trailingNewline = merged.endsWith("\n") ? "" : "\n";
331
- merged = merged + trailingNewline + markersToAdd.join("\n") + "\n";
332
- return merged;
319
+ return mergeMarkerDeclarations(existing, newContent);
333
320
  }
334
321
  /** Run formatter commands on a list of files, logging warnings on failure. */
335
322
  async function runFormatters(commands, files) {
@@ -500,6 +500,10 @@ type ExternalInterfaceResult = {
500
500
  elementProps?: boolean; /** Whether cross-file consumers use JSX spread ({...props}) */
501
501
  spreadProps?: boolean;
502
502
  };
503
+ interface MarkerFileContext {
504
+ /** Absolute path of the file being transformed */
505
+ filePath: string;
506
+ }
503
507
  /**
504
508
  * Configuration for a custom style merger function that combines stylex.props()
505
509
  * results with external className/style props.
@@ -659,6 +663,22 @@ interface Adapter {
659
663
  * @default false
660
664
  */
661
665
  usePhysicalProperties?: boolean;
666
+ /**
667
+ * Optional function to customize where marker sidecar files (`stylex.defineMarker()`)
668
+ * are written. By default, markers are placed in a `.stylex.ts` file next to the source.
669
+ *
670
+ * When provided, the function receives the source file path and returns an `ImportSource`
671
+ * that determines both the import path in the transformed file and the file path where
672
+ * markers are written.
673
+ *
674
+ * Example:
675
+ * ```typescript
676
+ * markerFile(ctx) {
677
+ * return { kind: "absolutePath", value: "/path/to/shared/markers.stylex.ts" };
678
+ * }
679
+ * ```
680
+ */
681
+ markerFile?: (context: MarkerFileContext) => ImportSource;
662
682
  }
663
683
  /**
664
684
  * User-facing adapter input type accepted by `defineAdapter()`.
@@ -686,6 +706,7 @@ interface AdapterInput {
686
706
  themeHook?: Adapter["themeHook"];
687
707
  useSxProp: Adapter["useSxProp"];
688
708
  usePhysicalProperties?: Adapter["usePhysicalProperties"];
709
+ markerFile?: Adapter["markerFile"];
689
710
  }
690
711
  /**
691
712
  * Helper for nicer user authoring + type inference.
@@ -777,4 +798,4 @@ interface CollectedWarning extends WarningLog {
777
798
  filePath: string;
778
799
  }
779
800
  //#endregion
780
- export { defineAdapter as a, AdapterInput as i, WarningLog as n, Adapter as r, CollectedWarning as t };
801
+ export { ImportSource as a, AdapterInput as i, WarningLog as n, MarkerFileContext as o, Adapter as r, defineAdapter as s, CollectedWarning as t };
@@ -133,6 +133,14 @@ function assertAdapterShape(candidate, where, allowAutoExtIf) {
133
133
  absolutePathExample: "/path/to/module.ts"
134
134
  });
135
135
  }
136
+ const markerFile = obj?.markerFile;
137
+ if (markerFile !== void 0 && markerFile !== null && typeof markerFile !== "function") throw new Error([
138
+ `${where}: adapter.markerFile must be a function when provided.`,
139
+ `Received: markerFile=${describeValue(markerFile)}`,
140
+ "",
141
+ "Expected signature:",
142
+ " markerFile(ctx: { filePath: string }) => { kind: \"specifier\" | \"absolutePath\", value: string }"
143
+ ].join("\n"));
136
144
  const themeHook = obj?.themeHook;
137
145
  if (themeHook !== null && themeHook !== void 0) {
138
146
  if (typeof themeHook !== "object") throw new Error([
@@ -269,4 +277,33 @@ function defineAdapter(adapter) {
269
277
  return adapter;
270
278
  }
271
279
  //#endregion
272
- export { assertValidAdapterInput as a, assertValidAdapter as i, defineAdapter as n, describeValue as o, isDirectionalResult as r, DEFAULT_THEME_HOOK as t };
280
+ //#region src/internal/merge-markers.ts
281
+ /**
282
+ * Shared utility for merging marker sidecar content.
283
+ * Core concepts: deduplication of defineMarker declarations across files.
284
+ */
285
+ /** Regex matching a marker block: optional JSDoc comment followed by the export line. */
286
+ const MARKER_BLOCK_RE = /(?:\/\*\*[^]*?\*\/\n)?export const \w+ = stylex\.defineMarker\(\);/gm;
287
+ /** Regex matching just the export line (used for dedup checks). */
288
+ const MARKER_EXPORT_RE = /^export const \w+ = stylex\.defineMarker\(\);$/gm;
289
+ /**
290
+ * Merge marker declarations from `incoming` into `base`, appending only new
291
+ * marker blocks (JSDoc + export). Returns `base` unchanged if all markers already exist.
292
+ */
293
+ function mergeMarkerDeclarations(base, incoming) {
294
+ const incomingExports = [...incoming.matchAll(MARKER_EXPORT_RE)].map((m) => m[0]);
295
+ if (incomingExports.length === 0) return base;
296
+ const newExportLines = incomingExports.filter((line) => !base.includes(line));
297
+ if (newExportLines.length === 0) return base;
298
+ const newExportSet = new Set(newExportLines);
299
+ const blocksToAdd = [...incoming.matchAll(MARKER_BLOCK_RE)].map((m) => m[0]).filter((block) => {
300
+ const exportLine = block.match(MARKER_EXPORT_RE);
301
+ return exportLine && newExportSet.has(exportLine[0]);
302
+ });
303
+ if (blocksToAdd.length === 0) return base;
304
+ let merged = base;
305
+ if (!merged.includes("@stylexjs/stylex")) merged = `import * as stylex from "@stylexjs/stylex";\n\n${merged}`;
306
+ return merged.trimEnd() + "\n\n" + blocksToAdd.join("\n\n") + "\n";
307
+ }
308
+ //#endregion
309
+ export { assertValidAdapter as a, isDirectionalResult as i, DEFAULT_THEME_HOOK as n, assertValidAdapterInput as o, defineAdapter as r, describeValue as s, mergeMarkerDeclarations as t };
@@ -1,4 +1,4 @@
1
- import { n as WarningLog, r as Adapter } from "./logger-C-Mherh5.mjs";
1
+ import { n as WarningLog, r as Adapter } from "./logger-xD1SimCA.mjs";
2
2
  import { API, FileInfo, Options } from "jscodeshift";
3
3
 
4
4
  //#region src/internal/transform-types.d.ts
@@ -10,6 +10,8 @@ interface TransformResult {
10
10
  warnings: WarningLog[];
11
11
  /** Content for the sidecar .stylex.ts file (defineMarker declarations). Undefined when no markers needed. */
12
12
  sidecarContent?: string;
13
+ /** Absolute file path for the sidecar file, when adapter.markerFile provides a custom location. */
14
+ sidecarFilePath?: string;
13
15
  /** Bridge components emitted for unconverted consumer selectors. */
14
16
  bridgeResults?: BridgeComponentResult[];
15
17
  /** Transient prop renames for exported components, keyed by export name. */
@@ -1,9 +1,9 @@
1
- import { i as assertValidAdapter, r as isDirectionalResult, t as DEFAULT_THEME_HOOK } from "./adapter-Cu-LRuPc.mjs";
1
+ import { a as assertValidAdapter, i as isDirectionalResult, n as DEFAULT_THEME_HOOK, t as mergeMarkerDeclarations } from "./merge-markers-B58_cRdA.mjs";
2
2
  import { t as Logger } from "./logger-C2O81VeU.mjs";
3
3
  import { a as isValidIdentifierName, c as lowerFirst, i as isPrettierIgnoreComment, l as normalizeWhitespace, n as escapeRegex, o as kebabToCamelCase, r as isBackgroundImageValue, s as looksLikeLength, t as capitalize, u as sanitizeIdentifier } from "./string-utils-5EMAWj3q.mjs";
4
4
  import { n as parseStyledTemplateLiteral, t as PLACEHOLDER_RE } from "./styled-css-C3QKH6Od.mjs";
5
5
  import { t as toRealPath } from "./path-utils-BlOXGcCF.mjs";
6
- import path, { basename, dirname, isAbsolute, join, resolve } from "node:path";
6
+ import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { existsSync, readFileSync, realpathSync } from "node:fs";
8
8
  import valueParser from "postcss-value-parser";
9
9
  import { compile } from "stylis";
@@ -1412,6 +1412,8 @@ var TransformContext = class {
1412
1412
  ancestorAttrsByStyleKey;
1413
1413
  /** Content for the sidecar .stylex.ts file (defineMarker declarations), populated by emitStylesStep */
1414
1414
  sidecarStylexContent;
1415
+ /** Absolute file path for the sidecar file, when adapter.markerFile provides a custom location */
1416
+ sidecarFilePath;
1415
1417
  /** Bridge components emitted for unconverted consumer selectors. */
1416
1418
  bridgeResults;
1417
1419
  /** Transient prop renames for exported components (for consumer patching). */
@@ -4973,7 +4975,13 @@ function collectStyledDefaultImportLocalNames(styledImports) {
4973
4975
  const styledLocalNames = /* @__PURE__ */ new Set();
4974
4976
  styledImports.forEach((imp) => {
4975
4977
  const specs = imp.node.specifiers ?? [];
4976
- for (const spec of specs) if (spec.type === "ImportDefaultSpecifier" && spec.local?.type === "Identifier") styledLocalNames.add(spec.local.name);
4978
+ for (const spec of specs) {
4979
+ if (spec.type === "ImportDefaultSpecifier" && spec.local?.type === "Identifier") {
4980
+ styledLocalNames.add(spec.local.name);
4981
+ continue;
4982
+ }
4983
+ if (spec.type === "ImportSpecifier" && spec.imported?.type === "Identifier" && spec.imported.name === "styled" && spec.local?.type === "Identifier") styledLocalNames.add(spec.local.name);
4984
+ }
4977
4985
  });
4978
4986
  return styledLocalNames;
4979
4987
  }
@@ -7202,7 +7210,8 @@ function collectStyledDeclsStep(ctx) {
7202
7210
  const { styledImports, root, j, cssLocal } = ctx;
7203
7211
  if (!styledImports) return CONTINUE;
7204
7212
  const styledDefaultSpecifier = styledImports.find(j.ImportDefaultSpecifier).nodes()[0];
7205
- const styledDefaultImport = styledDefaultSpecifier?.local?.type === "Identifier" ? styledDefaultSpecifier.local.name : void 0;
7213
+ const namedStyledSpecifier = !styledDefaultSpecifier ? styledImports.find(j.ImportSpecifier).filter((p) => p.node.imported?.type === "Identifier" && p.node.imported.name === "styled").nodes()[0] : void 0;
7214
+ const styledDefaultImport = styledDefaultSpecifier?.local?.type === "Identifier" ? styledDefaultSpecifier.local.name : namedStyledSpecifier?.local?.type === "Identifier" ? namedStyledSpecifier.local.name : void 0;
7206
7215
  ctx.styledDefaultImport = styledDefaultImport;
7207
7216
  if (extractStyledCallArgs({
7208
7217
  root,
@@ -7708,18 +7717,20 @@ function tryHandleAnimation(args) {
7708
7717
  timeSlots.sort((a, b) => a.originalIndex - b.originalIndex);
7709
7718
  for (let i = tokens.length - 1; i >= 0; i--) if (INTERPOLATED_TIME_RE.test(tokens[i])) tokens.splice(i, 1);
7710
7719
  const classified = classifyAnimationTokens(tokens);
7711
- classified.duration = null;
7712
- classified.delay = null;
7720
+ let durationValue = null;
7721
+ let delayValue = null;
7713
7722
  for (let i = 0; i < timeSlots.length && i < 2; i++) {
7714
7723
  const slot = timeSlots[i];
7715
7724
  const longhand = i === 0 ? "animationDuration" : "animationDelay";
7716
- if (slot.kind === "static") if (longhand === "animationDuration") classified.duration = slot.value;
7717
- else classified.delay = slot.value;
7725
+ if (slot.kind === "static") if (longhand === "animationDuration") durationValue = slot.value;
7726
+ else delayValue = slot.value;
7718
7727
  else {
7728
+ const expr = decl.templateExpressions[slot.slotId];
7719
7729
  const fallbackValue = computeInterpolatedTimeFallback(decl, slot.slotId, slot.unit);
7720
- if (longhand === "animationDuration") classified.duration = fallbackValue ?? `0${slot.unit}`;
7721
- else classified.delay = fallbackValue ?? `0${slot.unit}`;
7722
- interpolatedAnimTimes.push({
7730
+ const timeValue = buildInterpolatedTimeExpression(j, decl, slot.slotId, slot.unit) ?? fallbackValue ?? `0${slot.unit}`;
7731
+ if (longhand === "animationDuration") durationValue = timeValue;
7732
+ else delayValue = timeValue;
7733
+ if (expr?.type === "ArrowFunctionExpression") interpolatedAnimTimes.push({
7723
7734
  slotId: slot.slotId,
7724
7735
  unit: slot.unit,
7725
7736
  longhand,
@@ -7728,8 +7739,8 @@ function tryHandleAnimation(args) {
7728
7739
  });
7729
7740
  }
7730
7741
  }
7731
- durations.push(classified.duration);
7732
- delays.push(classified.delay);
7742
+ durations.push(durationValue);
7743
+ delays.push(delayValue);
7733
7744
  timings.push(classified.timing);
7734
7745
  directions.push(classified.direction);
7735
7746
  fillModes.push(classified.fillMode);
@@ -7748,7 +7759,7 @@ function tryHandleAnimation(args) {
7748
7759
  if (animNames.length === 1 && firstAnim && firstAnim.kind === "ident") applyProp("animationName", j.identifier(firstAnim.name), null);
7749
7760
  else applyProp("animationName", buildCommaTemplate(animNames), null);
7750
7761
  const anyValues = (values) => values.some((value) => value !== null);
7751
- const joinWithDefaults = (values, fallback) => values.map((value) => value ?? fallback).join(", ");
7762
+ const joinWithDefaults = (values, fallback) => buildCommaSeparatedValues(j, values, fallback);
7752
7763
  if (anyValues(durations)) applyProp("animationDuration", joinWithDefaults(durations, "0s"), null);
7753
7764
  if (anyValues(timings)) applyProp("animationTimingFunction", joinWithDefaults(timings, "ease"), null);
7754
7765
  if (anyValues(delays)) applyProp("animationDelay", joinWithDefaults(delays, "0s"), null);
@@ -7762,6 +7773,36 @@ function tryHandleAnimation(args) {
7762
7773
  }
7763
7774
  return false;
7764
7775
  }
7776
+ function buildCommaSeparatedValues(j, values, fallback) {
7777
+ const parts = values.map((value) => value ?? fallback);
7778
+ if (parts.length === 1) {
7779
+ const first = parts[0];
7780
+ return typeof first === "string" ? first : cloneAstNode(first);
7781
+ }
7782
+ const quasis = [];
7783
+ const exprs = [];
7784
+ let text = "";
7785
+ for (let i = 0; i < parts.length; i++) {
7786
+ if (i > 0) text += ", ";
7787
+ const part = parts[i];
7788
+ if (typeof part === "string") {
7789
+ text += part;
7790
+ continue;
7791
+ }
7792
+ quasis.push(j.templateElement({
7793
+ raw: text,
7794
+ cooked: text
7795
+ }, false));
7796
+ exprs.push(cloneAstNode(part));
7797
+ text = "";
7798
+ }
7799
+ if (exprs.length === 0) return text;
7800
+ quasis.push(j.templateElement({
7801
+ raw: text,
7802
+ cooked: text
7803
+ }, true));
7804
+ return j.templateLiteral(quasis, exprs);
7805
+ }
7765
7806
  /** Matches placeholder tokens with optional time unit suffix (e.g., `__SC_EXPR_1__ms`). */
7766
7807
  const INTERPOLATED_TIME_RE = /^__SC_EXPR_(\d+)__(ms|s)$/;
7767
7808
  /** Matches any placeholder token (with or without suffix) to exclude from timeline detection. */
@@ -7780,6 +7821,13 @@ function computeInterpolatedTimeFallback(decl, slotId, unit) {
7780
7821
  }
7781
7822
  return null;
7782
7823
  }
7824
+ function buildInterpolatedTimeExpression(j, decl, slotId, unit) {
7825
+ const expr = decl.templateExpressions[slotId];
7826
+ if (!expr || expr.type === "ArrowFunctionExpression") return null;
7827
+ const staticVal = literalToStaticValue(expr);
7828
+ if (staticVal !== null) return j.stringLiteral(`${staticVal}${unit}`);
7829
+ return buildTemplateWithStaticParts(j, cloneAstNode(expr), "", unit);
7830
+ }
7783
7831
  /**
7784
7832
  * Emits dynamic style functions for interpolated animation time tokens.
7785
7833
  * For each interpolated token, creates a style function like:
@@ -7873,8 +7921,20 @@ function buildMultiAnimationCallArg(j, interpSegments, valuesList, defaultFallba
7873
7921
  }, false));
7874
7922
  exprs.push(interp.expr);
7875
7923
  prefix = interp.unit;
7876
- } else prefix += valuesList[i] ?? defaultFallback;
7924
+ } else {
7925
+ const value = valuesList[i] ?? defaultFallback;
7926
+ if (typeof value === "string") prefix += value;
7927
+ else {
7928
+ quasis.push(j.templateElement({
7929
+ raw: prefix,
7930
+ cooked: prefix
7931
+ }, false));
7932
+ exprs.push(cloneAstNode(value));
7933
+ prefix = "";
7934
+ }
7935
+ }
7877
7936
  }
7937
+ if (exprs.length === 0) return prefix;
7878
7938
  quasis.push(j.templateElement({
7879
7939
  raw: prefix,
7880
7940
  cooked: prefix
@@ -7887,10 +7947,19 @@ function convertStyledKeyframes(args) {
7887
7947
  return convertStyledKeyframesImpl(args);
7888
7948
  }
7889
7949
  function parseKeyframesTemplate(args) {
7890
- const { template } = args;
7950
+ const { template, j, scopePath } = args;
7891
7951
  if (!template || template.type !== "TemplateLiteral") return null;
7892
- if ((template.expressions?.length ?? 0) > 0) return null;
7893
- const ast = compile(`@keyframes __SC_KEYFRAMES__ { ${(template.quasis ?? []).map((q) => q.value?.raw ?? "").join("")} }`);
7952
+ const slotExprById = /* @__PURE__ */ new Map();
7953
+ for (let i = 0; i < (template.expressions?.length ?? 0); i++) {
7954
+ const expr = template.expressions[i];
7955
+ if (!expr) return null;
7956
+ if (!isStaticSafeKeyframesSlotExpression(expr, scopePath)) return null;
7957
+ slotExprById.set(i, expr);
7958
+ }
7959
+ const ast = compile(`@keyframes __SC_KEYFRAMES__ { ${(template.quasis ?? []).map((q, i) => {
7960
+ const raw = q.value?.raw ?? "";
7961
+ return i < (template.expressions?.length ?? 0) ? `${raw}__SC_EXPR_${i}__` : raw;
7962
+ }).join("")} }`);
7894
7963
  const frames = {};
7895
7964
  const visit = (node) => {
7896
7965
  if (!node) return;
@@ -7911,7 +7980,10 @@ function parseKeyframesTemplate(args) {
7911
7980
  const propRaw = typeof c.props === "string" && c.props ? c.props : typeof c.value === "string" && c.value.includes(":") ? (c.value.split(":")[0] ?? "").trim() : "";
7912
7981
  const valueRaw = typeof c.children === "string" ? c.children.trim() : typeof c.value === "string" && c.value.includes(":") ? c.value.split(":").slice(1).join(":").replace(/;$/, "").trim() : "";
7913
7982
  if (!propRaw) continue;
7914
- applyStaticDeclsToStyleObj(styleObj, propRaw.trim(), valueRaw);
7983
+ applyStaticDeclsToStyleObj(styleObj, propRaw.trim(), valueRaw, {
7984
+ j,
7985
+ slotExprById
7986
+ });
7915
7987
  }
7916
7988
  frames[frameKey] = styleObj;
7917
7989
  return;
@@ -7931,7 +8003,11 @@ function convertStyledKeyframesImpl(args) {
7931
8003
  if (p.node.id.type !== "Identifier") return;
7932
8004
  const localName = p.node.id.name;
7933
8005
  const template = init?.quasi;
7934
- const frames = parseKeyframesTemplate({ template });
8006
+ const frames = parseKeyframesTemplate({
8007
+ template,
8008
+ j,
8009
+ scopePath: p
8010
+ });
7935
8011
  if (!frames) return;
7936
8012
  p.node.init = j.callExpression(j.memberExpression(j.identifier("stylex"), j.identifier("keyframes")), [objectToAst(j, frames)]);
7937
8013
  keyframesNames.add(localName);
@@ -7996,7 +8072,7 @@ function extractInlineKeyframes(rules) {
7996
8072
  * Expands static CSS declarations into a style object via cssDeclarationToStylexDeclarations.
7997
8073
  * Handles shorthand expansion and coerces numeric strings to numbers.
7998
8074
  */
7999
- function applyStaticDeclsToStyleObj(styleObj, property, valueRaw) {
8075
+ function applyStaticDeclsToStyleObj(styleObj, property, valueRaw, options) {
8000
8076
  for (const out of cssDeclarationToStylexDeclarations({
8001
8077
  property,
8002
8078
  value: {
@@ -8007,9 +8083,91 @@ function applyStaticDeclsToStyleObj(styleObj, property, valueRaw) {
8007
8083
  valueRaw
8008
8084
  })) if (out.value.kind === "static") {
8009
8085
  const v = out.value.value.trim();
8086
+ const exprValue = resolvePlaceholderValueToAst(v, options);
8087
+ if (exprValue) {
8088
+ styleObj[out.prop] = exprValue;
8089
+ continue;
8090
+ }
8010
8091
  styleObj[out.prop] = /^-?\d*\.?\d+$/.test(v) ? Number(v) : v;
8011
8092
  }
8012
8093
  }
8094
+ function resolvePlaceholderValueToAst(value, options) {
8095
+ const j = options?.j;
8096
+ const slotExprById = options?.slotExprById;
8097
+ if (!j || !slotExprById || !/__SC_EXPR_\d+__/.test(value)) return null;
8098
+ const placeholderRe = /__SC_EXPR_(\d+)__/g;
8099
+ const quasis = [];
8100
+ const exprs = [];
8101
+ let lastIndex = 0;
8102
+ let match;
8103
+ while (match = placeholderRe.exec(value)) {
8104
+ const expr = slotExprById.get(Number(match[1]));
8105
+ if (!expr) return null;
8106
+ const prefix = value.slice(lastIndex, match.index);
8107
+ quasis.push(j.templateElement({
8108
+ raw: prefix,
8109
+ cooked: prefix
8110
+ }, false));
8111
+ exprs.push(cloneAstNode(expr));
8112
+ lastIndex = match.index + match[0].length;
8113
+ }
8114
+ if (exprs.length === 0) return null;
8115
+ const suffix = value.slice(lastIndex);
8116
+ quasis.push(j.templateElement({
8117
+ raw: suffix,
8118
+ cooked: suffix
8119
+ }, true));
8120
+ if (quasis.length === 2 && quasis[0].value.raw === "" && quasis[1].value.raw === "") return exprs[0];
8121
+ return j.templateLiteral(quasis, exprs);
8122
+ }
8123
+ function isStaticSafeKeyframesSlotExpression(expr, scopePath, seenIdentifiers = /* @__PURE__ */ new Set()) {
8124
+ if (isFunctionLikeExpression(expr)) return false;
8125
+ const staticValue = literalToStaticValue(expr);
8126
+ if (staticValue !== null) return typeof staticValue === "string" || typeof staticValue === "number";
8127
+ if (expr.type === "Identifier") return isStaticSafeIdentifierBinding(expr.name, scopePath, seenIdentifiers);
8128
+ if (expr.type === "UnaryExpression") return isStaticSafeKeyframesSlotExpression(expr.argument, scopePath, seenIdentifiers);
8129
+ if (expr.type === "BinaryExpression" || expr.type === "LogicalExpression") return isStaticSafeKeyframesSlotExpression(expr.left, scopePath, seenIdentifiers) && isStaticSafeKeyframesSlotExpression(expr.right, scopePath, seenIdentifiers);
8130
+ if (expr.type === "ConditionalExpression") return isStaticSafeKeyframesSlotExpression(expr.test, scopePath, seenIdentifiers) && isStaticSafeKeyframesSlotExpression(expr.consequent, scopePath, seenIdentifiers) && isStaticSafeKeyframesSlotExpression(expr.alternate, scopePath, seenIdentifiers);
8131
+ if (expr.type === "TemplateLiteral") return expr.expressions.every((slotExpr) => isStaticSafeKeyframesSlotExpression(slotExpr, scopePath, seenIdentifiers));
8132
+ if (expr.type === "ParenthesizedExpression") return isStaticSafeKeyframesSlotExpression(expr.expression, scopePath, seenIdentifiers);
8133
+ if (expr.type === "TSAsExpression" || expr.type === "TSTypeAssertion" || expr.type === "TSSatisfiesExpression") return isStaticSafeKeyframesSlotExpression(expr.expression, scopePath, seenIdentifiers);
8134
+ return false;
8135
+ }
8136
+ function isFunctionLikeExpression(expr) {
8137
+ return expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression";
8138
+ }
8139
+ function isStaticSafeIdentifierBinding(name, scopePath, seenIdentifiers) {
8140
+ if (seenIdentifiers.has(name)) return false;
8141
+ seenIdentifiers.add(name);
8142
+ const scope = scopePath.scope?.lookup?.(name);
8143
+ if (!scope || typeof scope.getBindings !== "function") {
8144
+ seenIdentifiers.delete(name);
8145
+ return false;
8146
+ }
8147
+ const refs = scope.getBindings()?.[name];
8148
+ if (!Array.isArray(refs) || refs.length !== 1) {
8149
+ seenIdentifiers.delete(name);
8150
+ return false;
8151
+ }
8152
+ const idPath = refs[0];
8153
+ const declarator = idPath?.parent?.value;
8154
+ if (!declarator || declarator.type !== "VariableDeclarator" || declarator.id !== idPath.value) {
8155
+ seenIdentifiers.delete(name);
8156
+ return false;
8157
+ }
8158
+ const declaration = idPath?.parent?.parent?.value;
8159
+ if (!declaration || declaration.type !== "VariableDeclaration" || declaration.kind !== "const") {
8160
+ seenIdentifiers.delete(name);
8161
+ return false;
8162
+ }
8163
+ if (!declarator.init) {
8164
+ seenIdentifiers.delete(name);
8165
+ return false;
8166
+ }
8167
+ const isStatic = isStaticSafeKeyframesSlotExpression(declarator.init, scopePath, seenIdentifiers);
8168
+ seenIdentifiers.delete(name);
8169
+ return isStatic;
8170
+ }
8013
8171
  /**
8014
8172
  * Expands a static `animation` shorthand value into longhand properties,
8015
8173
  * replacing the animation name with a keyframes identifier when it matches
@@ -10031,14 +10189,37 @@ function emitStylesStep(ctx) {
10031
10189
  function emitDefineMarkerDeclarations(ctx, crossFileMarkers) {
10032
10190
  const j = ctx.j;
10033
10191
  const markerNames = [...crossFileMarkers.values()];
10034
- ctx.sidecarStylexContent = `import * as stylex from "@stylexjs/stylex";\n\n${markerNames.map((name) => `export const ${name} = stylex.defineMarker();`).join("\n")}\n`;
10035
- const sidecarImportPath = `./${basename(ctx.file.path).replace(/\.\w+$/, "")}.stylex`;
10192
+ ctx.sidecarStylexContent = `import * as stylex from "@stylexjs/stylex";\n\n${markerNames.map((name) => {
10193
+ return `/** Custom marker for ${name.replace(/Marker$/, "")} */\nexport const ${name} = stylex.defineMarker();`;
10194
+ }).join("\n\n")}\n`;
10195
+ let sidecarImportPath;
10196
+ const adapterMarkerFile = ctx.adapter.markerFile;
10197
+ if (adapterMarkerFile) {
10198
+ const importSource = adapterMarkerFile({ filePath: ctx.file.path });
10199
+ sidecarImportPath = importSourceToModuleSpecifier(importSource, ctx.file.path);
10200
+ ctx.sidecarFilePath = importSourceToAbsolutePath(importSource, ctx.file.path);
10201
+ } else sidecarImportPath = `./${basename(ctx.file.path).replace(/\.\w+$/, "")}.stylex`;
10036
10202
  const importDecl = j.importDeclaration(markerNames.map((name) => j.importSpecifier(j.identifier(name))), j.literal(sidecarImportPath));
10037
10203
  const programBody = ctx.root.get().node.program.body;
10038
10204
  const lastImportIdx = programBody.reduce((last, node, i) => node?.type === "ImportDeclaration" ? i : last, -1);
10039
10205
  const insertAt = lastImportIdx >= 0 ? lastImportIdx + 1 : 0;
10040
10206
  programBody.splice(insertAt, 0, importDecl);
10041
10207
  }
10208
+ /** Convert an ImportSource to a module specifier string for use in import declarations. */
10209
+ function importSourceToModuleSpecifier(source, filePath) {
10210
+ if (source.kind === "specifier") return source.value;
10211
+ let rel = relative(dirname(filePath), source.value).split(sep).join("/");
10212
+ rel = rel.replace(/\.tsx?$/, "");
10213
+ if (!rel.startsWith(".")) rel = `./${rel}`;
10214
+ return rel;
10215
+ }
10216
+ /** Resolve an ImportSource to an absolute file path for writing the sidecar file. */
10217
+ function importSourceToAbsolutePath(source, filePath) {
10218
+ if (source.kind === "absolutePath") return source.value;
10219
+ let resolved = join(dirname(filePath), source.value);
10220
+ if (!/\.[jt]sx?$/.test(resolved)) resolved += ".ts";
10221
+ return resolved;
10222
+ }
10042
10223
  //#endregion
10043
10224
  //#region src/internal/transform-steps/emit-bridge-exports.ts
10044
10225
  /**
@@ -14960,6 +15141,7 @@ function finalize(ctx) {
14960
15141
  code,
14961
15142
  warnings: ctx.warnings,
14962
15143
  sidecarContent: ctx.sidecarStylexContent,
15144
+ sidecarFilePath: ctx.sidecarFilePath,
14963
15145
  bridgeResults: ctx.bridgeResults,
14964
15146
  transientPropRenames: ctx.transientPropRenames
14965
15147
  };
@@ -15940,6 +16122,15 @@ function createCssHelperResolver(args) {
15940
16122
  }
15941
16123
  return null;
15942
16124
  };
16125
+ /**
16126
+ * Extracts the theme path from a ternary test that accesses `props.theme.*`.
16127
+ * e.g., `props.theme.isDark` → "isDark", `props.theme.mode` → "mode"
16128
+ */
16129
+ const extractThemePathFromCondTest = (test, paramName) => {
16130
+ if (!test || !paramName) return null;
16131
+ const path = getMemberPathFromIdentifier(test, paramName);
16132
+ return path && path[0] === "theme" && path.length > 1 ? path.slice(1).join(".") : null;
16133
+ };
15943
16134
  const resolveCssHelperTemplate = (template, paramName, loc) => {
15944
16135
  const bail = (type, context, exprLoc) => {
15945
16136
  warnings.push({
@@ -16078,6 +16269,38 @@ function createCssHelperResolver(args) {
16078
16269
  return bail("Conditional `css` block: failed to parse expression", { property: d.property }, exprLoc);
16079
16270
  }
16080
16271
  const resolved = resolveHelperExprToAst(expr, paramName);
16272
+ if (!resolved && expr.type === "ConditionalExpression") {
16273
+ const ternaryExpr = expr;
16274
+ const themePath = extractThemePathFromCondTest(ternaryExpr.test, paramName);
16275
+ if (themePath) {
16276
+ const consResolved = resolveTernaryBranchToAst(ternaryExpr.consequent);
16277
+ const altResolved = resolveTernaryBranchToAst(ternaryExpr.alternate);
16278
+ if (consResolved && altResolved) {
16279
+ const buildVariantStyle = (branchResolved) => {
16280
+ const variantStyle = {};
16281
+ for (const mapped of cssDeclarationToStylexDeclarations(d)) if (hasStaticParts) {
16282
+ const { prefix, suffix } = extractPrefixSuffix(parts);
16283
+ const ast = parseExpr(wrapExprWithStaticParts(branchResolved.exprString, prefix, suffix));
16284
+ if (ast) variantStyle[mapped.prop] = mergeIntoContext(ast, mapped.prop, target);
16285
+ } else variantStyle[mapped.prop] = mergeIntoContext(branchResolved.ast, mapped.prop, target);
16286
+ return variantStyle;
16287
+ };
16288
+ const consStyle = buildVariantStyle(consResolved);
16289
+ const altStyle = buildVariantStyle(altResolved);
16290
+ conditionalVariants.push({
16291
+ when: `theme.${themePath}`,
16292
+ propName: "",
16293
+ style: consStyle
16294
+ });
16295
+ conditionalVariants.push({
16296
+ when: `!theme.${themePath}`,
16297
+ propName: "",
16298
+ style: altStyle
16299
+ });
16300
+ continue;
16301
+ }
16302
+ }
16303
+ }
16081
16304
  if (!resolved && hasThemeAccessInExpr(expr, paramName)) return bail("Conditional `css` block: failed to parse expression", { property: d.property }, exprLoc);
16082
16305
  if (resolved) if (hasStaticParts) {
16083
16306
  const { prefix, suffix } = extractPrefixSuffix(parts);
@@ -19767,6 +19990,24 @@ function createCssHelperConditionalHandler(ctx) {
19767
19990
  };
19768
19991
  return replace(cloned, void 0);
19769
19992
  };
19993
+ /** Apply conditional variants, composing with an outer condition, and inject useTheme() for theme refs. */
19994
+ const applyConditionalVariantsInline = (conditionalVariants, outerCondition) => {
19995
+ for (const cv of conditionalVariants) {
19996
+ applyVariant({
19997
+ when: `${outerCondition} && ${cv.when}`,
19998
+ propName: cv.propName
19999
+ }, cv.style);
20000
+ if (cv.propName) ensureShouldForwardPropDrop(decl, cv.propName);
20001
+ if (cv.when.startsWith("theme.") || cv.when.startsWith("!theme.")) {
20002
+ if (!decl.needsUseThemeHook) decl.needsUseThemeHook = [];
20003
+ if (!decl.needsUseThemeHook.some((e) => e.trueStyleKey === null && e.falseStyleKey === null)) decl.needsUseThemeHook.push({
20004
+ themeProp: "__variantCondition",
20005
+ trueStyleKey: null,
20006
+ falseStyleKey: null
20007
+ });
20008
+ }
20009
+ }
20010
+ };
19770
20011
  const resolveCssBranchToInlineMap = (node) => {
19771
20012
  let tpl = null;
19772
20013
  if (isCssHelperTaggedTemplate(node)) tpl = node.quasi;
@@ -19911,13 +20152,8 @@ function createCssHelperConditionalHandler(ctx) {
19911
20152
  }
19912
20153
  }
19913
20154
  if (Object.keys(consStyle).length > 0) applyVariant(testInfo, consStyle);
19914
- for (const cv of conditionalVariants) {
19915
- applyVariant({
19916
- when: `${testInfo.when} && ${cv.when}`,
19917
- propName: cv.propName
19918
- }, cv.style);
19919
- ensureShouldForwardPropDrop(decl, cv.propName);
19920
- }
20155
+ applyConditionalVariantsInline(conditionalVariants, testInfo.when);
20156
+ dropAllTestInfoProps(testInfo);
19921
20157
  return true;
19922
20158
  }
19923
20159
  if (body.right?.type === "StringLiteral" || body.right?.type === "Literal" && typeof body.right.value === "string") {
@@ -20374,15 +20610,7 @@ function createCssHelperConditionalHandler(ctx) {
20374
20610
  if (!isCssHelperTaggedTemplate(node)) return null;
20375
20611
  return resolveCssHelperTemplate(node.quasi, paramName, decl.loc);
20376
20612
  };
20377
- const applyConditionalVariants = (conditionalVariants, outerCondition) => {
20378
- for (const cv of conditionalVariants) {
20379
- applyVariant({
20380
- when: `${outerCondition} && ${cv.when}`,
20381
- propName: cv.propName
20382
- }, cv.style);
20383
- ensureShouldForwardPropDrop(decl, cv.propName);
20384
- }
20385
- };
20613
+ const applyConditionalVariants = applyConditionalVariantsInline;
20386
20614
  if (consIsCss && altIsCss) {
20387
20615
  const consResolved = resolveCssBranch(cons);
20388
20616
  const altResolved = resolveCssBranch(alt);
@@ -27810,7 +28038,13 @@ function preflight(ctx) {
27810
28038
  const styledLocalNames = /* @__PURE__ */ new Set();
27811
28039
  styledImports.forEach((imp) => {
27812
28040
  const specs = imp.node.specifiers ?? [];
27813
- for (const spec of specs) if (spec.type === "ImportDefaultSpecifier" && spec.local?.type === "Identifier") styledLocalNames.add(spec.local.name);
28041
+ for (const spec of specs) {
28042
+ if (spec.type === "ImportDefaultSpecifier" && spec.local?.type === "Identifier") {
28043
+ styledLocalNames.add(spec.local.name);
28044
+ continue;
28045
+ }
28046
+ if (spec.type === "ImportSpecifier" && spec.imported?.type === "Identifier" && spec.imported.name === "styled" && spec.local?.type === "Identifier") styledLocalNames.add(spec.local.name);
28047
+ }
27814
28048
  });
27815
28049
  ctx.styledLocalNames = styledLocalNames;
27816
28050
  ctx.isStyledTag = (tag) => isStyledTag(styledLocalNames, tag);
@@ -28449,9 +28683,9 @@ function transform(file, api, options) {
28449
28683
  if (result.sidecarContent) {
28450
28684
  const sidecarFiles = options.sidecarFiles;
28451
28685
  if (sidecarFiles) {
28452
- const dir = dirname(file.path);
28453
- const fileBase = basename(file.path).replace(/\.\w+$/, "");
28454
- sidecarFiles.set(join(dir, `${fileBase}.stylex.ts`), result.sidecarContent);
28686
+ const sidecarPath = result.sidecarFilePath ?? join(dirname(file.path), `${basename(file.path).replace(/\.\w+$/, "")}.stylex.ts`);
28687
+ const existing = sidecarFiles.get(sidecarPath);
28688
+ sidecarFiles.set(sidecarPath, existing ? mergeMarkerDeclarations(existing, result.sidecarContent) : result.sidecarContent);
28455
28689
  }
28456
28690
  }
28457
28691
  if (result.bridgeResults && result.bridgeResults.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styled-components-to-stylex-codemod",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "description": "Codemod to transform styled-components to StyleX",
5
5
  "keywords": [
6
6
  "codemod",