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