@plumeria/codemod 19.15.0 → 21.0.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/README.md +38 -0
- package/dist/cli.js +46 -8
- package/dist/release.js +1 -1
- package/dist/transforms/adopt-styles.js +7 -1
- package/dist/transforms/from-plumeria.js +3 -3
- package/dist/transforms/release-styles.js +25 -5
- package/dist/transforms/rename-api.d.ts +6 -0
- package/dist/transforms/rename-api.js +73 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ No install needed — run it with `npx`.
|
|
|
7
7
|
npx @plumeria/codemod migrate --from css-modules
|
|
8
8
|
npx @plumeria/codemod migrate --from plumeria
|
|
9
9
|
npx @plumeria/codemod rename-prop classStyle sx
|
|
10
|
+
npx @plumeria/codemod rename-api viewTransition viewTransitionClass
|
|
10
11
|
```
|
|
11
12
|
|
|
12
13
|
Commit before running, so the rewrite can be reverted with `git checkout`.
|
|
@@ -361,3 +362,40 @@ withPlumeria(nextConfig, { styleProp: 'sx' });
|
|
|
361
362
|
// eslint.config.js
|
|
362
363
|
settings: { plumeria: { styleProp: 'sx' } }
|
|
363
364
|
```
|
|
365
|
+
|
|
366
|
+
## `rename-api`
|
|
367
|
+
|
|
368
|
+
Renames a Plumeria API after it has changed its name in a release:
|
|
369
|
+
|
|
370
|
+
```sh
|
|
371
|
+
npx @plumeria/codemod rename-api <from> <to> [paths...]
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Option | Effect |
|
|
375
|
+
| --------------- | ---------------------------------------- |
|
|
376
|
+
| `-d, --dry-run` | report what would change without writing |
|
|
377
|
+
|
|
378
|
+
`<to>` has to be an API `@plumeria/core` exports, so a typo is reported instead
|
|
379
|
+
of written. Paths default to the current directory, with the same directories
|
|
380
|
+
skipped as `rename-prop`.
|
|
381
|
+
|
|
382
|
+
### What it rewrites
|
|
383
|
+
|
|
384
|
+
Calls through the namespace or default import of `@plumeria/core`:
|
|
385
|
+
|
|
386
|
+
```diff
|
|
387
|
+
import * as css from '@plumeria/core';
|
|
388
|
+
- const fade = css.viewTransition({ old: { opacity: 0 } });
|
|
389
|
+
+ const fade = css.viewTransitionClass({ old: { opacity: 0 } });
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
And named imports, together with every reference to them. An alias keeps its
|
|
393
|
+
local name, so only the import changes:
|
|
394
|
+
|
|
395
|
+
```diff
|
|
396
|
+
- import { viewTransition, viewTransition as vt } from '@plumeria/core';
|
|
397
|
+
- const fade = viewTransition({ old: { opacity: 0 } });
|
|
398
|
+
+ import { viewTransitionClass, viewTransitionClass as vt } from '@plumeria/core';
|
|
399
|
+
+ const fade = viewTransitionClass({ old: { opacity: 0 } });
|
|
400
|
+
const slide = vt({ new: { opacity: 1 } });
|
|
401
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -42,12 +42,25 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
42
42
|
const eslint_1 = require("eslint");
|
|
43
43
|
const tsParser = __importStar(require("@typescript-eslint/parser"));
|
|
44
44
|
const rename_prop_1 = require("./transforms/rename-prop");
|
|
45
|
+
const rename_api_1 = require("./transforms/rename-api");
|
|
45
46
|
const adopt_styles_1 = require("./transforms/adopt-styles");
|
|
46
47
|
const release_styles_1 = require("./transforms/release-styles");
|
|
47
48
|
const migrate_1 = require("./migrate");
|
|
48
49
|
const release_1 = require("./release");
|
|
49
50
|
const { version } = require('../package.json');
|
|
50
|
-
const TRANSFORMS = ['rename-prop', 'migrate'];
|
|
51
|
+
const TRANSFORMS = ['rename-prop', 'rename-api', 'migrate'];
|
|
52
|
+
const APIS = [
|
|
53
|
+
'create',
|
|
54
|
+
'createTheme',
|
|
55
|
+
'createStatic',
|
|
56
|
+
'keyframes',
|
|
57
|
+
'viewTransitionClass',
|
|
58
|
+
'positionTry',
|
|
59
|
+
'varType',
|
|
60
|
+
'marker',
|
|
61
|
+
'extended',
|
|
62
|
+
'use',
|
|
63
|
+
];
|
|
51
64
|
const SOURCES = ['css-modules', 'plumeria'];
|
|
52
65
|
const SOURCE_FILES = '**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}';
|
|
53
66
|
const IGNORES = [
|
|
@@ -63,6 +76,7 @@ const USAGE = `plumeria-codemod — codemods for migrating Plumeria APIs
|
|
|
63
76
|
|
|
64
77
|
Usage
|
|
65
78
|
npx @plumeria/codemod rename-prop <from> <to> [paths...]
|
|
79
|
+
npx @plumeria/codemod rename-api <from> <to> [paths...]
|
|
66
80
|
npx @plumeria/codemod migrate --from css-modules [paths...]
|
|
67
81
|
npx @plumeria/codemod migrate --from plumeria [paths...]
|
|
68
82
|
|
|
@@ -76,6 +90,7 @@ Options
|
|
|
76
90
|
Examples
|
|
77
91
|
npx @plumeria/codemod rename-prop classStyle sx
|
|
78
92
|
npx @plumeria/codemod rename-prop classStyle sx src app --dry-run
|
|
93
|
+
npx @plumeria/codemod rename-api viewTransition viewTransitionClass
|
|
79
94
|
npx @plumeria/codemod migrate --from css-modules
|
|
80
95
|
npx @plumeria/codemod migrate --from plumeria --dry-run
|
|
81
96
|
|
|
@@ -139,14 +154,27 @@ function parseArgs(argv) {
|
|
|
139
154
|
};
|
|
140
155
|
}
|
|
141
156
|
if (!from || !to) {
|
|
142
|
-
throw new UsageError(
|
|
143
|
-
}
|
|
144
|
-
if (!IDENTIFIER.test(to)) {
|
|
145
|
-
throw new UsageError(`"${to}" is not a valid identifier`);
|
|
157
|
+
throw new UsageError(`${transform} needs both <from> and <to>`);
|
|
146
158
|
}
|
|
147
159
|
if (from === to) {
|
|
148
160
|
throw new UsageError(`<from> and <to> are both "${from}"`);
|
|
149
161
|
}
|
|
162
|
+
if (transform === 'rename-api') {
|
|
163
|
+
if (!APIS.includes(to)) {
|
|
164
|
+
throw new UsageError(`"${to}" is not a Plumeria API. Available: ${APIS.join(', ')}`);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
transform: 'rename-api',
|
|
168
|
+
from,
|
|
169
|
+
to,
|
|
170
|
+
targets: targets.length > 0 ? targets : ['.'],
|
|
171
|
+
dryRun,
|
|
172
|
+
includeTypes,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (!IDENTIFIER.test(to)) {
|
|
176
|
+
throw new UsageError(`"${to}" is not a valid identifier`);
|
|
177
|
+
}
|
|
150
178
|
if (RESERVED.has(to)) {
|
|
151
179
|
throw new UsageError(`"${to}" is already used by React`);
|
|
152
180
|
}
|
|
@@ -160,7 +188,18 @@ function parseArgs(argv) {
|
|
|
160
188
|
};
|
|
161
189
|
}
|
|
162
190
|
function buildConfig(options) {
|
|
163
|
-
const { from, to, includeTypes } = options;
|
|
191
|
+
const { transform, from, to, includeTypes } = options;
|
|
192
|
+
const rule = transform === 'rename-api'
|
|
193
|
+
? {
|
|
194
|
+
plugins: { codemod: { rules: { 'rename-api': rename_api_1.renameApi } } },
|
|
195
|
+
rules: { 'codemod/rename-api': ['error', { from, to }] },
|
|
196
|
+
}
|
|
197
|
+
: {
|
|
198
|
+
plugins: { codemod: { rules: { 'rename-prop': rename_prop_1.renameProp } } },
|
|
199
|
+
rules: {
|
|
200
|
+
'codemod/rename-prop': ['error', { from, to, includeTypes }],
|
|
201
|
+
},
|
|
202
|
+
};
|
|
164
203
|
return [
|
|
165
204
|
{ ignores: IGNORES },
|
|
166
205
|
{
|
|
@@ -172,8 +211,7 @@ function buildConfig(options) {
|
|
|
172
211
|
ecmaFeatures: { jsx: true },
|
|
173
212
|
},
|
|
174
213
|
},
|
|
175
|
-
|
|
176
|
-
rules: { 'codemod/rename-prop': ['error', { from, to, includeTypes }] },
|
|
214
|
+
...rule,
|
|
177
215
|
},
|
|
178
216
|
];
|
|
179
217
|
}
|
package/dist/release.js
CHANGED
|
@@ -42,7 +42,7 @@ const path = __importStar(require("node:path"));
|
|
|
42
42
|
const parser_1 = require("@typescript-eslint/parser");
|
|
43
43
|
const resolve_1 = require("./resolve");
|
|
44
44
|
const from_plumeria_1 = require("./transforms/from-plumeria");
|
|
45
|
-
const GLOBAL_OPEN = '/* Generated from css.createTheme, css.keyframes, and css.
|
|
45
|
+
const GLOBAL_OPEN = '/* Generated from css.createTheme, css.keyframes, and css.viewTransitionClass by @plumeria/codemod */';
|
|
46
46
|
const GLOBAL_CLOSE = '/* End of @plumeria/codemod generated styles */';
|
|
47
47
|
const EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
|
|
48
48
|
const IGNORED = new Set([
|
|
@@ -105,6 +105,12 @@ exports.adoptStyles = {
|
|
|
105
105
|
(current.parent.name?.name === 'className' ||
|
|
106
106
|
current.parent.name?.name === styleProp));
|
|
107
107
|
};
|
|
108
|
+
const lone = (node) => node?.type === 'LogicalExpression' &&
|
|
109
|
+
node.operator === '||' &&
|
|
110
|
+
node.right.type === 'Identifier' &&
|
|
111
|
+
node.right.name === 'undefined'
|
|
112
|
+
? [node.left]
|
|
113
|
+
: undefined;
|
|
108
114
|
const joinedElements = (node) => {
|
|
109
115
|
if (node?.type === 'TemplateLiteral' && node.expressions.length > 1) {
|
|
110
116
|
const spaced = node.quasis.every((quasi, index) => index === 0 || index === node.quasis.length - 1
|
|
@@ -117,7 +123,7 @@ exports.adoptStyles = {
|
|
|
117
123
|
node.callee.property?.name !== 'join' ||
|
|
118
124
|
node.arguments.length !== 1 ||
|
|
119
125
|
node.arguments[0].value !== ' ')
|
|
120
|
-
return
|
|
126
|
+
return lone(node);
|
|
121
127
|
const joined = node.callee.object;
|
|
122
128
|
if (joined?.type === 'ArrayExpression')
|
|
123
129
|
return joined.elements.filter(Boolean);
|
|
@@ -389,11 +389,11 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
|
|
|
389
389
|
}
|
|
390
390
|
for (const declaration of declarations) {
|
|
391
391
|
if (declaration.id.type !== 'Identifier' ||
|
|
392
|
-
!isCall(declaration.init, '
|
|
392
|
+
!isCall(declaration.init, 'viewTransitionClass'))
|
|
393
393
|
continue;
|
|
394
394
|
const object = evaluate(declaration.init.arguments[0], values);
|
|
395
395
|
if (!object || typeof object !== 'object') {
|
|
396
|
-
report(declaration.init, 'dynamic-view-transition', '`
|
|
396
|
+
report(declaration.init, 'dynamic-view-transition', '`viewTransitionClass` needs a statically resolvable object.');
|
|
397
397
|
continue;
|
|
398
398
|
}
|
|
399
399
|
const hash = (0, zss_engine_1.genBase36Hash)(object, 1, 8);
|
|
@@ -406,7 +406,7 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
|
|
|
406
406
|
const style = object[part];
|
|
407
407
|
if (style && typeof style === 'object') {
|
|
408
408
|
const pseudoPart = part === 'imagePair' ? 'image-pair' : part;
|
|
409
|
-
parts.push(emitGlobalObject(`::view-transition-${pseudoPart}(
|
|
409
|
+
parts.push(emitGlobalObject(`::view-transition-${pseudoPart}(*.${name})`, style));
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
if (parts.length > 0)
|
|
@@ -76,6 +76,13 @@ const walk = (node, visit) => {
|
|
|
76
76
|
const needsFilter = (node) => node.type === 'LogicalExpression' ||
|
|
77
77
|
node.type === 'ConditionalExpression' ||
|
|
78
78
|
node.type === 'SpreadElement';
|
|
79
|
+
const mayBeFalsy = (node) => {
|
|
80
|
+
if (node.type === 'LogicalExpression')
|
|
81
|
+
return node.operator === '&&' || mayBeFalsy(node.right);
|
|
82
|
+
if (node.type === 'ConditionalExpression')
|
|
83
|
+
return mayBeFalsy(node.consequent) || mayBeFalsy(node.alternate);
|
|
84
|
+
return node.type === 'Literal' && !node.value;
|
|
85
|
+
};
|
|
79
86
|
exports.__private = { memberPath, isCreate, needsFilter };
|
|
80
87
|
exports.releaseStyles = {
|
|
81
88
|
meta: {
|
|
@@ -89,7 +96,7 @@ exports.releaseStyles = {
|
|
|
89
96
|
static: '`createStatic` values were inlined into the generated CSS.',
|
|
90
97
|
theme: '`createTheme` was written to the global stylesheet.',
|
|
91
98
|
themeImport: 'Remove the createTheme import after inlining its CSS variables.',
|
|
92
|
-
animation: '`keyframes` or `
|
|
99
|
+
animation: '`keyframes` or `viewTransitionClass` was written globally.',
|
|
93
100
|
animationImport: 'Remove the animation import after replacing it with its generated name.',
|
|
94
101
|
staticImport: 'Remove the createStatic import after inlining its values into the CSS.',
|
|
95
102
|
alias: 'Read this style from the CSS Module the whole file was exported to.',
|
|
@@ -101,6 +108,7 @@ exports.releaseStyles = {
|
|
|
101
108
|
styleType: 'A released style is a class name.',
|
|
102
109
|
folded: 'This only named a style key, which the export has resolved.',
|
|
103
110
|
read: 'Read this style by the name the export gave it.',
|
|
111
|
+
falsy: 'className takes a string or undefined, so a condition that can be false reads as undefined.',
|
|
104
112
|
functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
|
|
105
113
|
unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
|
|
106
114
|
},
|
|
@@ -162,7 +170,7 @@ exports.releaseStyles = {
|
|
|
162
170
|
return Boolean(entry);
|
|
163
171
|
if (called[1] === 'createTheme')
|
|
164
172
|
return themeBindings.includes(only.id.name);
|
|
165
|
-
if (called[1] === 'keyframes' || called[1] === '
|
|
173
|
+
if (called[1] === 'keyframes' || called[1] === 'viewTransitionClass')
|
|
166
174
|
return animationBindings.includes(only.id.name);
|
|
167
175
|
if (called[1] === 'create')
|
|
168
176
|
return Boolean(entry &&
|
|
@@ -359,7 +367,7 @@ exports.releaseStyles = {
|
|
|
359
367
|
? Boolean(entry)
|
|
360
368
|
: method === 'createTheme'
|
|
361
369
|
? themeBindings.length > 0
|
|
362
|
-
: method === 'keyframes' || method === '
|
|
370
|
+
: method === 'keyframes' || method === 'viewTransitionClass'
|
|
363
371
|
? animationBindings.length > 0
|
|
364
372
|
: false;
|
|
365
373
|
const survivesCore = (program) => {
|
|
@@ -596,14 +604,14 @@ exports.releaseStyles = {
|
|
|
596
604
|
((entry && path[1] === 'createStatic') ||
|
|
597
605
|
(path[1] === 'createTheme' &&
|
|
598
606
|
themeBindings.includes(declaration.id.name)) ||
|
|
599
|
-
((path[1] === 'keyframes' || path[1] === '
|
|
607
|
+
((path[1] === 'keyframes' || path[1] === 'viewTransitionClass') &&
|
|
600
608
|
animationBindings.includes(declaration.id.name)))) {
|
|
601
609
|
const target = node.parent?.type === 'ExportNamedDeclaration' ? node.parent : node;
|
|
602
610
|
context.report({
|
|
603
611
|
node: target,
|
|
604
612
|
messageId: path[1] === 'createTheme'
|
|
605
613
|
? 'theme'
|
|
606
|
-
: path[1] === 'keyframes' || path[1] === '
|
|
614
|
+
: path[1] === 'keyframes' || path[1] === 'viewTransitionClass'
|
|
607
615
|
? 'animation'
|
|
608
616
|
: 'static',
|
|
609
617
|
fix: (fixer) => removeStatement(fixer, target),
|
|
@@ -923,6 +931,18 @@ exports.releaseStyles = {
|
|
|
923
931
|
const text = expression ? resolveText(expression) : undefined;
|
|
924
932
|
if (!complete && expression && text === undefined)
|
|
925
933
|
return;
|
|
934
|
+
if (!carried &&
|
|
935
|
+
expression &&
|
|
936
|
+
text !== undefined &&
|
|
937
|
+
mayBeFalsy(expression)) {
|
|
938
|
+
context.report({
|
|
939
|
+
node: expression,
|
|
940
|
+
messageId: 'falsy',
|
|
941
|
+
fix: (fixer) => fixer.replaceText(expression, `(${text}) || undefined`),
|
|
942
|
+
});
|
|
943
|
+
takeProp();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
926
946
|
const foldsKey = expression?.type === 'MemberExpression' &&
|
|
927
947
|
expression.computed &&
|
|
928
948
|
expression.property.type === 'Identifier' &&
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renameApi = void 0;
|
|
4
|
+
const CORE = '@plumeria/core';
|
|
5
|
+
exports.renameApi = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Rename a Plumeria API to a new name',
|
|
10
|
+
},
|
|
11
|
+
fixable: 'code',
|
|
12
|
+
messages: {
|
|
13
|
+
rename: 'Rename "{{from}}" to "{{to}}".',
|
|
14
|
+
},
|
|
15
|
+
schema: [
|
|
16
|
+
{
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
from: { type: 'string' },
|
|
20
|
+
to: { type: 'string' },
|
|
21
|
+
},
|
|
22
|
+
required: ['from', 'to'],
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
create(context) {
|
|
28
|
+
const { from, to } = context.options[0];
|
|
29
|
+
const namespaces = new Set();
|
|
30
|
+
const rename = (node) => context.report({
|
|
31
|
+
node,
|
|
32
|
+
messageId: 'rename',
|
|
33
|
+
data: { from, to },
|
|
34
|
+
fix: (fixer) => fixer.replaceText(node, to),
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
ImportDeclaration(node) {
|
|
38
|
+
if (node.source.value !== CORE)
|
|
39
|
+
return;
|
|
40
|
+
for (const specifier of node.specifiers) {
|
|
41
|
+
if (specifier.type !== 'ImportSpecifier') {
|
|
42
|
+
namespaces.add(specifier.local.name);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (specifier.imported.type !== 'Identifier')
|
|
46
|
+
continue;
|
|
47
|
+
if (specifier.imported.name !== from)
|
|
48
|
+
continue;
|
|
49
|
+
rename(specifier.imported);
|
|
50
|
+
if (specifier.local.name !== from)
|
|
51
|
+
continue;
|
|
52
|
+
const [variable] = context.sourceCode.getDeclaredVariables(specifier);
|
|
53
|
+
for (const reference of variable?.references ?? []) {
|
|
54
|
+
rename(reference.identifier);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
MemberExpression(node) {
|
|
59
|
+
if (node.computed)
|
|
60
|
+
return;
|
|
61
|
+
if (node.object.type !== 'Identifier')
|
|
62
|
+
return;
|
|
63
|
+
if (!namespaces.has(node.object.name))
|
|
64
|
+
return;
|
|
65
|
+
if (node.property.type !== 'Identifier')
|
|
66
|
+
return;
|
|
67
|
+
if (node.property.name !== from)
|
|
68
|
+
return;
|
|
69
|
+
rename(node.property);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/codemod",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Codemods for Plumeria: migrate a CSS Modules codebase in, export it back out, and rename the styling prop",
|
|
3
|
+
"version": "21.0.0",
|
|
4
|
+
"description": "Codemods for Plumeria: migrate a CSS Modules codebase in, export it back out, and rename the styling prop or an API",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://plumeria.dev",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"get-tsconfig": "^4.14.2",
|
|
38
38
|
"postcss": "8.5.26",
|
|
39
39
|
"postcss-selector-parser": "^7.1.1",
|
|
40
|
-
"zss-engine": "
|
|
40
|
+
"zss-engine": "3.0.1"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public",
|