@typestyles/migrate 0.5.1 → 0.5.3

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.
@@ -0,0 +1,686 @@
1
+ #!/usr/bin/env node
2
+ import { parse } from "@babel/parser";
3
+ import generate from "@babel/generator";
4
+ import traverse from "@babel/traverse";
5
+ import * as t from "@babel/types";
6
+ import postcss from "postcss";
7
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
8
+ import { dirname, extname, relative, resolve } from "node:path";
9
+ import { createPatch } from "diff";
10
+ import fg from "fast-glob";
11
+ import picomatch from "picomatch";
12
+ //#region src/css.ts
13
+ function camelCaseProperty$1(property) {
14
+ return property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
15
+ }
16
+ function toValueNode(value, varReplacements) {
17
+ const trimmed = value.trim();
18
+ if (varReplacements?.has(trimmed)) return varReplacements.get(trimmed);
19
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) return t.numericLiteral(Number(trimmed));
20
+ return t.stringLiteral(trimmed);
21
+ }
22
+ function toKeyNode$1(key) {
23
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return t.identifier(key);
24
+ return t.stringLiteral(key);
25
+ }
26
+ function nodesToObject(nodes, warnings, varReplacements) {
27
+ const properties = [];
28
+ for (const node of nodes ?? []) {
29
+ if (node.type === "decl") {
30
+ const normalized = node.prop.startsWith("--") ? node.prop : camelCaseProperty$1(node.prop);
31
+ properties.push(t.objectProperty(toKeyNode$1(normalized), toValueNode(node.value, varReplacements)));
32
+ continue;
33
+ }
34
+ if (node.type === "rule") {
35
+ const nested = nodesToObject(node.nodes, warnings, varReplacements);
36
+ properties.push(t.objectProperty(t.stringLiteral(node.selector.trim()), nested));
37
+ continue;
38
+ }
39
+ if (node.type === "atrule") {
40
+ const nested = nodesToObject(node.nodes, warnings, varReplacements);
41
+ const atRuleKey = `@${node.name}${node.params ? ` ${node.params}` : ""}`;
42
+ properties.push(t.objectProperty(t.stringLiteral(atRuleKey), nested));
43
+ continue;
44
+ }
45
+ warnings.push({ message: `Unsupported CSS node "${node.type}" skipped.` });
46
+ }
47
+ return t.objectExpression(properties);
48
+ }
49
+ function cssToObjectExpression(cssText, warnings, varReplacements) {
50
+ try {
51
+ return nodesToObject(postcss.parse(cssText).nodes, warnings, varReplacements);
52
+ } catch (error) {
53
+ warnings.push({ message: `Could not parse CSS template literal: ${error.message}` });
54
+ return null;
55
+ }
56
+ }
57
+ //#endregion
58
+ //#region src/interpolations.ts
59
+ const PLACEHOLDER_PREFIX = "__ts_migrate_var_";
60
+ function camelCaseProperty(property) {
61
+ return property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
62
+ }
63
+ function placeholderToken(index) {
64
+ return `${PLACEHOLDER_PREFIX}${index}__`;
65
+ }
66
+ function extractSuffixFromQuasi(quasiText) {
67
+ return quasiText.match(/^([^;\n]*)/)?.[1] ?? "";
68
+ }
69
+ function literalToCssValue(node) {
70
+ if (t.isStringLiteral(node)) return node.value;
71
+ if (t.isNumericLiteral(node)) return String(node.value);
72
+ return null;
73
+ }
74
+ function unwrapExpression(expression) {
75
+ if (t.isParenthesizedExpression(expression)) return unwrapExpression(expression.expression);
76
+ return expression;
77
+ }
78
+ function extractCssPropertyBeforeInterpolation(quasiText) {
79
+ return quasiText.match(/([a-zA-Z-]+)\s*:\s*$/)?.[1] ?? null;
80
+ }
81
+ function stripTrailingPropertyDeclaration(quasiText) {
82
+ return quasiText.replace(/[a-zA-Z-]+\s*:\s*$/, "");
83
+ }
84
+ function stripLeadingDeclarationRemainder(quasiText) {
85
+ const semiIdx = quasiText.indexOf(";");
86
+ if (semiIdx === -1) return quasiText;
87
+ return quasiText.slice(semiIdx + 1).trimStart();
88
+ }
89
+ function parsePropInterpolation(expression) {
90
+ if (!t.isArrowFunctionExpression(expression)) return null;
91
+ const param = expression.params[0];
92
+ if (!param) return null;
93
+ const body = unwrapExpression(expression.body);
94
+ if (!t.isExpression(body)) return null;
95
+ if (t.isIdentifier(param)) {
96
+ if (t.isMemberExpression(body) && !body.computed && t.isIdentifier(body.object, { name: param.name }) && t.isIdentifier(body.property)) return { propName: body.property.name };
97
+ return null;
98
+ }
99
+ if (t.isObjectPattern(param) && t.isIdentifier(body)) {
100
+ const propName = body.name;
101
+ if (!param.properties.some((property) => {
102
+ if (!t.isObjectProperty(property)) return false;
103
+ return (t.isIdentifier(property.key) ? property.key.name : t.isStringLiteral(property.key) ? property.key.value : null) === propName;
104
+ })) return null;
105
+ return { propName };
106
+ }
107
+ return null;
108
+ }
109
+ function parseBooleanTernaryInterpolation(expression) {
110
+ if (!t.isArrowFunctionExpression(expression)) return null;
111
+ const param = expression.params[0];
112
+ if (!t.isIdentifier(param)) return null;
113
+ const body = unwrapExpression(expression.body);
114
+ if (!t.isConditionalExpression(body)) return null;
115
+ if (!t.isMemberExpression(body.test) || body.test.computed || !t.isIdentifier(body.test.object, { name: param.name }) || !t.isIdentifier(body.test.property)) return null;
116
+ const trueValue = literalToCssValue(body.consequent);
117
+ const falseValue = literalToCssValue(body.alternate);
118
+ if (trueValue === null || falseValue === null) return null;
119
+ return {
120
+ propName: body.test.property.name,
121
+ trueValue,
122
+ falseValue
123
+ };
124
+ }
125
+ function parseTemplateInterpolations(template) {
126
+ const interpolations = [];
127
+ for (let i = 0; i < template.expressions.length; i++) {
128
+ const expression = template.expressions[i];
129
+ if (!t.isExpression(expression)) return null;
130
+ const parsed = parsePropInterpolation(expression);
131
+ if (!parsed) return null;
132
+ const followingQuasi = template.quasis[i + 1]?.value.cooked ?? "";
133
+ interpolations.push({
134
+ index: i,
135
+ propName: parsed.propName,
136
+ suffix: extractSuffixFromQuasi(followingQuasi)
137
+ });
138
+ }
139
+ return interpolations;
140
+ }
141
+ function parseBooleanTemplateInterpolations(template) {
142
+ const interpolations = [];
143
+ for (let i = 0; i < template.expressions.length; i++) {
144
+ const expression = template.expressions[i];
145
+ if (!t.isExpression(expression)) return null;
146
+ const parsed = parseBooleanTernaryInterpolation(expression);
147
+ if (!parsed) return null;
148
+ const cssProperty = extractCssPropertyBeforeInterpolation(template.quasis[i]?.value.cooked ?? "");
149
+ if (!cssProperty) return null;
150
+ interpolations.push({
151
+ index: i,
152
+ propName: parsed.propName,
153
+ trueValue: parsed.trueValue,
154
+ falseValue: parsed.falseValue,
155
+ cssProperty: camelCaseProperty(cssProperty)
156
+ });
157
+ }
158
+ return interpolations;
159
+ }
160
+ function reconstructInterpolatedCss(template, interpolations) {
161
+ let result = "";
162
+ for (let i = 0; i < template.quasis.length; i++) {
163
+ let quasiText = template.quasis[i].value.cooked ?? "";
164
+ if (i > 0) {
165
+ const interpolation = interpolations[i - 1];
166
+ if (interpolation?.suffix && quasiText.startsWith(interpolation.suffix)) quasiText = quasiText.slice(interpolation.suffix.length);
167
+ }
168
+ result += quasiText;
169
+ if (i < interpolations.length) result += placeholderToken(interpolations[i].index);
170
+ }
171
+ return result;
172
+ }
173
+ function reconstructStaticCssWithoutVariants(template, booleanInterpolations) {
174
+ const skipIndices = new Set(booleanInterpolations.map((interpolation) => interpolation.index));
175
+ let result = "";
176
+ for (let i = 0; i < template.quasis.length; i++) {
177
+ let quasiText = template.quasis[i].value.cooked ?? "";
178
+ if (skipIndices.has(i)) quasiText = stripTrailingPropertyDeclaration(quasiText);
179
+ if (i > 0 && skipIndices.has(i - 1)) quasiText = stripLeadingDeclarationRemainder(quasiText);
180
+ result += quasiText;
181
+ }
182
+ return result.trim();
183
+ }
184
+ function toVarDebugName(componentName, propName) {
185
+ return `${componentName}${propName.charAt(0).toUpperCase()}${propName.slice(1)}`;
186
+ }
187
+ function toComponentConstName(componentName) {
188
+ if (!componentName) return componentName;
189
+ return componentName.charAt(0).toLowerCase() + componentName.slice(1);
190
+ }
191
+ //#endregion
192
+ //#region src/variants.ts
193
+ function toCssValueNode(value) {
194
+ if (/^-?\d+(\.\d+)?$/.test(value.trim())) return t.numericLiteral(Number(value));
195
+ return t.stringLiteral(value);
196
+ }
197
+ function toKeyNode(key) {
198
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return t.identifier(key);
199
+ return t.stringLiteral(key);
200
+ }
201
+ function buildVariantStyles(properties) {
202
+ return t.objectExpression(Object.entries(properties).map(([key, value]) => t.objectProperty(toKeyNode(key), toCssValueNode(value))));
203
+ }
204
+ function buildVariantRecipeInput(name, baseObject, booleanInterpolations) {
205
+ const variantGroups = new Map();
206
+ for (const interpolation of booleanInterpolations) {
207
+ const group = variantGroups.get(interpolation.propName) ?? {
208
+ true: {},
209
+ false: {}
210
+ };
211
+ group.true[interpolation.cssProperty] = interpolation.trueValue;
212
+ group.false[interpolation.cssProperty] = interpolation.falseValue;
213
+ variantGroups.set(interpolation.propName, group);
214
+ }
215
+ const variantsProperties = [...variantGroups.entries()].map(([propName, values]) => t.objectProperty(t.identifier(propName), t.objectExpression([t.objectProperty(t.identifier("true"), buildVariantStyles(values.true)), t.objectProperty(t.identifier("false"), buildVariantStyles(values.false))])));
216
+ const axisProperties = [...variantGroups.keys()].map((propName) => t.objectProperty(t.identifier(propName), t.callExpression(t.memberExpression(t.identifier("variant"), t.identifier("boolean")), [t.objectExpression([t.objectProperty(t.identifier("default"), t.booleanLiteral(false))])])));
217
+ return t.objectExpression([
218
+ t.objectProperty(t.identifier("name"), t.stringLiteral(name)),
219
+ t.objectProperty(t.identifier("variants"), t.objectExpression(axisProperties)),
220
+ t.objectProperty(t.identifier("styles"), t.objectExpression([t.objectProperty(t.identifier("base"), baseObject), t.objectProperty(t.identifier("variants"), t.objectExpression(variantsProperties))]))
221
+ ]);
222
+ }
223
+ //#endregion
224
+ //#region src/transform.ts
225
+ function toKebabCase(input) {
226
+ return input.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
227
+ }
228
+ function memberExpressionToJsxName(expression) {
229
+ if (t.isIdentifier(expression)) return t.jsxIdentifier(expression.name);
230
+ if (t.isMemberExpression(expression) && !expression.computed) {
231
+ const left = memberExpressionToJsxName(expression.object);
232
+ const right = t.isIdentifier(expression.property) ? t.jsxIdentifier(expression.property.name) : null;
233
+ if (!left || !right) return null;
234
+ return t.jsxMemberExpression(left, right);
235
+ }
236
+ return null;
237
+ }
238
+ function parseStyledTarget(tag, styledNames) {
239
+ if (t.isMemberExpression(tag) && t.isIdentifier(tag.object) && styledNames.has(tag.object.name) && t.isIdentifier(tag.property)) return {
240
+ kind: "intrinsic",
241
+ jsxName: t.jsxIdentifier(tag.property.name)
242
+ };
243
+ if (t.isCallExpression(tag) && t.isIdentifier(tag.callee) && styledNames.has(tag.callee.name)) {
244
+ const firstArg = tag.arguments[0];
245
+ if (!firstArg || !t.isExpression(firstArg)) return null;
246
+ const jsxName = memberExpressionToJsxName(firstArg);
247
+ if (!jsxName) return null;
248
+ return {
249
+ kind: "component",
250
+ jsxName
251
+ };
252
+ }
253
+ return null;
254
+ }
255
+ function addWarning(warnings, message, nodeName) {
256
+ warnings.push({
257
+ message,
258
+ nodeName
259
+ });
260
+ }
261
+ function createMergedClassExpression(existing, classNameExpression) {
262
+ return t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.arrayExpression([existing, classNameExpression]), t.identifier("filter")), [t.identifier("Boolean")]), t.identifier("join")), [t.stringLiteral(" ")]);
263
+ }
264
+ function updateClassNameAttribute(openingElement, classNameExpression) {
265
+ const existingAttr = openingElement.attributes.find((attribute) => t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: "className" }));
266
+ if (!existingAttr) {
267
+ openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier("className"), t.jsxExpressionContainer(classNameExpression)));
268
+ return;
269
+ }
270
+ if (!existingAttr.value) {
271
+ existingAttr.value = t.jsxExpressionContainer(classNameExpression);
272
+ return;
273
+ }
274
+ if (t.isStringLiteral(existingAttr.value)) {
275
+ existingAttr.value = t.jsxExpressionContainer(createMergedClassExpression(t.stringLiteral(existingAttr.value.value), classNameExpression));
276
+ return;
277
+ }
278
+ if (t.isJSXExpressionContainer(existingAttr.value)) existingAttr.value = t.jsxExpressionContainer(createMergedClassExpression(existingAttr.value.expression, classNameExpression));
279
+ }
280
+ function hasLocalBinding(ast, name) {
281
+ for (const statement of ast.program.body) {
282
+ if (!t.isVariableDeclaration(statement)) continue;
283
+ for (const declarator of statement.declarations) {
284
+ if (t.isIdentifier(declarator.id) && declarator.id.name === name) return true;
285
+ if (t.isObjectPattern(declarator.id)) {
286
+ for (const prop of declarator.id.properties) if (t.isObjectProperty(prop) && t.isIdentifier(prop.value) && prop.value.name === name) return true;
287
+ }
288
+ }
289
+ }
290
+ return false;
291
+ }
292
+ function ensureTypestylesRuntime(ast, needs) {
293
+ const requiredSpecifiers = new Set(["createTypeStyles"]);
294
+ if (needs.recipe) requiredSpecifiers.add("variant");
295
+ if (needs.vars) {
296
+ requiredSpecifiers.add("createVar");
297
+ requiredSpecifiers.add("assignVars");
298
+ }
299
+ let typestylesImport = null;
300
+ let lastImportIndex = -1;
301
+ for (let i = 0; i < ast.program.body.length; i++) {
302
+ const statement = ast.program.body[i];
303
+ if (t.isImportDeclaration(statement)) {
304
+ lastImportIndex = i;
305
+ if (statement.source.value === "typestyles" && typestylesImport == null) typestylesImport = statement;
306
+ }
307
+ }
308
+ const buildSpecifiers = (names) => [...names].map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
309
+ if (!typestylesImport) {
310
+ ast.program.body.unshift(t.importDeclaration(buildSpecifiers(requiredSpecifiers), t.stringLiteral("typestyles")));
311
+ lastImportIndex = 0;
312
+ } else for (const name of requiredSpecifiers) if (!typestylesImport.specifiers.some((specifier) => t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name }) && t.isIdentifier(specifier.local, { name }))) typestylesImport.specifiers.push(t.importSpecifier(t.identifier(name), t.identifier(name)));
313
+ const destructureProps = [];
314
+ if (needs.style && !hasLocalBinding(ast, "style")) destructureProps.push(t.objectProperty(t.identifier("style"), t.identifier("style"), false, true));
315
+ if (needs.recipe && !hasLocalBinding(ast, "recipe")) destructureProps.push(t.objectProperty(t.identifier("recipe"), t.identifier("recipe"), false, true));
316
+ if (destructureProps.length === 0) return;
317
+ const factoryDecl = t.variableDeclaration("const", [t.variableDeclarator(t.objectPattern(destructureProps), t.callExpression(t.identifier("createTypeStyles"), []))]);
318
+ ast.program.body.splice(lastImportIndex + 1, 0, factoryDecl);
319
+ }
320
+ function cleanupUnusedImports(ast) {
321
+ traverse(ast, {
322
+ Program(path) {
323
+ path.scope.crawl();
324
+ },
325
+ ImportDeclaration(path) {
326
+ const unusedLocals = path.node.specifiers.filter((specifier) => {
327
+ if (path.node.source.value === "typestyles" && t.isImportSpecifier(specifier) && (specifier.local.name === "createTypeStyles" || specifier.local.name === "createVar" || specifier.local.name === "assignVars")) return false;
328
+ const local = specifier.local.name;
329
+ const binding = path.scope.getBinding(local);
330
+ return !binding || binding.referencePaths.length === 0;
331
+ });
332
+ if (unusedLocals.length === 0) return;
333
+ path.node.specifiers = path.node.specifiers.filter((specifier) => !unusedLocals.includes(specifier));
334
+ if (path.node.specifiers.length === 0) path.remove();
335
+ }
336
+ });
337
+ }
338
+ function createAssignVarValue(propExpression, suffix) {
339
+ if (!suffix) return propExpression;
340
+ if (t.isStringLiteral(propExpression)) return t.stringLiteral(`${propExpression.value}${suffix}`);
341
+ return t.binaryExpression("+", propExpression, t.stringLiteral(suffix));
342
+ }
343
+ function buildAssignVarsCall(propVars, propValues) {
344
+ const properties = propVars.filter((binding) => propValues.has(binding.propName)).map((binding) => {
345
+ const propExpression = propValues.get(binding.propName);
346
+ return t.objectProperty(t.identifier(binding.varConstName), createAssignVarValue(propExpression, binding.suffix), true);
347
+ });
348
+ return t.callExpression(t.identifier("assignVars"), [t.objectExpression(properties)]);
349
+ }
350
+ function mergeStyleExpressions(existing, assignVarsCall) {
351
+ return t.objectExpression([t.spreadElement(existing), t.spreadElement(assignVarsCall)]);
352
+ }
353
+ function updateStyleAttribute(openingElement, assignVarsCall) {
354
+ const existingAttr = openingElement.attributes.find((attribute) => t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: "style" }));
355
+ if (!existingAttr) {
356
+ openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(assignVarsCall)));
357
+ return;
358
+ }
359
+ if (!existingAttr.value) {
360
+ existingAttr.value = t.jsxExpressionContainer(assignVarsCall);
361
+ return;
362
+ }
363
+ if (t.isJSXExpressionContainer(existingAttr.value)) existingAttr.value = t.jsxExpressionContainer(mergeStyleExpressions(existingAttr.value.expression, assignVarsCall));
364
+ }
365
+ function collectPropValuesFromJsx(openingElement, propNames) {
366
+ const values = new Map();
367
+ for (const attribute of openingElement.attributes) {
368
+ if (!t.isJSXAttribute(attribute) || !t.isJSXIdentifier(attribute.name)) continue;
369
+ if (!propNames.has(attribute.name.name)) continue;
370
+ if (!attribute.value) {
371
+ values.set(attribute.name.name, t.booleanLiteral(true));
372
+ continue;
373
+ }
374
+ if (t.isStringLiteral(attribute.value)) {
375
+ values.set(attribute.name.name, t.stringLiteral(attribute.value.value));
376
+ continue;
377
+ }
378
+ if (t.isJSXExpressionContainer(attribute.value)) values.set(attribute.name.name, attribute.value.expression);
379
+ }
380
+ return values;
381
+ }
382
+ function removeStyledProps(openingElement, propNames) {
383
+ openingElement.attributes = openingElement.attributes.filter((attribute) => {
384
+ if (!t.isJSXAttribute(attribute) || !t.isJSXIdentifier(attribute.name)) return true;
385
+ return !propNames.has(attribute.name.name);
386
+ });
387
+ }
388
+ function buildComponentClassNameExpression(componentConstName, variantProps, propValues) {
389
+ const properties = variantProps.filter((propName) => propValues.has(propName)).map((propName) => t.objectProperty(t.identifier(propName), propValues.get(propName)));
390
+ const call = properties.length === 0 ? t.callExpression(t.identifier(componentConstName), []) : t.callExpression(t.identifier(componentConstName), [t.objectExpression(properties)]);
391
+ return t.memberExpression(call, t.identifier("className"));
392
+ }
393
+ function migrateBooleanVariantTemplate(path, variableName, template, styledTarget, warnings, styledTransforms) {
394
+ const booleanInterpolations = parseBooleanTemplateInterpolations(template);
395
+ if (!booleanInterpolations) return false;
396
+ const baseObject = cssToObjectExpression(reconstructStaticCssWithoutVariants(template, booleanInterpolations), warnings);
397
+ if (!baseObject) {
398
+ addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
399
+ return false;
400
+ }
401
+ const componentConstName = path.scope.generateUidIdentifier(toComponentConstName(variableName)).name;
402
+ const recipeInput = buildVariantRecipeInput(toKebabCase(variableName), baseObject, booleanInterpolations);
403
+ const variantProps = [...new Set(booleanInterpolations.map((interpolation) => interpolation.propName))];
404
+ const declaration = path.parentPath;
405
+ if (!declaration.isVariableDeclaration()) return false;
406
+ declaration.node.declarations = [t.variableDeclarator(t.identifier(componentConstName), t.callExpression(t.identifier("recipe"), [recipeInput]))];
407
+ styledTransforms.set(variableName, {
408
+ originalName: variableName,
409
+ mode: "component",
410
+ classConstName: componentConstName,
411
+ target: styledTarget,
412
+ propVars: [],
413
+ variantProps
414
+ });
415
+ return true;
416
+ }
417
+ function migrateInterpolatedTemplate(path, variableName, template, styledTarget, warnings, styledTransforms) {
418
+ const interpolations = parseTemplateInterpolations(template);
419
+ if (!interpolations) {
420
+ addWarning(warnings, "Skipped template literal with unsupported interpolations. Only prop-based patterns like `${props => props.color}` are migrated.", variableName);
421
+ return false;
422
+ }
423
+ const cssText = reconstructInterpolatedCss(template, interpolations);
424
+ const propVars = interpolations.map((interpolation) => ({
425
+ propName: interpolation.propName,
426
+ varConstName: path.scope.generateUidIdentifier(`${variableName}${interpolation.propName.charAt(0).toUpperCase()}${interpolation.propName.slice(1)}Var`).name,
427
+ suffix: interpolation.suffix
428
+ }));
429
+ const varReplacements = new Map();
430
+ for (let i = 0; i < interpolations.length; i++) varReplacements.set(placeholderToken(interpolations[i].index), t.identifier(propVars[i].varConstName));
431
+ const objectExpression = cssToObjectExpression(cssText, warnings, varReplacements);
432
+ if (!objectExpression) {
433
+ addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
434
+ return false;
435
+ }
436
+ const classConstName = path.scope.generateUidIdentifier(`${variableName}Class`).name;
437
+ const varDeclarators = propVars.map((propVar) => t.variableDeclarator(t.identifier(propVar.varConstName), t.callExpression(t.identifier("createVar"), [t.stringLiteral(toVarDebugName(variableName, propVar.propName))])));
438
+ const declaration = path.parentPath;
439
+ if (!declaration.isVariableDeclaration()) return false;
440
+ declaration.node.declarations = [...varDeclarators, t.variableDeclarator(t.identifier(classConstName), t.callExpression(t.identifier("style"), [t.stringLiteral(toKebabCase(variableName)), objectExpression]))];
441
+ styledTransforms.set(variableName, {
442
+ originalName: variableName,
443
+ mode: "class",
444
+ classConstName,
445
+ target: styledTarget,
446
+ propVars,
447
+ variantProps: []
448
+ });
449
+ return true;
450
+ }
451
+ function isOnlyJsxReferences(binding) {
452
+ return binding.referencePaths.every((referencePath) => {
453
+ const parent = referencePath.parentPath;
454
+ if (!parent) return false;
455
+ return parent.isJSXOpeningElement() && parent.get("name") === referencePath || parent.isJSXClosingElement() && parent.get("name") === referencePath;
456
+ });
457
+ }
458
+ function migrateSource(filePath, source) {
459
+ const warnings = [];
460
+ let changed = false;
461
+ const ast = parse(source, {
462
+ sourceType: "module",
463
+ plugins: ["typescript", "jsx"]
464
+ });
465
+ const styledNames = new Set();
466
+ const cssTagNames = new Set();
467
+ const styledTransforms = new Map();
468
+ let needsVars = false;
469
+ let needsStyle = false;
470
+ let needsRecipe = false;
471
+ traverse(ast, { ImportDeclaration(path) {
472
+ if (path.node.source.value === "styled-components" || path.node.source.value === "@emotion/styled") for (const specifier of path.node.specifiers) {
473
+ if (t.isImportDefaultSpecifier(specifier)) styledNames.add(specifier.local.name);
474
+ if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name: "css" })) cssTagNames.add(specifier.local.name);
475
+ }
476
+ if (path.node.source.value === "@emotion/react" || path.node.source.value === "@emotion/css") {
477
+ for (const specifier of path.node.specifiers) if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported, { name: "css" })) cssTagNames.add(specifier.local.name);
478
+ }
479
+ } });
480
+ traverse(ast, { VariableDeclarator(path) {
481
+ if (!t.isIdentifier(path.node.id)) return;
482
+ if (!t.isTaggedTemplateExpression(path.node.init)) return;
483
+ const variableName = path.node.id.name;
484
+ const binding = path.scope.getBinding(variableName);
485
+ if (!binding) return;
486
+ const template = path.node.init.quasi;
487
+ const hasInterpolations = template.expressions.length > 0;
488
+ const styledTarget = parseStyledTarget(path.node.init.tag, styledNames);
489
+ if (hasInterpolations) {
490
+ if (!styledTarget) {
491
+ addWarning(warnings, "Skipped template literal with interpolations. Only styled-components prop patterns are migrated.", variableName);
492
+ return;
493
+ }
494
+ const declaration = path.parentPath;
495
+ if (declaration.parentPath?.isExportNamedDeclaration() || declaration.parentPath?.isExportDefaultDeclaration()) {
496
+ addWarning(warnings, "Skipped exported styled component to avoid changing external API shape.", variableName);
497
+ return;
498
+ }
499
+ if (!isOnlyJsxReferences(binding)) {
500
+ addWarning(warnings, "Skipped styled component with non-JSX references.", variableName);
501
+ return;
502
+ }
503
+ if (migrateBooleanVariantTemplate(path, variableName, template, styledTarget, warnings, styledTransforms)) {
504
+ needsRecipe = true;
505
+ changed = true;
506
+ return;
507
+ }
508
+ if (migrateInterpolatedTemplate(path, variableName, template, styledTarget, warnings, styledTransforms)) {
509
+ needsVars = true;
510
+ needsStyle = true;
511
+ changed = true;
512
+ }
513
+ return;
514
+ }
515
+ const objectExpression = cssToObjectExpression(template.quasis.map((quasi) => quasi.value.cooked ?? "").join(""), warnings);
516
+ if (!objectExpression) {
517
+ addWarning(warnings, "Skipped because CSS could not be parsed.", variableName);
518
+ return;
519
+ }
520
+ if (styledTarget) {
521
+ const declaration = path.parentPath;
522
+ if (declaration.parentPath?.isExportNamedDeclaration() || declaration.parentPath?.isExportDefaultDeclaration()) {
523
+ addWarning(warnings, "Skipped exported styled component to avoid changing external API shape.", variableName);
524
+ return;
525
+ }
526
+ if (!isOnlyJsxReferences(binding)) {
527
+ addWarning(warnings, "Skipped styled component with non-JSX references.", variableName);
528
+ return;
529
+ }
530
+ const classConstName = path.scope.generateUidIdentifier(`${variableName}Class`).name;
531
+ path.node.id = t.identifier(classConstName);
532
+ path.node.init = t.callExpression(t.identifier("style"), [t.stringLiteral(toKebabCase(variableName)), objectExpression]);
533
+ styledTransforms.set(variableName, {
534
+ originalName: variableName,
535
+ mode: "class",
536
+ classConstName,
537
+ target: styledTarget,
538
+ propVars: [],
539
+ variantProps: []
540
+ });
541
+ needsStyle = true;
542
+ changed = true;
543
+ return;
544
+ }
545
+ if (t.isIdentifier(path.node.init.tag) && cssTagNames.has(path.node.init.tag.name)) {
546
+ path.node.init = t.callExpression(t.identifier("style"), [t.stringLiteral(toKebabCase(variableName)), objectExpression]);
547
+ needsStyle = true;
548
+ changed = true;
549
+ }
550
+ } });
551
+ if (styledTransforms.size > 0) traverse(ast, { JSXElement(path) {
552
+ const openingName = path.node.openingElement.name;
553
+ if (!t.isJSXIdentifier(openingName)) return;
554
+ const transform = styledTransforms.get(openingName.name);
555
+ if (!transform) return;
556
+ path.node.openingElement.name = transform.target.jsxName;
557
+ if (path.node.closingElement) path.node.closingElement.name = transform.target.jsxName;
558
+ const classNameExpression = transform.mode === "component" ? buildComponentClassNameExpression(transform.classConstName, transform.variantProps, collectPropValuesFromJsx(path.node.openingElement, new Set(transform.variantProps))) : t.identifier(transform.classConstName);
559
+ updateClassNameAttribute(path.node.openingElement, classNameExpression);
560
+ if (transform.propVars.length > 0) {
561
+ const propNames = new Set(transform.propVars.map((propVar) => propVar.propName));
562
+ const propValues = collectPropValuesFromJsx(path.node.openingElement, propNames);
563
+ if (propValues.size > 0) {
564
+ const assignVarsCall = buildAssignVarsCall(transform.propVars, propValues);
565
+ updateStyleAttribute(path.node.openingElement, assignVarsCall);
566
+ removeStyledProps(path.node.openingElement, propNames);
567
+ }
568
+ }
569
+ if (transform.variantProps.length > 0) removeStyledProps(path.node.openingElement, new Set(transform.variantProps));
570
+ } });
571
+ if (changed) {
572
+ ensureTypestylesRuntime(ast, {
573
+ style: needsStyle,
574
+ recipe: needsRecipe,
575
+ vars: needsVars
576
+ });
577
+ cleanupUnusedImports(ast);
578
+ }
579
+ const output = generate(ast, {
580
+ retainLines: false,
581
+ comments: true,
582
+ concise: false
583
+ }, source);
584
+ return {
585
+ filePath,
586
+ changed: changed && output.code !== source,
587
+ code: output.code,
588
+ warnings
589
+ };
590
+ }
591
+ //#endregion
592
+ //#region src/files.ts
593
+ const DEFAULT_EXCLUDES = [
594
+ "**/node_modules/**",
595
+ "**/dist/**",
596
+ "**/.next/**",
597
+ "**/.turbo/**"
598
+ ];
599
+ function normalizeExtension(extension) {
600
+ return extension.startsWith(".") ? extension : `.${extension}`;
601
+ }
602
+ async function collectTargetFiles(cwd, targets, extensions, include, exclude) {
603
+ const normalizedTargets = targets.length > 0 ? targets : ["."];
604
+ const literalFiles = [];
605
+ const dynamicPatterns = [];
606
+ for (const target of normalizedTargets) {
607
+ const absolute = resolve(cwd, target);
608
+ try {
609
+ const targetStat = await stat(absolute);
610
+ if (targetStat.isFile()) literalFiles.push(absolute);
611
+ else if (targetStat.isDirectory()) dynamicPatterns.push(`${absolute.split("\\").join("/")}/**/*`);
612
+ } catch {
613
+ dynamicPatterns.push(target);
614
+ }
615
+ }
616
+ const globbed = await fg(dynamicPatterns, {
617
+ cwd,
618
+ absolute: true,
619
+ onlyFiles: true,
620
+ ignore: [...DEFAULT_EXCLUDES, ...exclude]
621
+ });
622
+ const extensionSet = new Set(extensions.map(normalizeExtension));
623
+ const includeMatchers = include.length > 0 ? include.map((pattern) => picomatch(pattern)) : null;
624
+ const unique = new Set([...literalFiles, ...globbed]);
625
+ return Array.from(unique).filter((filePath) => extensionSet.has(extname(filePath))).filter((filePath) => {
626
+ if (!includeMatchers) return true;
627
+ const rel = relative(cwd, filePath).split("\\").join("/");
628
+ return includeMatchers.some((matcher) => matcher(rel));
629
+ }).sort();
630
+ }
631
+ //#endregion
632
+ //#region src/migrate.ts
633
+ function renderPatch(filePath, before, after) {
634
+ return createPatch(filePath, before, after, "before", "after").split("\n").slice(2).join("\n");
635
+ }
636
+ async function runMigration(cwd, options) {
637
+ const files = await collectTargetFiles(cwd, options.targets, options.extensions, options.include, options.exclude);
638
+ const reportEntries = [];
639
+ let changedCount = 0;
640
+ let warningCount = 0;
641
+ for (const filePath of files) {
642
+ const before = await readFile(filePath, "utf8");
643
+ const result = migrateSource(filePath, before);
644
+ const relativePath = relative(cwd, filePath);
645
+ warningCount += result.warnings.length;
646
+ if (result.changed) {
647
+ changedCount += 1;
648
+ if (options.write) {
649
+ await writeFile(filePath, result.code, "utf8");
650
+ process.stdout.write(`updated ${relativePath}\n`);
651
+ } else {
652
+ process.stdout.write(`\n--- ${relativePath} (dry-run) ---\n`);
653
+ process.stdout.write(`${renderPatch(relativePath, before, result.code)}\n`);
654
+ }
655
+ }
656
+ for (const warning of result.warnings) {
657
+ const nodeLabel = warning.nodeName ? ` (${warning.nodeName})` : "";
658
+ process.stdout.write(`warning ${relativePath}${nodeLabel}: ${warning.message}\n`);
659
+ }
660
+ reportEntries.push({
661
+ filePath: relativePath,
662
+ changed: result.changed,
663
+ warnings: result.warnings
664
+ });
665
+ }
666
+ const summary = {
667
+ filesScanned: files.length,
668
+ filesChanged: changedCount,
669
+ warnings: warningCount
670
+ };
671
+ process.stdout.write(`\nScanned ${summary.filesScanned} files, changed ${summary.filesChanged}, warnings ${summary.warnings}.\n`);
672
+ const report = {
673
+ summary,
674
+ files: reportEntries
675
+ };
676
+ if (options.reportPath) {
677
+ await mkdir(dirname(options.reportPath), { recursive: true });
678
+ await writeFile(options.reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
679
+ process.stdout.write(`Report written to ${options.reportPath}\n`);
680
+ }
681
+ return report;
682
+ }
683
+ //#endregion
684
+ export { migrateSource as n, runMigration as t };
685
+
686
+ //# sourceMappingURL=migrate-DFON1MgM.js.map