@typestyles/migrate 0.2.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,67 +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(t2__namespace.memberExpression(t2__namespace.arrayExpression([existing, classNameIdentifier]), t2__namespace.identifier("filter")), [
161
- t2__namespace.identifier("Boolean")
162
- ]),
163
- t2__namespace.identifier("join")
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")
371
+ ),
372
+ [t4__namespace.identifier("Boolean")]
373
+ ),
374
+ t4__namespace.identifier("join")
164
375
  ),
165
- [t2__namespace.stringLiteral(" ")]
376
+ [t4__namespace.stringLiteral(" ")]
166
377
  );
167
378
  }
168
- function updateClassNameAttribute(openingElement, classNameIdentifier) {
379
+ function updateClassNameAttribute(openingElement, classNameExpression) {
169
380
  const existingAttr = openingElement.attributes.find(
170
- (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" })
171
382
  );
172
383
  if (!existingAttr) {
173
384
  openingElement.attributes.push(
174
- t2__namespace.jsxAttribute(t2__namespace.jsxIdentifier("className"), t2__namespace.jsxExpressionContainer(classNameIdentifier))
385
+ t4__namespace.jsxAttribute(t4__namespace.jsxIdentifier("className"), t4__namespace.jsxExpressionContainer(classNameExpression))
175
386
  );
176
387
  return;
177
388
  }
178
389
  if (!existingAttr.value) {
179
- existingAttr.value = t2__namespace.jsxExpressionContainer(classNameIdentifier);
390
+ existingAttr.value = t4__namespace.jsxExpressionContainer(classNameExpression);
180
391
  return;
181
392
  }
182
- if (t2__namespace.isStringLiteral(existingAttr.value)) {
183
- existingAttr.value = t2__namespace.jsxExpressionContainer(
184
- 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)
185
396
  );
186
397
  return;
187
398
  }
188
- if (t2__namespace.isJSXExpressionContainer(existingAttr.value)) {
189
- existingAttr.value = t2__namespace.jsxExpressionContainer(
190
- createMergedClassExpression(existingAttr.value.expression, classNameIdentifier)
399
+ if (t4__namespace.isJSXExpressionContainer(existingAttr.value)) {
400
+ existingAttr.value = t4__namespace.jsxExpressionContainer(
401
+ createMergedClassExpression(
402
+ existingAttr.value.expression,
403
+ classNameExpression
404
+ )
191
405
  );
192
406
  }
193
407
  }
194
- 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
+ }
195
414
  let typestylesImport = null;
196
415
  for (const statement of ast.program.body) {
197
- if (t2__namespace.isImportDeclaration(statement) && statement.source.value === "typestyles") {
416
+ if (t4__namespace.isImportDeclaration(statement) && statement.source.value === "typestyles") {
198
417
  typestylesImport = statement;
199
418
  break;
200
419
  }
201
420
  }
421
+ const buildSpecifiers = (names) => [...names].map((name) => t4__namespace.importSpecifier(t4__namespace.identifier(name), t4__namespace.identifier(name)));
202
422
  if (!typestylesImport) {
203
423
  ast.program.body.unshift(
204
- t2__namespace.importDeclaration(
205
- [t2__namespace.importSpecifier(t2__namespace.identifier("styles"), t2__namespace.identifier("styles"))],
206
- t2__namespace.stringLiteral("typestyles")
207
- )
424
+ t4__namespace.importDeclaration(buildSpecifiers(requiredSpecifiers), t4__namespace.stringLiteral("typestyles"))
208
425
  );
209
426
  return;
210
427
  }
211
- const hasStylesSpecifier = typestylesImport.specifiers.some(
212
- (specifier) => t2__namespace.isImportSpecifier(specifier) && t2__namespace.isIdentifier(specifier.imported, { name: "styles" }) && t2__namespace.isIdentifier(specifier.local, { name: "styles" })
213
- );
214
- if (!hasStylesSpecifier) {
215
- typestylesImport.specifiers.push(
216
- 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 })
217
431
  );
432
+ if (!hasSpecifier) {
433
+ typestylesImport.specifiers.push(t4__namespace.importSpecifier(t4__namespace.identifier(name), t4__namespace.identifier(name)));
434
+ }
218
435
  }
219
436
  }
220
437
  function cleanupUnusedImports(ast) {
@@ -224,7 +441,7 @@ function cleanupUnusedImports(ast) {
224
441
  },
225
442
  ImportDeclaration(path) {
226
443
  const unusedLocals = path.node.specifiers.filter((specifier) => {
227
- 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")) {
228
445
  return false;
229
446
  }
230
447
  const local = specifier.local.name;
@@ -232,13 +449,186 @@ function cleanupUnusedImports(ast) {
232
449
  return !binding || binding.referencePaths.length === 0;
233
450
  });
234
451
  if (unusedLocals.length === 0) return;
235
- path.node.specifiers = path.node.specifiers.filter((specifier) => !unusedLocals.includes(specifier));
452
+ path.node.specifiers = path.node.specifiers.filter(
453
+ (specifier) => !unusedLocals.includes(specifier)
454
+ );
236
455
  if (path.node.specifiers.length === 0) {
237
456
  path.remove();
238
457
  }
239
458
  }
240
459
  });
