@typestyles/migrate 0.3.0 → 0.4.0

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/cli.cjs CHANGED
@@ -9,7 +9,7 @@ var picomatch = require('picomatch');
9
9
  var parser = require('@babel/parser');
10
10
  var generate = require('@babel/generator');
11
11
  var traverse = require('@babel/traverse');
12
- var t2 = require('@babel/types');
12
+ var t4 = require('@babel/types');
13
13
  var postcss = require('postcss');
14
14
 
15
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -36,7 +36,7 @@ var fg__default = /*#__PURE__*/_interopDefault(fg);
36
36
  var picomatch__default = /*#__PURE__*/_interopDefault(picomatch);
37
37
  var generate__default = /*#__PURE__*/_interopDefault(generate);
38
38
  var traverse__default = /*#__PURE__*/_interopDefault(traverse);
39
- var t2__namespace = /*#__PURE__*/_interopNamespace(t2);
39
+ var t4__namespace = /*#__PURE__*/_interopNamespace(t4);
40
40
  var postcss__default = /*#__PURE__*/_interopDefault(postcss);
41
41
 
42
42
  var DEFAULT_EXCLUDES = ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/.turbo/**"];
@@ -78,42 +78,53 @@ async function collectTargetFiles(cwd, targets, extensions, include, exclude) {
78
78
  function camelCaseProperty(property) {
79
79
  return property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
80
80
  }
81
- function toValueNode(value) {
81
+ function toValueNode(value, varReplacements) {
82
82
  const trimmed = value.trim();
83
+ if (varReplacements?.has(trimmed)) {
84
+ return varReplacements.get(trimmed);
85
+ }
83
86
  if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
84
- return t2__namespace.numericLiteral(Number(trimmed));
87
+ return t4__namespace.numericLiteral(Number(trimmed));
85
88
  }
86
- return t2__namespace.stringLiteral(trimmed);
89
+ return t4__namespace.stringLiteral(trimmed);
87
90
  }
88
91
  function toKeyNode(key) {
89
92
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
90
- return t2__namespace.identifier(key);
93
+ return t4__namespace.identifier(key);
91
94
  }
92
- return t2__namespace.stringLiteral(key);
95
+ return t4__namespace.stringLiteral(key);
93
96
  }
94
- function nodesToObject(nodes, warnings) {
97
+ function nodesToObject(nodes, warnings, varReplacements) {
95
98
  const properties = [];
96
99
  for (const node of nodes ?? []) {
97
100
  if (node.type === "decl") {
98
101
  const normalized = node.prop.startsWith("--") ? node.prop : camelCaseProperty(node.prop);
99
- properties.push(t2__namespace.objectProperty(toKeyNode(normalized), toValueNode(node.value)));
102
+ properties.push(
103
+ t4__namespace.objectProperty(toKeyNode(normalized), toValueNode(node.value, varReplacements))
104
+ );
100
105
  continue;
101
106
  }
102
107
  if (node.type === "rule") {
103
- const nested = nodesToObject(node.nodes, warnings);
104
- properties.push(t2__namespace.objectProperty(t2__namespace.stringLiteral(node.selector.trim()), nested));
108
+ const nested = nodesToObject(node.nodes, warnings, varReplacements);
109
+ properties.push(t4__namespace.objectProperty(t4__namespace.stringLiteral(node.selector.trim()), nested));
110
+ continue;
111
+ }
112
+ if (node.type === "atrule") {
113
+ const nested = nodesToObject(node.nodes, warnings, varReplacements);
114
+ const atRuleKey = `@${node.name}${node.params ? ` ${node.params}` : ""}`;
115
+ properties.push(t4__namespace.objectProperty(t4__namespace.stringLiteral(atRuleKey), nested));
105
116
  continue;
106
117
  }
107
118
  warnings.push({
108
119
  message: `Unsupported CSS node "${node.type}" skipped.`
109
120
  });
110
121
  }
111
- return t2__namespace.objectExpression(properties);
122
+ return t4__namespace.objectExpression(properties);
112
123
  }
113
- function cssToObjectExpression(cssText, warnings) {
124
+ function cssToObjectExpression(cssText, warnings, varReplacements) {
114
125
  try {
115
126
  const root = postcss__default.default.parse(cssText);
116
- return nodesToObject(root.nodes, warnings);
127
+ return nodesToObject(root.nodes, warnings, varReplacements);
117
128
  } catch (error) {
118
129
  warnings.push({
119
130
  message: `Could not parse CSS template literal: ${error.message}`
@@ -121,30 +132,226 @@ function cssToObjectExpression(cssText, warnings) {
121
132
  return null;
122
133
  }
123
134
  }
135
+ var PLACEHOLDER_PREFIX = "__ts_migrate_var_";
136
+ function camelCaseProperty2(property) {
137
+ return property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
138
+ }
139
+ function placeholderToken(index) {
140
+ return `${PLACEHOLDER_PREFIX}${index}__`;
141
+ }
142
+ function extractSuffixFromQuasi(quasiText) {
143
+ const match = quasiText.match(/^([^;\n]*)/);
144
+ return match?.[1] ?? "";
145
+ }
146
+ function literalToCssValue(node) {
147
+ if (t4__namespace.isStringLiteral(node)) return node.value;
148
+ if (t4__namespace.isNumericLiteral(node)) return String(node.value);
149
+ return null;
150
+ }
151
+ function unwrapExpression(expression) {
152
+ if (t4__namespace.isParenthesizedExpression(expression)) {
153
+ return unwrapExpression(expression.expression);
154
+ }
155
+ return expression;
156
+ }
157
+ function extractCssPropertyBeforeInterpolation(quasiText) {
158
+ const match = quasiText.match(/([a-zA-Z-]+)\s*:\s*$/);
159
+ return match?.[1] ?? null;
160
+ }
161
+ function stripTrailingPropertyDeclaration(quasiText) {
162
+ return quasiText.replace(/[a-zA-Z-]+\s*:\s*$/, "");
163
+ }
164
+ function stripLeadingDeclarationRemainder(quasiText) {
165
+ const semiIdx = quasiText.indexOf(";");
166
+ if (semiIdx === -1) return quasiText;
167
+ return quasiText.slice(semiIdx + 1).trimStart();
168
+ }
169
+ function parsePropInterpolation(expression) {
170
+ if (!t4__namespace.isArrowFunctionExpression(expression)) return null;
171
+ const param = expression.params[0];
172
+ if (!param) return null;
173
+ const body = unwrapExpression(expression.body);
174
+ if (!t4__namespace.isExpression(body)) return null;
175
+ if (t4__namespace.isIdentifier(param)) {
176
+ if (t4__namespace.isMemberExpression(body) && !body.computed && t4__namespace.isIdentifier(body.object, { name: param.name }) && t4__namespace.isIdentifier(body.property)) {
177
+ return { propName: body.property.name };
178
+ }
179
+ return null;
180
+ }
181
+ if (t4__namespace.isObjectPattern(param) && t4__namespace.isIdentifier(body)) {
182
+ const propName = body.name;
183
+ const hasProp = param.properties.some((property) => {
184
+ if (!t4__namespace.isObjectProperty(property)) return false;
185
+ const keyName = t4__namespace.isIdentifier(property.key) ? property.key.name : t4__namespace.isStringLiteral(property.key) ? property.key.value : null;
186
+ return keyName === propName;
187
+ });
188
+ if (!hasProp) return null;
189
+ return { propName };
190
+ }
191
+ return null;
192
+ }
193
+ function parseBooleanTernaryInterpolation(expression) {
194
+ if (!t4__namespace.isArrowFunctionExpression(expression)) return null;
195
+ const param = expression.params[0];
196
+ if (!t4__namespace.isIdentifier(param)) return null;
197
+ const body = unwrapExpression(expression.body);
198
+ if (!t4__namespace.isConditionalExpression(body)) return null;
199
+ if (!t4__namespace.isMemberExpression(body.test) || body.test.computed || !t4__namespace.isIdentifier(body.test.object, { name: param.name }) || !t4__namespace.isIdentifier(body.test.property)) {
200
+ return null;
201
+ }
202
+ const trueValue = literalToCssValue(body.consequent);
203
+ const falseValue = literalToCssValue(body.alternate);
204
+ if (trueValue === null || falseValue === null) return null;
205
+ return {
206
+ propName: body.test.property.name,
207
+ trueValue,
208
+ falseValue
209
+ };
210
+ }
211
+ function parseTemplateInterpolations(template) {
212
+ const interpolations = [];
213
+ for (let i = 0; i < template.expressions.length; i++) {
214
+ const expression = template.expressions[i];
215
+ if (!t4__namespace.isExpression(expression)) return null;
216
+ const parsed = parsePropInterpolation(expression);
217
+ if (!parsed) return null;
218
+ const followingQuasi = template.quasis[i + 1]?.value.cooked ?? "";
219
+ interpolations.push({
220
+ index: i,
221
+ propName: parsed.propName,
222
+ suffix: extractSuffixFromQuasi(followingQuasi)
223
+ });
224
+ }
225
+ return interpolations;
226
+ }
227
+ function parseBooleanTemplateInterpolations(template) {
228
+ const interpolations = [];
229
+ for (let i = 0; i < template.expressions.length; i++) {
230
+ const expression = template.expressions[i];
231
+ if (!t4__namespace.isExpression(expression)) return null;
232
+ const parsed = parseBooleanTernaryInterpolation(expression);
233
+ if (!parsed) return null;
234
+ const quasiBefore = template.quasis[i]?.value.cooked ?? "";
235
+ const cssProperty = extractCssPropertyBeforeInterpolation(quasiBefore);
236
+ if (!cssProperty) return null;
237
+ interpolations.push({
238
+ index: i,
239
+ propName: parsed.propName,
240
+ trueValue: parsed.trueValue,
241
+ falseValue: parsed.falseValue,
242
+ cssProperty: camelCaseProperty2(cssProperty)
243
+ });
244
+ }
245
+ return interpolations;
246
+ }
247
+ function reconstructInterpolatedCss(template, interpolations) {
248
+ let result = "";
249
+ for (let i = 0; i < template.quasis.length; i++) {
250
+ let quasiText = template.quasis[i].value.cooked ?? "";
251
+ if (i > 0) {
252
+ const interpolation = interpolations[i - 1];
253
+ if (interpolation?.suffix && quasiText.startsWith(interpolation.suffix)) {
254
+ quasiText = quasiText.slice(interpolation.suffix.length);
255
+ }
256
+ }
257
+ result += quasiText;
258
+ if (i < interpolations.length) {
259
+ result += placeholderToken(interpolations[i].index);
260
+ }
261
+ }
262
+ return result;
263
+ }
264
+ function reconstructStaticCssWithoutVariants(template, booleanInterpolations) {
265
+ const skipIndices = new Set(booleanInterpolations.map((interpolation) => interpolation.index));
266
+ let result = "";
267
+ for (let i = 0; i < template.quasis.length; i++) {
268
+ let quasiText = template.quasis[i].value.cooked ?? "";
269
+ if (skipIndices.has(i)) {
270
+ quasiText = stripTrailingPropertyDeclaration(quasiText);
271
+ }
272
+ if (i > 0 && skipIndices.has(i - 1)) {
273
+ quasiText = stripLeadingDeclarationRemainder(quasiText);
274
+ }
275
+ result += quasiText;
276
+ }
277
+ return result.trim();
278
+ }
279
+ function toVarDebugName(componentName, propName) {
280
+ return `${componentName}${propName.charAt(0).toUpperCase()}${propName.slice(1)}`;
281
+ }
282
+ function toComponentConstName(componentName) {
283
+ if (!componentName) return componentName;
284
+ return componentName.charAt(0).toLowerCase() + componentName.slice(1);
285
+ }
286
+ function toCssValueNode(value) {
287
+ if (/^-?\d+(\.\d+)?$/.test(value.trim())) {
288
+ return t4__namespace.numericLiteral(Number(value));
289
+ }
290
+ return t4__namespace.stringLiteral(value);
291
+ }
292
+ function toKeyNode2(key) {
293
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
294
+ return t4__namespace.identifier(key);
295
+ }
296
+ return t4__namespace.stringLiteral(key);
297
+ }
298
+ function buildVariantStyles(properties) {
299
+ return t4__namespace.objectExpression(
300
+ Object.entries(properties).map(
301
+ ([key, value]) => t4__namespace.objectProperty(toKeyNode2(key), toCssValueNode(value))
302
+ )
303
+ );
304
+ }
305
+ function buildVariantComponentConfig(baseObject, booleanInterpolations) {
306
+ const variantGroups = /* @__PURE__ */ new Map();
307
+ for (const interpolation of booleanInterpolations) {
308
+ const group = variantGroups.get(interpolation.propName) ?? { true: {}, false: {} };
309
+ group.true[interpolation.cssProperty] = interpolation.trueValue;
310
+ group.false[interpolation.cssProperty] = interpolation.falseValue;
311
+ variantGroups.set(interpolation.propName, group);
312
+ }
313
+ const variantsProperties = [...variantGroups.entries()].map(
314
+ ([propName, values]) => t4__namespace.objectProperty(
315
+ t4__namespace.identifier(propName),
316
+ t4__namespace.objectExpression([
317
+ t4__namespace.objectProperty(t4__namespace.identifier("true"), buildVariantStyles(values.true)),
318
+ t4__namespace.objectProperty(t4__namespace.identifier("false"), buildVariantStyles(values.false))
319
+ ])
320
+ )
321
+ );
322
+ const defaultVariantProperties = [...variantGroups.keys()].map(
323
+ (propName) => t4__namespace.objectProperty(t4__namespace.identifier(propName), t4__namespace.booleanLiteral(false))
324
+ );
325
+ return t4__namespace.objectExpression([
326
+ t4__namespace.objectProperty(t4__namespace.identifier("base"), baseObject),
327
+ t4__namespace.objectProperty(t4__namespace.identifier("variants"), t4__namespace.objectExpression(variantsProperties)),
328
+ t4__namespace.objectProperty(t4__namespace.identifier("defaultVariants"), t4__namespace.objectExpression(defaultVariantProperties))
329
+ ]);
330
+ }
124
331
 
