@plumeria/codemod 18.2.16 → 18.2.18
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/README.md +139 -1
- package/dist/cli.js +73 -4
- package/dist/release.d.ts +22 -0
- package/dist/release.js +268 -0
- package/dist/transforms/from-plumeria.d.ts +37 -0
- package/dist/transforms/from-plumeria.js +436 -0
- package/dist/transforms/release-styles.d.ts +22 -0
- package/dist/transforms/release-styles.js +344 -0
- package/package.json +3 -2
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.releaseStyles = exports.__private = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const memberPath = (node) => {
|
|
39
|
+
if (node?.type === 'Identifier')
|
|
40
|
+
return [node.name];
|
|
41
|
+
if (node?.type !== 'MemberExpression')
|
|
42
|
+
return undefined;
|
|
43
|
+
const parent = memberPath(node.object);
|
|
44
|
+
const key = node.computed
|
|
45
|
+
? node.property.type === 'Literal'
|
|
46
|
+
? String(node.property.value)
|
|
47
|
+
: undefined
|
|
48
|
+
: node.property.name;
|
|
49
|
+
return parent && key !== undefined ? [...parent, key] : undefined;
|
|
50
|
+
};
|
|
51
|
+
const isCreate = (node) => {
|
|
52
|
+
if (node?.type !== 'CallExpression')
|
|
53
|
+
return false;
|
|
54
|
+
const path = memberPath(node.callee);
|
|
55
|
+
return path?.length === 2 && path[1] === 'create';
|
|
56
|
+
};
|
|
57
|
+
const needsFilter = (node) => node.type === 'LogicalExpression' ||
|
|
58
|
+
node.type === 'ConditionalExpression' ||
|
|
59
|
+
node.type === 'SpreadElement';
|
|
60
|
+
exports.__private = { memberPath, isCreate, needsFilter };
|
|
61
|
+
exports.releaseStyles = {
|
|
62
|
+
meta: {
|
|
63
|
+
type: 'suggestion',
|
|
64
|
+
docs: {
|
|
65
|
+
description: 'Replace Plumeria style definitions and usages with a generated CSS Module',
|
|
66
|
+
},
|
|
67
|
+
fixable: 'code',
|
|
68
|
+
messages: {
|
|
69
|
+
definition: 'Import the generated CSS Module.',
|
|
70
|
+
static: '`createStatic` values were inlined into the generated CSS.',
|
|
71
|
+
theme: '`createTheme` was written to the global stylesheet.',
|
|
72
|
+
themeImport: 'Remove the createTheme import after inlining its CSS variables.',
|
|
73
|
+
animation: '`keyframes` or `viewTransition` was written globally.',
|
|
74
|
+
animationImport: 'Remove the animation import after replacing it with its generated name.',
|
|
75
|
+
core: 'Remove the Plumeria import after exporting the styles.',
|
|
76
|
+
prop: 'Use className after exporting this style to CSS Modules.',
|
|
77
|
+
composition: 'Join the composed CSS Module class names.',
|
|
78
|
+
functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
|
|
79
|
+
unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
|
|
80
|
+
},
|
|
81
|
+
schema: [
|
|
82
|
+
{
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
modules: { type: 'object' },
|
|
86
|
+
themes: { type: 'object' },
|
|
87
|
+
animations: { type: 'object' },
|
|
88
|
+
styleProp: { type: 'string' },
|
|
89
|
+
active: { type: 'boolean' },
|
|
90
|
+
},
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
create(context) {
|
|
96
|
+
const raw = (context.options[0] ?? {});
|
|
97
|
+
const entry = raw.modules?.[context.filename];
|
|
98
|
+
const themeBindings = raw.themes?.[context.filename] ?? [];
|
|
99
|
+
const animationBindings = raw.animations?.[context.filename] ?? [];
|
|
100
|
+
const styleProp = raw.styleProp ?? 'classStyle';
|
|
101
|
+
const sourceCode = context.sourceCode;
|
|
102
|
+
let active = Boolean(entry || raw.active);
|
|
103
|
+
let activeModule = entry;
|
|
104
|
+
const importedReleasedModule = (specifier) => {
|
|
105
|
+
if (!specifier.startsWith('.'))
|
|
106
|
+
return undefined;
|
|
107
|
+
const resolved = path.resolve(path.dirname(context.filename), specifier);
|
|
108
|
+
const withoutExtension = resolved.replace(/\.[^.]+$/, '');
|
|
109
|
+
const source = Object.keys(raw.modules ?? {}).find((candidate) => candidate.replace(/\.[^.]+$/, '') === withoutExtension);
|
|
110
|
+
return source ? raw.modules?.[source] : undefined;
|
|
111
|
+
};
|
|
112
|
+
const importedThemeBindings = (specifier) => {
|
|
113
|
+
if (!specifier.startsWith('.'))
|
|
114
|
+
return undefined;
|
|
115
|
+
const resolved = path.resolve(path.dirname(context.filename), specifier);
|
|
116
|
+
const withoutExtension = resolved.replace(/\.[^.]+$/, '');
|
|
117
|
+
const source = Object.keys(raw.themes ?? {}).find((candidate) => candidate.replace(/\.[^.]+$/, '') === withoutExtension);
|
|
118
|
+
return source ? raw.themes?.[source] : undefined;
|
|
119
|
+
};
|
|
120
|
+
const importedAnimationBindings = (specifier) => {
|
|
121
|
+
if (!specifier.startsWith('.'))
|
|
122
|
+
return undefined;
|
|
123
|
+
const resolved = path.resolve(path.dirname(context.filename), specifier);
|
|
124
|
+
const withoutExtension = resolved.replace(/\.[^.]+$/, '');
|
|
125
|
+
const source = Object.keys(raw.animations ?? {}).find((candidate) => candidate.replace(/\.[^.]+$/, '') === withoutExtension);
|
|
126
|
+
return source ? raw.animations?.[source] : undefined;
|
|
127
|
+
};
|
|
128
|
+
const removeImportedBindings = (node, bindings, messageId) => {
|
|
129
|
+
const removed = node.specifiers.filter((specifier) => {
|
|
130
|
+
if (specifier.type !== 'ImportSpecifier')
|
|
131
|
+
return false;
|
|
132
|
+
const name = specifier.imported.name ?? specifier.imported.value;
|
|
133
|
+
return bindings.includes(name);
|
|
134
|
+
});
|
|
135
|
+
if (removed.length === 0)
|
|
136
|
+
return false;
|
|
137
|
+
const retained = node.specifiers.filter((specifier) => !removed.includes(specifier));
|
|
138
|
+
context.report({
|
|
139
|
+
node,
|
|
140
|
+
messageId,
|
|
141
|
+
fix: (fixer) => {
|
|
142
|
+
if (retained.length === 0)
|
|
143
|
+
return fixer.remove(node);
|
|
144
|
+
const defaultImport = retained.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
|
|
145
|
+
const namedImports = retained.filter((specifier) => specifier.type === 'ImportSpecifier');
|
|
146
|
+
const clauses = [];
|
|
147
|
+
if (defaultImport)
|
|
148
|
+
clauses.push(sourceCode.getText(defaultImport));
|
|
149
|
+
if (namedImports.length > 0) {
|
|
150
|
+
clauses.push(`{ ${namedImports
|
|
151
|
+
.map((specifier) => sourceCode.getText(specifier))
|
|
152
|
+
.join(', ')} }`);
|
|
153
|
+
}
|
|
154
|
+
return fixer.replaceText(node, `import ${clauses.join(', ')} from ${sourceCode.getText(node.source)};`);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
ImportDeclaration(node) {
|
|
161
|
+
if ((entry || themeBindings.length > 0 || animationBindings.length > 0) &&
|
|
162
|
+
node.source.value === '@plumeria/core') {
|
|
163
|
+
context.report({
|
|
164
|
+
node,
|
|
165
|
+
messageId: 'core',
|
|
166
|
+
fix: (fixer) => fixer.remove(node),
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const importedAnimations = importedAnimationBindings(String(node.source.value));
|
|
171
|
+
if (importedAnimations) {
|
|
172
|
+
if (removeImportedBindings(node, importedAnimations, 'animationImport'))
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const importedThemes = importedThemeBindings(String(node.source.value));
|
|
176
|
+
if (importedThemes) {
|
|
177
|
+
if (removeImportedBindings(node, importedThemes, 'themeImport'))
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const importedModule = importedReleasedModule(String(node.source.value));
|
|
181
|
+
if (importedModule) {
|
|
182
|
+
active = true;
|
|
183
|
+
const importedBinding = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
184
|
+
(specifier.imported.name ?? specifier.imported.value) ===
|
|
185
|
+
importedModule.binding);
|
|
186
|
+
activeModule = {
|
|
187
|
+
...importedModule,
|
|
188
|
+
binding: importedBinding?.local.name ?? importedModule.binding,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
VariableDeclaration(node) {
|
|
193
|
+
if (node.declarations.length !== 1)
|
|
194
|
+
return;
|
|
195
|
+
const declaration = node.declarations[0];
|
|
196
|
+
if (declaration.id.type !== 'Identifier')
|
|
197
|
+
return;
|
|
198
|
+
const path = declaration.init?.type === 'CallExpression'
|
|
199
|
+
? memberPath(declaration.init.callee)
|
|
200
|
+
: undefined;
|
|
201
|
+
if (path?.length === 2 &&
|
|
202
|
+
((entry && path[1] === 'createStatic') ||
|
|
203
|
+
(path[1] === 'createTheme' &&
|
|
204
|
+
themeBindings.includes(declaration.id.name)) ||
|
|
205
|
+
((path[1] === 'keyframes' || path[1] === 'viewTransition') &&
|
|
206
|
+
animationBindings.includes(declaration.id.name)))) {
|
|
207
|
+
const target = node.parent?.type === 'ExportNamedDeclaration' ? node.parent : node;
|
|
208
|
+
context.report({
|
|
209
|
+
node: target,
|
|
210
|
+
messageId: path[1] === 'createTheme'
|
|
211
|
+
? 'theme'
|
|
212
|
+
: path[1] === 'keyframes' || path[1] === 'viewTransition'
|
|
213
|
+
? 'animation'
|
|
214
|
+
: 'static',
|
|
215
|
+
fix: (fixer) => fixer.remove(target),
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!entry)
|
|
220
|
+
return;
|
|
221
|
+
if (declaration.id.name !== entry.binding ||
|
|
222
|
+
!isCreate(declaration.init))
|
|
223
|
+
return;
|
|
224
|
+
const exported = node.parent?.type === 'ExportNamedDeclaration';
|
|
225
|
+
const target = exported ? node.parent : node;
|
|
226
|
+
const imported = `import ${entry.binding} from '${entry.source}';`;
|
|
227
|
+
const replacement = exported
|
|
228
|
+
? `${imported}\nexport { ${entry.binding} };`
|
|
229
|
+
: imported;
|
|
230
|
+
context.report({
|
|
231
|
+
node: target,
|
|
232
|
+
messageId: 'definition',
|
|
233
|
+
fix: (fixer) => fixer.replaceText(target, replacement),
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
JSXAttribute(node) {
|
|
237
|
+
if (!active)
|
|
238
|
+
return;
|
|
239
|
+
if (node.name.type !== 'JSXIdentifier' || node.name.name !== styleProp)
|
|
240
|
+
return;
|
|
241
|
+
const expression = node.value?.type === 'JSXExpressionContainer'
|
|
242
|
+
? node.value.expression
|
|
243
|
+
: undefined;
|
|
244
|
+
const moduleInfo = activeModule;
|
|
245
|
+
const functionStyle = (value) => {
|
|
246
|
+
if (!moduleInfo ||
|
|
247
|
+
value?.type !== 'CallExpression' ||
|
|
248
|
+
value.callee.type !== 'MemberExpression' ||
|
|
249
|
+
value.callee.object.type !== 'Identifier' ||
|
|
250
|
+
value.callee.object.name !== moduleInfo.binding)
|
|
251
|
+
return undefined;
|
|
252
|
+
const key = value.callee.computed
|
|
253
|
+
? value.callee.property.type === 'Literal'
|
|
254
|
+
? String(value.callee.property.value)
|
|
255
|
+
: undefined
|
|
256
|
+
: value.callee.property.name;
|
|
257
|
+
const func = key ? moduleInfo.functions?.[key] : undefined;
|
|
258
|
+
if (!key || !func || func.variables.length !== value.arguments.length)
|
|
259
|
+
return undefined;
|
|
260
|
+
return {
|
|
261
|
+
className: `${moduleInfo.binding}.${key}`,
|
|
262
|
+
declarations: func.variables.map((variable, index) => {
|
|
263
|
+
const argument = sourceCode.getText(value.arguments[index]);
|
|
264
|
+
return `'${variable}': ${argument}`;
|
|
265
|
+
}),
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
const replaceWithFunctionStyle = (className, declarations) => {
|
|
269
|
+
const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
|
|
270
|
+
attribute.name.type === 'JSXIdentifier' &&
|
|
271
|
+
attribute.name.name === 'style');
|
|
272
|
+
const classReplacement = `className={${className}}`;
|
|
273
|
+
if (styleAttribute?.value?.type === 'JSXExpressionContainer' &&
|
|
274
|
+
styleAttribute.value.expression.type !== 'JSXEmptyExpression') {
|
|
275
|
+
const existing = sourceCode.getText(styleAttribute.value.expression);
|
|
276
|
+
const merged = styleAttribute.value.expression.type === 'ObjectExpression'
|
|
277
|
+
? `{ ...${existing}, ${declarations.join(', ')} }`
|
|
278
|
+
: `{ ...(${existing}), ${declarations.join(', ')} }`;
|
|
279
|
+
context.report({
|
|
280
|
+
node,
|
|
281
|
+
messageId: 'functionStyle',
|
|
282
|
+
fix: (fixer) => [
|
|
283
|
+
fixer.replaceText(node, classReplacement),
|
|
284
|
+
fixer.replaceText(styleAttribute.value.expression, merged),
|
|
285
|
+
],
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
context.report({
|
|
290
|
+
node,
|
|
291
|
+
messageId: 'functionStyle',
|
|
292
|
+
fix: (fixer) => fixer.replaceText(node, `${classReplacement} style={{ ${declarations.join(', ')} }}`),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const directFunction = functionStyle(expression);
|
|
297
|
+
if (directFunction) {
|
|
298
|
+
replaceWithFunctionStyle(directFunction.className, directFunction.declarations);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (expression?.type === 'CallExpression' &&
|
|
302
|
+
expression.callee.type === 'MemberExpression' &&
|
|
303
|
+
expression.callee.object.type === 'Identifier' &&
|
|
304
|
+
expression.callee.object.name === moduleInfo?.binding) {
|
|
305
|
+
context.report({
|
|
306
|
+
node,
|
|
307
|
+
messageId: 'unsupportedFunctionStyle',
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (expression?.type === 'ArrayExpression') {
|
|
312
|
+
const elements = expression.elements.filter(Boolean);
|
|
313
|
+
if (elements.length > 0) {
|
|
314
|
+
const declarations = [];
|
|
315
|
+
const texts = elements.map((element) => {
|
|
316
|
+
const resolved = functionStyle(element);
|
|
317
|
+
if (!resolved)
|
|
318
|
+
return sourceCode.getText(element);
|
|
319
|
+
declarations.push(...resolved.declarations);
|
|
320
|
+
return resolved.className;
|
|
321
|
+
});
|
|
322
|
+
const replacement = elements.some(needsFilter)
|
|
323
|
+
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
324
|
+
: `\`${texts.map((text) => `\${${text}}`).join(' ')}\``;
|
|
325
|
+
if (declarations.length > 0) {
|
|
326
|
+
replaceWithFunctionStyle(replacement, declarations);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
context.report({
|
|
330
|
+
node: expression,
|
|
331
|
+
messageId: 'composition',
|
|
332
|
+
fix: (fixer) => fixer.replaceText(expression, replacement),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
context.report({
|
|
337
|
+
node: node.name,
|
|
338
|
+
messageId: 'prop',
|
|
339
|
+
fix: (fixer) => fixer.replaceText(node.name, 'className'),
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/codemod",
|
|
3
|
-
"version": "18.2.
|
|
3
|
+
"version": "18.2.18",
|
|
4
4
|
"description": "Codemods for migrating Plumeria APIs",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"@typescript-eslint/parser": "^8.60.0",
|
|
36
36
|
"eslint": "^10.4.1",
|
|
37
37
|
"postcss": "8.5.25",
|
|
38
|
-
"postcss-selector-parser": "^7.1.1"
|
|
38
|
+
"postcss-selector-parser": "^7.1.1",
|
|
39
|
+
"zss-engine": "2.6.0"
|
|
39
40
|
},
|
|
40
41
|
"publishConfig": {
|
|
41
42
|
"access": "public",
|