241
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
+ }
242
632
  function isOnlyJsxReferences(binding) {
243
633
  return binding.referencePaths.every((referencePath) => {
244
634
  const parent = referencePath.parentPath;
@@ -256,21 +646,22 @@ function migrateSource(filePath, source) {
256
646
  const styledNames = /* @__PURE__ */ new Set();
257
647
  const cssTagNames = /* @__PURE__ */ new Set();
258
648
  const styledTransforms = /* @__PURE__ */ new Map();
649
+ let needsVars = false;
259
650
  traverse__default.default(ast, {
260
651
  ImportDeclaration(path) {
261
652
  if (path.node.source.value === "styled-components" || path.node.source.value === "@emotion/styled") {
262
653
  for (const specifier of path.node.specifiers) {
263
- if (t2__namespace.isImportDefaultSpecifier(specifier)) {
654
+ if (t4__namespace.isImportDefaultSpecifier(specifier)) {
264
655
  styledNames.add(specifier.local.name);
265
656
  }
266
- 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" })) {
267
658
  cssTagNames.add(specifier.local.name);
268
659
  }
269
660
  }
270
661
  }
271
662
  if (path.node.source.value === "@emotion/react" || path.node.source.value === "@emotion/css") {
272
663
  for (const specifier of path.node.specifiers) {
273
- 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" })) {
274
665
  cssTagNames.add(specifier.local.name);
275
666
  }
276
667
  }
@@ -279,27 +670,66 @@ function migrateSource(filePath, source) {
279
670
  });
280
671
  traverse__default.default(ast, {
281
672
  VariableDeclarator(path) {
282
- if (!t2__namespace.isIdentifier(path.node.id)) return;
283
- 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;
284
675
  const variableName = path.node.id.name;
285
676
  const binding = path.scope.getBinding(variableName);
286
677
  if (!binding) return;
287
678
  const template = path.node.init.quasi;
288
- if (template.expressions.length > 0) {
289
- 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,
290
708
  warnings,
291
- "Skipped template literal with interpolations. Only static templates are migrated.",
292
- variableName
293
- );
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
+ }
294
725
  return;
295
726
  }
296
727
  const cssText = template.quasis.map((quasi) => quasi.value.cooked ?? "").join("");
297
- const objectExpression2 = cssToObjectExpression(cssText, warnings);
298
- if (!objectExpression2) {
728
+ const objectExpression4 = cssToObjectExpression(cssText, warnings);
729
+ if (!objectExpression4) {
299
730
  addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
300
731
  return;
301
732
  }
302
- const styledTarget = parseStyledTarget(path.node.init.tag, styledNames);
303
733
  if (styledTarget) {
304
734
  const declaration = path.parentPath;
305
735
  if (declaration.parentPath?.isExportNamedDeclaration() || declaration.parentPath?.isExportDefaultDeclaration()) {
@@ -311,31 +741,30 @@ function migrateSource(filePath, source) {
311
741
  return;
312
742
  }
313
743
  if (!isOnlyJsxReferences(binding)) {
314
- addWarning(
315
- warnings,
316
- "Skipped styled component with non-JSX references.",
317
- variableName
318
- );
744
+ addWarning(warnings, "Skipped styled component with non-JSX references.", variableName);
319
745
  return;
320
746
  }
321
747
  const classConstName = path.scope.generateUidIdentifier(`${variableName}Class`).name;
322
- path.node.id = t2__namespace.identifier(classConstName);
323
- path.node.init = t2__namespace.callExpression(
324
- t2__namespace.memberExpression(t2__namespace.identifier("styles"), t2__namespace.identifier("class")),
325
- [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]
326
752
  );
327
753
  styledTransforms.set(variableName, {
328
754
  originalName: variableName,
755
+ mode: "class",
329
756
  classConstName,
330
- target: styledTarget
757
+ target: styledTarget,
758
+ propVars: [],
759
+ variantProps: []
331
760
  });
332
761
  changed = true;
333
762
  return;
334
763
  }
335
- if (t2__namespace.isIdentifier(path.node.init.tag) && cssTagNames.has(path.node.init.tag.name)) {
336
- path.node.init = t2__namespace.callExpression(
337
- t2__namespace.memberExpression(t2__namespace.identifier("styles"), t2__namespace.identifier("class")),
338
- [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]
339
768
  );
340
769
  changed = true;
341
770
  }
@@ -345,19 +774,36 @@ function migrateSource(filePath, source) {
345
774
  traverse__default.default(ast, {
346
775
  JSXElement(path) {
347
776
  const openingName = path.node.openingElement.name;
348
- if (!t2__namespace.isJSXIdentifier(openingName)) return;
777
+ if (!t4__namespace.isJSXIdentifier(openingName)) return;
349
778
  const transform = styledTransforms.get(openingName.name);
350
779
  if (!transform) return;
351
780
  path.node.openingElement.name = transform.target.jsxName;
352
781
  if (path.node.closingElement) {
353
782
  path.node.closingElement.name = transform.target.jsxName;
354
783
  }
355
- 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
+ }
356
802
  }
357
803
  });
358
804
  }
359
805
  if (changed) {
360
- ensureStylesImport(ast);
806
+ ensureTypestylesImport(ast, needsVars);
361
807
  cleanupUnusedImports(ast);
362
808
  }
363
809
  const output = generate__default.default(