125
332
  // src/transform.ts
126
333
  function toKebabCase(input) {
127
334
  return input.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
128
335
  }
129
336
  function memberExpressionToJsxName(expression) {
130
- if (t2__namespace.isIdentifier(expression)) {
131
- return t2__namespace.jsxIdentifier(expression.name);
337
+ if (t4__namespace.isIdentifier(expression)) {
338
+ return t4__namespace.jsxIdentifier(expression.name);
132
339
  }
133
- if (t2__namespace.isMemberExpression(expression) && !expression.computed) {
340
+ if (t4__namespace.isMemberExpression(expression) && !expression.computed) {
134
341
  const left = memberExpressionToJsxName(expression.object);
135
- const right = t2__namespace.isIdentifier(expression.property) ? t2__namespace.jsxIdentifier(expression.property.name) : null;
342
+ const right = t4__namespace.isIdentifier(expression.property) ? t4__namespace.jsxIdentifier(expression.property.name) : null;
136
343
  if (!left || !right) return null;
137
- return t2__namespace.jsxMemberExpression(left, right);
344
+ return t4__namespace.jsxMemberExpression(left, right);
138
345
  }
139
346
  return null;
140
347
  }
141
348
  function parseStyledTarget(tag, styledNames) {
142
- if (t2__namespace.isMemberExpression(tag) && t2__namespace.isIdentifier(tag.object) && styledNames.has(tag.object.name) && t2__namespace.isIdentifier(tag.property)) {
143
- return { kind: "intrinsic", jsxName: t2__namespace.jsxIdentifier(tag.property.name) };
349
+ if (t4__namespace.isMemberExpression(tag) && t4__namespace.isIdentifier(tag.object) && styledNames.has(tag.object.name) && t4__namespace.isIdentifier(tag.property)) {
350
+ return { kind: "intrinsic", jsxName: t4__namespace.jsxIdentifier(tag.property.name) };
144
351
  }
145
- if (t2__namespace.isCallExpression(tag) && t2__namespace.isIdentifier(tag.callee) && styledNames.has(tag.callee.name)) {
352
+ if (t4__namespace.isCallExpression(tag) && t4__namespace.isIdentifier(tag.callee) && styledNames.has(tag.callee.name)) {
146
353
  const firstArg = tag.arguments[0];
147
- if (!firstArg || !t2__namespace.isExpression(firstArg)) return null;
354
+ if (!firstArg || !t4__namespace.isExpression(firstArg)) return null;
148
355
  const jsxName = memberExpressionToJsxName(firstArg);
149
356
  if (!jsxName) return null;
150
357
  return { kind: "component", jsxName };
@@ -154,74 +361,77 @@ function parseStyledTarget(tag, styledNames) {
154
361
  function addWarning(warnings, message, nodeName) {
155
362
  warnings.push({ message, nodeName });
156
363
  }
157
- function createMergedClassExpression(existing, classNameIdentifier) {
158
- return t2__namespace.callExpression(
159
- t2__namespace.memberExpression(
160
- t2__namespace.callExpression(
161
- t2__namespace.memberExpression(
162
- t2__namespace.arrayExpression([existing, classNameIdentifier]),
163
- t2__namespace.identifier("filter")
364
+ function createMergedClassExpression(existing, classNameExpression) {
365
+ return t4__namespace.callExpression(
366
+ t4__namespace.memberExpression(
367
+ t4__namespace.callExpression(
368
+ t4__namespace.memberExpression(
369
+ t4__namespace.arrayExpression([existing, classNameExpression]),
370
+ t4__namespace.identifier("filter")
164
371
  ),
165
- [t2__namespace.identifier("Boolean")]
372
+ [t4__namespace.identifier("Boolean")]
166
373
  ),
167
- t2__namespace.identifier("join")
374
+ t4__namespace.identifier("join")
168
375
  ),
169
- [t2__namespace.stringLiteral(" ")]
376
+ [t4__namespace.stringLiteral(" ")]
170
377
  );
171
378
  }
172
- function updateClassNameAttribute(openingElement, classNameIdentifier) {
379
+ function updateClassNameAttribute(openingElement, classNameExpression) {
173
380
  const existingAttr = openingElement.attributes.find(
174
- (attribute) => t2__namespace.isJSXAttribute(attribute) && t2__namespace.isJSXIdentifier(attribute.name, { name: "className" })
381
+ (attribute) => t4__namespace.isJSXAttribute(attribute) && t4__namespace.isJSXIdentifier(attribute.name, { name: "className" })
175
382
  );
176
383
  if (!existingAttr) {
177
384
  openingElement.attributes.push(
178
- t2__namespace.jsxAttribute(t2__namespace.jsxIdentifier("className"), t2__namespace.jsxExpressionContainer(classNameIdentifier))
385
+ t4__namespace.jsxAttribute(t4__namespace.jsxIdentifier("className"), t4__namespace.jsxExpressionContainer(classNameExpression))
179
386
  );
180
387
  return;
181
388
  }
182
389
  if (!existingAttr.value) {
183
- existingAttr.value = t2__namespace.jsxExpressionContainer(classNameIdentifier);
390
+ existingAttr.value = t4__namespace.jsxExpressionContainer(classNameExpression);
184
391
  return;
185
392
  }
186
- if (t2__namespace.isStringLiteral(existingAttr.value)) {
187
- existingAttr.value = t2__namespace.jsxExpressionContainer(
188
- createMergedClassExpression(t2__namespace.stringLiteral(existingAttr.value.value), classNameIdentifier)
393
+ if (t4__namespace.isStringLiteral(existingAttr.value)) {
394
+ existingAttr.value = t4__namespace.jsxExpressionContainer(
395
+ createMergedClassExpression(t4__namespace.stringLiteral(existingAttr.value.value), classNameExpression)
189
396
  );
190
397
  return;
191
398
  }
192
- if (t2__namespace.isJSXExpressionContainer(existingAttr.value)) {
193
- existingAttr.value = t2__namespace.jsxExpressionContainer(
399
+ if (t4__namespace.isJSXExpressionContainer(existingAttr.value)) {
400
+ existingAttr.value = t4__namespace.jsxExpressionContainer(
194
401
  createMergedClassExpression(
195
402
  existingAttr.value.expression,
196
- classNameIdentifier
403
+ classNameExpression
197
404
  )
198
405
  );
199
406
  }
200
407
  }
201
- function ensureStylesImport(ast) {
408
+ function ensureTypestylesImport(ast, needsVars) {
409
+ const requiredSpecifiers = /* @__PURE__ */ new Set(["styles"]);
410
+ if (needsVars) {
411
+ requiredSpecifiers.add("createVar");
412
+ requiredSpecifiers.add("assignVars");
413
+ }
202
414
  let typestylesImport = null;
203
415
  for (const statement of ast.program.body) {
204
- if (t2__namespace.isImportDeclaration(statement) && statement.source.value === "typestyles") {
416
+ if (t4__namespace.isImportDeclaration(statement) && statement.source.value === "typestyles") {
205
417
  typestylesImport = statement;
206
418
  break;
207
419
  }
208
420
  }
421
+ const buildSpecifiers = (names) => [...names].map((name) => t4__namespace.importSpecifier(t4__namespace.identifier(name), t4__namespace.identifier(name)));
209
422
  if (!typestylesImport) {
210
423
  ast.program.body.unshift(
211
- t2__namespace.importDeclaration(
212
- [t2__namespace.importSpecifier(t2__namespace.identifier("styles"), t2__namespace.identifier("styles"))],
213
- t2__namespace.stringLiteral("typestyles")
214
- )
424
+ t4__namespace.importDeclaration(buildSpecifiers(requiredSpecifiers), t4__namespace.stringLiteral("typestyles"))
215
425
  );
216
426
  return;
217
427
  }
218
- const hasStylesSpecifier = typestylesImport.specifiers.some(
219
- (specifier) => t2__namespace.isImportSpecifier(specifier) && t2__namespace.isIdentifier(specifier.imported, { name: "styles" }) && t2__namespace.isIdentifier(specifier.local, { name: "styles" })
220
- );
221
- if (!hasStylesSpecifier) {
222
- typestylesImport.specifiers.push(
223
- t2__namespace.importSpecifier(t2__namespace.identifier("styles"), t2__namespace.identifier("styles"))
428
+ for (const name of requiredSpecifiers) {
429
+ const hasSpecifier = typestylesImport.specifiers.some(
430
+ (specifier) => t4__namespace.isImportSpecifier(specifier) && t4__namespace.isIdentifier(specifier.imported, { name }) && t4__namespace.isIdentifier(specifier.local, { name })
224
431
  );
432
+ if (!hasSpecifier) {
433
+ typestylesImport.specifiers.push(t4__namespace.importSpecifier(t4__namespace.identifier(name), t4__namespace.identifier(name)));
434
+ }
225
435
  }
226
436
  }
227
437
  function cleanupUnusedImports(ast) {
@@ -231,7 +441,7 @@ function cleanupUnusedImports(ast) {
231
441
  },
232
442
  ImportDeclaration(path) {
233
443
  const unusedLocals = path.node.specifiers.filter((specifier) => {
234
- if (path.node.source.value === "typestyles" && t2__namespace.isImportSpecifier(specifier) && t2__namespace.isIdentifier(specifier.local, { name: "styles" })) {
444
+ if (path.node.source.value === "typestyles" && t4__namespace.isImportSpecifier(specifier) && (specifier.local.name === "styles" || specifier.local.name === "createVar" || specifier.local.name === "assignVars")) {
235
445
  return false;
236
446
  }
237
447
  const local = specifier.local.name;
@@ -248,6 +458,177 @@ function cleanupUnusedImports(ast) {
248
458
  }
249
459
  });
250
460
  }
461
+ function createAssignVarValue(propExpression, suffix) {
462
+ if (!suffix) return propExpression;
463
+ if (t4__namespace.isStringLiteral(propExpression)) {
464
+ return t4__namespace.stringLiteral(`${propExpression.value}${suffix}`);
465
+ }
466
+ return t4__namespace.binaryExpression("+", propExpression, t4__namespace.stringLiteral(suffix));
467
+ }
468
+ function buildAssignVarsCall(propVars, propValues) {
469
+ const properties = propVars.filter((binding) => propValues.has(binding.propName)).map((binding) => {
470
+ const propExpression = propValues.get(binding.propName);
471
+ return t4__namespace.objectProperty(
472
+ t4__namespace.identifier(binding.varConstName),
473
+ createAssignVarValue(propExpression, binding.suffix),
474
+ true
475
+ );
476
+ });
477
+ return t4__namespace.callExpression(t4__namespace.identifier("assignVars"), [t4__namespace.objectExpression(properties)]);
478
+ }
479
+ function mergeStyleExpressions(existing, assignVarsCall) {
480
+ return t4__namespace.objectExpression([t4__namespace.spreadElement(existing), t4__namespace.spreadElement(assignVarsCall)]);
481
+ }
482
+ function updateStyleAttribute(openingElement, assignVarsCall) {
483
+ const existingAttr = openingElement.attributes.find(
484
+ (attribute) => t4__namespace.isJSXAttribute(attribute) && t4__namespace.isJSXIdentifier(attribute.name, { name: "style" })
485
+ );
486
+ if (!existingAttr) {
487
+ openingElement.attributes.push(
488
+ t4__namespace.jsxAttribute(t4__namespace.jsxIdentifier("style"), t4__namespace.jsxExpressionContainer(assignVarsCall))
489
+ );
490
+ return;
491
+ }
492
+ if (!existingAttr.value) {
493
+ existingAttr.value = t4__namespace.jsxExpressionContainer(assignVarsCall);
494
+ return;
495
+ }
496
+ if (t4__namespace.isJSXExpressionContainer(existingAttr.value)) {
497
+ existingAttr.value = t4__namespace.jsxExpressionContainer(
498
+ mergeStyleExpressions(existingAttr.value.expression, assignVarsCall)
499
+ );
500
+ }
501
+ }
502
+ function collectPropValuesFromJsx(openingElement, propNames) {
503
+ const values = /* @__PURE__ */ new Map();
504
+ for (const attribute of openingElement.attributes) {
505
+ if (!t4__namespace.isJSXAttribute(attribute) || !t4__namespace.isJSXIdentifier(attribute.name)) continue;
506
+ if (!propNames.has(attribute.name.name)) continue;
507
+ if (!attribute.value) {
508
+ values.set(attribute.name.name, t4__namespace.booleanLiteral(true));
509
+ continue;
510
+ }
511
+ if (t4__namespace.isStringLiteral(attribute.value)) {
512
+ values.set(attribute.name.name, t4__namespace.stringLiteral(attribute.value.value));
513
+ continue;
514
+ }
515
+ if (t4__namespace.isJSXExpressionContainer(attribute.value)) {
516
+ values.set(attribute.name.name, attribute.value.expression);
517
+ }
518
+ }
519
+ return values;
520
+ }
521
+ function removeStyledProps(openingElement, propNames) {
522
+ openingElement.attributes = openingElement.attributes.filter((attribute) => {
523
+ if (!t4__namespace.isJSXAttribute(attribute) || !t4__namespace.isJSXIdentifier(attribute.name)) return true;
524
+ return !propNames.has(attribute.name.name);
525
+ });
526
+ }
527
+ function buildComponentClassNameExpression(componentConstName, variantProps, propValues) {
528
+ const properties = variantProps.filter((propName) => propValues.has(propName)).map((propName) => t4__namespace.objectProperty(t4__namespace.identifier(propName), propValues.get(propName)));
529
+ if (properties.length === 0) {
530
+ return t4__namespace.callExpression(t4__namespace.identifier(componentConstName), []);
531
+ }
532
+ return t4__namespace.callExpression(t4__namespace.identifier(componentConstName), [t4__namespace.objectExpression(properties)]);
533
+ }
534
+ function migrateBooleanVariantTemplate(path, variableName, template, styledTarget, warnings, styledTransforms) {
535
+ const booleanInterpolations = parseBooleanTemplateInterpolations(template);
536
+ if (!booleanInterpolations) return false;
537
+ const staticCss = reconstructStaticCssWithoutVariants(template, booleanInterpolations);
538
+ const baseObject = cssToObjectExpression(staticCss, warnings);
539
+ if (!baseObject) {
540
+ addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
541
+ return false;
542
+ }
543
+ const componentConstName = path.scope.generateUidIdentifier(
544
+ toComponentConstName(variableName)
545
+ ).name;
546
+ const componentConfig = buildVariantComponentConfig(baseObject, booleanInterpolations);
547
+ const variantProps = [
548
+ ...new Set(booleanInterpolations.map((interpolation) => interpolation.propName))
549
+ ];
550
+ const declaration = path.parentPath;
551
+ if (!declaration.isVariableDeclaration()) return false;
552
+ declaration.node.declarations = [
553
+ t4__namespace.variableDeclarator(
554
+ t4__namespace.identifier(componentConstName),
555
+ t4__namespace.callExpression(t4__namespace.memberExpression(t4__namespace.identifier("styles"), t4__namespace.identifier("component")), [
556
+ t4__namespace.stringLiteral(toKebabCase(variableName)),
557
+ componentConfig
558
+ ])
559
+ )
560
+ ];
561
+ styledTransforms.set(variableName, {
562
+ originalName: variableName,
563
+ mode: "component",
564
+ classConstName: componentConstName,
565
+ target: styledTarget,
566
+ propVars: [],
567
+ variantProps
568
+ });
569
+ return true;
570
+ }
571
+ function migrateInterpolatedTemplate(path, variableName, template, styledTarget, warnings, styledTransforms) {
572
+ const interpolations = parseTemplateInterpolations(template);
573
+ if (!interpolations) {
574
+ addWarning(
575
+ warnings,
576
+ "Skipped template literal with unsupported interpolations. Only prop-based patterns like `${props => props.color}` are migrated.",
577
+ variableName
578
+ );
579
+ return false;
580
+ }
581
+ const cssText = reconstructInterpolatedCss(template, interpolations);
582
+ const propVars = interpolations.map((interpolation) => ({
583
+ propName: interpolation.propName,
584
+ varConstName: path.scope.generateUidIdentifier(
585
+ `${variableName}${interpolation.propName.charAt(0).toUpperCase()}${interpolation.propName.slice(1)}Var`
586
+ ).name,
587
+ suffix: interpolation.suffix
588
+ }));
589
+ const varReplacements = /* @__PURE__ */ new Map();
590
+ for (let i = 0; i < interpolations.length; i++) {
591
+ varReplacements.set(
592
+ placeholderToken(interpolations[i].index),
593
+ t4__namespace.identifier(propVars[i].varConstName)
594
+ );
595
+ }
596
+ const objectExpression4 = cssToObjectExpression(cssText, warnings, varReplacements);
597
+ if (!objectExpression4) {
598
+ addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
599
+ return false;
600
+ }
601
+ const classConstName = path.scope.generateUidIdentifier(`${variableName}Class`).name;
602
+ const varDeclarators = propVars.map(
603
+ (propVar) => t4__namespace.variableDeclarator(
604
+ t4__namespace.identifier(propVar.varConstName),
605
+ t4__namespace.callExpression(t4__namespace.identifier("createVar"), [
606
+ t4__namespace.stringLiteral(toVarDebugName(variableName, propVar.propName))
607
+ ])
608
+ )
609
+ );
610
+ const declaration = path.parentPath;
611
+ if (!declaration.isVariableDeclaration()) return false;
612
+ declaration.node.declarations = [
613
+ ...varDeclarators,
614
+ t4__namespace.variableDeclarator(
615
+ t4__namespace.identifier(classConstName),
616
+ t4__namespace.callExpression(t4__namespace.memberExpression(t4__namespace.identifier("styles"), t4__namespace.identifier("class")), [
617
+ t4__namespace.stringLiteral(toKebabCase(variableName)),
618
+ objectExpression4
619
+ ])
620
+ )
621
+ ];
622
+ styledTransforms.set(variableName, {
623
+ originalName: variableName,
624
+ mode: "class",
625
+ classConstName,
626
+ target: styledTarget,
627
+ propVars,
628
+ variantProps: []
629
+ });
630
+ return true;
631
+ }
251
632
  function isOnlyJsxReferences(binding) {
252
633
  return binding.referencePaths.every((referencePath) => {
253
634
  const parent = referencePath.parentPath;
@@ -265,21 +646,22 @@ function migrateSource(filePath, source) {
265
646
  const styledNames = /* @__PURE__ */ new Set();
266
647
  const cssTagNames = /* @__PURE__ */ new Set();
267
648
  const styledTransforms = /* @__PURE__ */ new Map();
649
+ let needsVars = false;
268
650
  traverse__default.default(ast, {
269
651
  ImportDeclaration(path) {
270
652
  if (path.node.source.value === "styled-components" || path.node.source.value === "@emotion/styled") {
271
653
  for (const specifier of path.node.specifiers) {
272
- if (t2__namespace.isImportDefaultSpecifier(specifier)) {
654
+ if (t4__namespace.isImportDefaultSpecifier(specifier)) {
273
655
  styledNames.add(specifier.local.name);
274
656
  }
275
- if (t2__namespace.isImportSpecifier(specifier) && t2__namespace.isIdentifier(specifier.imported, { name: "css" })) {
657
+ if (t4__namespace.isImportSpecifier(specifier) && t4__namespace.isIdentifier(specifier.imported, { name: "css" })) {
276
658
  cssTagNames.add(specifier.local.name);
277
659
  }
278
660
  }
279
661
  }
280
662
  if (path.node.source.value === "@emotion/react" || path.node.source.value === "@emotion/css") {
281
663
  for (const specifier of path.node.specifiers) {
282
- if (t2__namespace.isImportSpecifier(specifier) && t2__namespace.isIdentifier(specifier.imported, { name: "css" })) {
664
+ if (t4__namespace.isImportSpecifier(specifier) && t4__namespace.isIdentifier(specifier.imported, { name: "css" })) {
283
665
  cssTagNames.add(specifier.local.name);
284
666
  }
285
667
  }
@@ -288,27 +670,66 @@ function migrateSource(filePath, source) {
288
670
  });
289
671
  traverse__default.default(ast, {
290
672
  VariableDeclarator(path) {
291
- if (!t2__namespace.isIdentifier(path.node.id)) return;
292
- if (!t2__namespace.isTaggedTemplateExpression(path.node.init)) return;
673
+ if (!t4__namespace.isIdentifier(path.node.id)) return;
674
+ if (!t4__namespace.isTaggedTemplateExpression(path.node.init)) return;
293
675
  const variableName = path.node.id.name;
294
676
  const binding = path.scope.getBinding(variableName);
295
677
  if (!binding) return;
296
678
  const template = path.node.init.quasi;
297
- if (template.expressions.length > 0) {
298
- addWarning(
679
+ const hasInterpolations = template.expressions.length > 0;
680
+ const styledTarget = parseStyledTarget(path.node.init.tag, styledNames);
681
+ if (hasInterpolations) {
682
+ if (!styledTarget) {
683
+ addWarning(
684
+ warnings,
685
+ "Skipped template literal with interpolations. Only styled-components prop patterns are migrated.",
686
+ variableName
687
+ );
688
+ return;
689
+ }
690
+ const declaration = path.parentPath;
691
+ if (declaration.parentPath?.isExportNamedDeclaration() || declaration.parentPath?.isExportDefaultDeclaration()) {
692
+ addWarning(
693
+ warnings,
694
+ "Skipped exported styled component to avoid changing external API shape.",
695
+ variableName
696
+ );
697
+ return;
698
+ }
699
+ if (!isOnlyJsxReferences(binding)) {
700
+ addWarning(warnings, "Skipped styled component with non-JSX references.", variableName);
701
+ return;
702
+ }
703
+ if (migrateBooleanVariantTemplate(
704
+ path,
705
+ variableName,
706
+ template,
707
+ styledTarget,
299
708
  warnings,
300
- "Skipped template literal with interpolations. Only static templates are migrated.",
301
- variableName
302
- );
709
+ styledTransforms
710
+ )) {
711
+ changed = true;
712
+ return;
713
+ }
714
+ if (migrateInterpolatedTemplate(
715
+ path,
716
+ variableName,
717
+ template,
718
+ styledTarget,
719
+ warnings,
720
+ styledTransforms
721
+ )) {
722
+ needsVars = true;
723
+ changed = true;
724
+ }
303
725
  return;
304
726
  }
305
727
  const cssText = template.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
306
- const objectExpression2 = cssToObjectExpression(cssText, warnings);
307
- if (!objectExpression2) {
728
+ const objectExpression4 = cssToObjectExpression(cssText, warnings);
729
+ if (!objectExpression4) {
308
730
  addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
309
731
  return;
310
732
  }
311
- const styledTarget = parseStyledTarget(path.node.init.tag, styledNames);
312
733
  if (styledTarget) {
313
734
  const declaration = path.parentPath;
314
735
  if (declaration.parentPath?.isExportNamedDeclaration() || declaration.parentPath?.isExportDefaultDeclaration()) {
@@ -324,23 +745,26 @@ function migrateSource(filePath, source) {
324
745
  return;
325
746
  }
326
747
  const classConstName = path.scope.generateUidIdentifier(`${variableName}Class`).name;
327
- path.node.id = t2__namespace.identifier(classConstName);
328
- path.node.init = t2__namespace.callExpression(
329
- t2__namespace.memberExpression(t2__namespace.identifier("styles"), t2__namespace.identifier("class")),
330
- [t2__namespace.stringLiteral(toKebabCase(variableName)), objectExpression2]
748
+ path.node.id = t4__namespace.identifier(classConstName);
749
+ path.node.init = t4__namespace.callExpression(
750
+ t4__namespace.memberExpression(t4__namespace.identifier("styles"), t4__namespace.identifier("class")),
751
+ [t4__namespace.stringLiteral(toKebabCase(variableName)), objectExpression4]
331
752
  );
332
753
  styledTransforms.set(variableName, {
333
754
  originalName: variableName,
755
+ mode: "class",
334
756
  classConstName,
335
- target: styledTarget
757
+ target: styledTarget,
758
+ propVars: [],
759
+ variantProps: []
336
760
  });
337
761
  changed = true;
338
762
  return;
339
763
  }
340
- if (t2__namespace.isIdentifier(path.node.init.tag) && cssTagNames.has(path.node.init.tag.name)) {
341
- path.node.init = t2__namespace.callExpression(
342
- t2__namespace.memberExpression(t2__namespace.identifier("styles"), t2__namespace.identifier("class")),
343
- [t2__namespace.stringLiteral(toKebabCase(variableName)), objectExpression2]
764
+ if (t4__namespace.isIdentifier(path.node.init.tag) && cssTagNames.has(path.node.init.tag.name)) {
765
+ path.node.init = t4__namespace.callExpression(
766
+ t4__namespace.memberExpression(t4__namespace.identifier("styles"), t4__namespace.identifier("class")),
767
+ [t4__namespace.stringLiteral(toKebabCase(variableName)), objectExpression4]
344
768
  );
345
769
  changed = true;
346
770
  }
@@ -350,19 +774,36 @@ function migrateSource(filePath, source) {
350
774
  traverse__default.default(ast, {
351
775
  JSXElement(path) {
352
776
  const openingName = path.node.openingElement.name;
353
- if (!t2__namespace.isJSXIdentifier(openingName)) return;
777
+ if (!t4__namespace.isJSXIdentifier(openingName)) return;
354
778
  const transform = styledTransforms.get(openingName.name);
355
779
  if (!transform) return;
356
780
  path.node.openingElement.name = transform.target.jsxName;
357
781
  if (path.node.closingElement) {
358
782
  path.node.closingElement.name = transform.target.jsxName;
359
783
  }
360
- updateClassNameAttribute(path.node.openingElement, t2__namespace.identifier(transform.classConstName));
784
+ const classNameExpression = transform.mode === "component" ? buildComponentClassNameExpression(
785
+ transform.classConstName,
786
+ transform.variantProps,
787
+ collectPropValuesFromJsx(path.node.openingElement, new Set(transform.variantProps))
788
+ ) : t4__namespace.identifier(transform.classConstName);
789
+ updateClassNameAttribute(path.node.openingElement, classNameExpression);
790
+ if (transform.propVars.length > 0) {
791
+ const propNames = new Set(transform.propVars.map((propVar) => propVar.propName));
792
+ const propValues = collectPropValuesFromJsx(path.node.openingElement, propNames);
793
+ if (propValues.size > 0) {
794
+ const assignVarsCall = buildAssignVarsCall(transform.propVars, propValues);
795
+ updateStyleAttribute(path.node.openingElement, assignVarsCall);
796
+ removeStyledProps(path.node.openingElement, propNames);
797
+ }
798
+ }
799
+ if (transform.variantProps.length > 0) {
800
+ removeStyledProps(path.node.openingElement, new Set(transform.variantProps));
801
+ }
361
802
  }
362
803
  });
363
804
  }
364
805
  if (changed) {
365
- ensureStylesImport(ast);
806
+ ensureTypestylesImport(ast, needsVars);
366
807
  cleanupUnusedImports(ast);
367
808
  }
368
809
  const output = generate__default.default(