@plumeria/codemod 18.2.20 → 18.2.21
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 +13 -0
- package/dist/cli.js +11 -3
- package/dist/release.d.ts +1 -0
- package/dist/release.js +74 -0
- package/dist/transforms/adopt-styles.js +28 -11
- package/dist/transforms/release-styles.d.ts +1 -0
- package/dist/transforms/release-styles.js +72 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -197,6 +197,19 @@ Order is only forced where the two classes actually disagree, measured per
|
|
|
197
197
|
at-rule and per selector. Anything left unsettled is reported as
|
|
198
198
|
`composition-order`.
|
|
199
199
|
|
|
200
|
+
A variant picked with a bracket is resolved to the class its key names, whether
|
|
201
|
+
the constant is declared in the same file or imported, and a constant left
|
|
202
|
+
naming nothing is removed with it:
|
|
203
|
+
|
|
204
|
+
```diff
|
|
205
|
+
- const size = 'small';
|
|
206
|
+
- <div classStyle={styles[size]} />
|
|
207
|
+
+ <div className={styles.small} />
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Only a `const` holding a string literal is followed. A key computed at runtime is
|
|
211
|
+
reported as `dynamic-style-access` and its definitions stay in Plumeria.
|
|
212
|
+
|
|
200
213
|
### Global styles
|
|
201
214
|
|
|
202
215
|
`css.createTheme`, `css.keyframes`, and `css.viewTransition` produce rules that
|
package/dist/cli.js
CHANGED
|
@@ -331,7 +331,7 @@ async function migrateFromCssModules(options) {
|
|
|
331
331
|
console.log(line);
|
|
332
332
|
return 1;
|
|
333
333
|
}
|
|
334
|
-
function releaseConfig(modules, themes, animations, statics, values, complete) {
|
|
334
|
+
function releaseConfig(modules, themes, animations, statics, values, complete, constants) {
|
|
335
335
|
return [
|
|
336
336
|
{ ignores: IGNORES },
|
|
337
337
|
{
|
|
@@ -347,7 +347,15 @@ function releaseConfig(modules, themes, animations, statics, values, complete) {
|
|
|
347
347
|
rules: {
|
|
348
348
|
'codemod/release-styles': [
|
|
349
349
|
'error',
|
|
350
|
-
{
|
|
350
|
+
{
|
|
351
|
+
modules,
|
|
352
|
+
themes,
|
|
353
|
+
animations,
|
|
354
|
+
statics,
|
|
355
|
+
values,
|
|
356
|
+
complete,
|
|
357
|
+
constants,
|
|
358
|
+
},
|
|
351
359
|
],
|
|
352
360
|
},
|
|
353
361
|
},
|
|
@@ -374,7 +382,7 @@ async function migrateFromPlumeria(options) {
|
|
|
374
382
|
}
|
|
375
383
|
const config = {
|
|
376
384
|
overrideConfigFile: true,
|
|
377
|
-
overrideConfig: releaseConfig(planned.modules, planned.themes, planned.animations, planned.statics, planned.values, planned.complete),
|
|
385
|
+
overrideConfig: releaseConfig(planned.modules, planned.themes, planned.animations, planned.statics, planned.values, planned.complete, planned.constants),
|
|
378
386
|
errorOnUnmatchedPattern: false,
|
|
379
387
|
};
|
|
380
388
|
const preview = await new eslint_1.ESLint(config).lintFiles(targets);
|
package/dist/release.d.ts
CHANGED
package/dist/release.js
CHANGED
|
@@ -310,6 +310,54 @@ function planRelease(targets) {
|
|
|
310
310
|
catch {
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
|
+
const stringConstants = new Map();
|
|
314
|
+
const exportedConstants = new Map();
|
|
315
|
+
for (const source of sources) {
|
|
316
|
+
let ast;
|
|
317
|
+
try {
|
|
318
|
+
ast = (0, parser_1.parse)(sourceText.get(source), {
|
|
319
|
+
sourceType: 'module',
|
|
320
|
+
ecmaFeatures: { jsx: true },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const held = new Map();
|
|
327
|
+
const exported = new Map();
|
|
328
|
+
const duplicated = new Set();
|
|
329
|
+
const read = (statement, isExport) => {
|
|
330
|
+
if (statement?.type !== 'VariableDeclaration' ||
|
|
331
|
+
statement.kind !== 'const')
|
|
332
|
+
return;
|
|
333
|
+
for (const declaration of statement.declarations) {
|
|
334
|
+
if (declaration.id.type !== 'Identifier' ||
|
|
335
|
+
declaration.init?.type !== 'Literal' ||
|
|
336
|
+
typeof declaration.init.value !== 'string')
|
|
337
|
+
continue;
|
|
338
|
+
if (held.has(declaration.id.name))
|
|
339
|
+
duplicated.add(declaration.id.name);
|
|
340
|
+
held.set(declaration.id.name, declaration.init.value);
|
|
341
|
+
if (isExport)
|
|
342
|
+
exported.set(declaration.id.name, declaration.init.value);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
walk(ast.body, (node) => {
|
|
346
|
+
if (node.type === 'ExportNamedDeclaration')
|
|
347
|
+
read(node.declaration, true);
|
|
348
|
+
else
|
|
349
|
+
read(node, false);
|
|
350
|
+
});
|
|
351
|
+
for (const name of duplicated) {
|
|
352
|
+
held.delete(name);
|
|
353
|
+
exported.delete(name);
|
|
354
|
+
}
|
|
355
|
+
if (held.size > 0)
|
|
356
|
+
stringConstants.set(source, held);
|
|
357
|
+
if (exported.size > 0)
|
|
358
|
+
exportedConstants.set(source, exported);
|
|
359
|
+
}
|
|
360
|
+
const resolvedConstants = new Map();
|
|
313
361
|
const compositions = new Map();
|
|
314
362
|
for (const source of sources) {
|
|
315
363
|
let ast;
|
|
@@ -324,6 +372,25 @@ function planRelease(targets) {
|
|
|
324
372
|
catch {
|
|
325
373
|
continue;
|
|
326
374
|
}
|
|
375
|
+
const constants = new Map(stringConstants.get(source) ?? []);
|
|
376
|
+
for (const node of ast.body) {
|
|
377
|
+
if (node.type !== 'ImportDeclaration')
|
|
378
|
+
continue;
|
|
379
|
+
const target = (0, resolve_1.resolveSourcePath)(String(node.source.value), source);
|
|
380
|
+
const available = target ? exportedConstants.get(target) : undefined;
|
|
381
|
+
if (!available)
|
|
382
|
+
continue;
|
|
383
|
+
for (const specifier of node.specifiers) {
|
|
384
|
+
if (specifier.type !== 'ImportSpecifier')
|
|
385
|
+
continue;
|
|
386
|
+
const imported = specifier.imported.name ?? specifier.imported.value;
|
|
387
|
+
const value = available.get(imported);
|
|
388
|
+
if (value !== undefined)
|
|
389
|
+
constants.set(specifier.local.name, value);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (constants.size > 0)
|
|
393
|
+
resolvedConstants.set(source, constants);
|
|
327
394
|
const owners = new Map();
|
|
328
395
|
for (const binding of createBindings.get(source) ?? [])
|
|
329
396
|
owners.set(binding, { source, binding });
|
|
@@ -354,6 +421,9 @@ function planRelease(targets) {
|
|
|
354
421
|
const owner = owners.get(node.object.name);
|
|
355
422
|
if (!owner)
|
|
356
423
|
return;
|
|
424
|
+
if (node.property.type === 'Identifier' &&
|
|
425
|
+
constants.has(node.property.name))
|
|
426
|
+
return;
|
|
357
427
|
note(owner.source, [
|
|
358
428
|
{
|
|
359
429
|
line: node.loc.start.line,
|
|
@@ -505,6 +575,10 @@ function planRelease(targets) {
|
|
|
505
575
|
statics,
|
|
506
576
|
values,
|
|
507
577
|
complete: stylesheets.every((sheet) => sheet.reports.length === 0),
|
|
578
|
+
constants: Object.fromEntries([...resolvedConstants].map(([source, held]) => [
|
|
579
|
+
source,
|
|
580
|
+
Object.fromEntries(held),
|
|
581
|
+
])),
|
|
508
582
|
global: globalRules.length > 0
|
|
509
583
|
? {
|
|
510
584
|
target: globalPath(targets),
|
|
@@ -119,6 +119,25 @@ exports.adoptStyles = {
|
|
|
119
119
|
const name = element?.name;
|
|
120
120
|
return name?.type === 'JSXIdentifier' && /^[a-z]/.test(String(name.name));
|
|
121
121
|
};
|
|
122
|
+
const renamedText = (element) => {
|
|
123
|
+
const read = styleRead(element);
|
|
124
|
+
const binding = read?.object?.type === 'Identifier'
|
|
125
|
+
? bindings.get(read.object.name)
|
|
126
|
+
: undefined;
|
|
127
|
+
if (!binding)
|
|
128
|
+
return context.sourceCode.getText(element);
|
|
129
|
+
const written = read.computed && read.property.type === 'Literal'
|
|
130
|
+
? String(read.property.value)
|
|
131
|
+
: !read.computed && read.property.type === 'Identifier'
|
|
132
|
+
? read.property.name
|
|
133
|
+
: undefined;
|
|
134
|
+
if (written === undefined)
|
|
135
|
+
return context.sourceCode.getText(element);
|
|
136
|
+
const access = `${binding.local}.${binding.entry.names[written] ?? written}`;
|
|
137
|
+
return element.type === 'LogicalExpression'
|
|
138
|
+
? `${context.sourceCode.getText(element.left)} ${element.operator} ${access}`
|
|
139
|
+
: access;
|
|
140
|
+
};
|
|
122
141
|
const styleRead = (node) => node?.type === 'LogicalExpression'
|
|
123
142
|
? styleRead(node.right)
|
|
124
143
|
: node?.type === 'MemberExpression'
|
|
@@ -268,14 +287,6 @@ exports.adoptStyles = {
|
|
|
268
287
|
: undefined;
|
|
269
288
|
if (!binding)
|
|
270
289
|
return;
|
|
271
|
-
if (coreLocal && !stylingPosition(node) && !insideUse(node)) {
|
|
272
|
-
context.report({
|
|
273
|
-
node,
|
|
274
|
-
messageId: 'value',
|
|
275
|
-
fix: (fixer) => fixer.replaceText(node, `${coreLocal}.use(${context.sourceCode.getText(node)})`),
|
|
276
|
-
});
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
290
|
const written = node.computed && node.property.type === 'Literal'
|
|
280
291
|
? String(node.property.value)
|
|
281
292
|
: !node.computed && node.property.type === 'Identifier'
|
|
@@ -285,6 +296,14 @@ exports.adoptStyles = {
|
|
|
285
296
|
return;
|
|
286
297
|
const key = binding.entry.names[written] ?? written;
|
|
287
298
|
const local = binding.local;
|
|
299
|
+
if (coreLocal && !stylingPosition(node) && !insideUse(node)) {
|
|
300
|
+
context.report({
|
|
301
|
+
node,
|
|
302
|
+
messageId: 'value',
|
|
303
|
+
fix: (fixer) => fixer.replaceText(node, `${coreLocal}.use(${local}.${key})`),
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
288
307
|
if (key === written && local === node.object.name)
|
|
289
308
|
return;
|
|
290
309
|
context.report({
|
|
@@ -383,9 +402,7 @@ exports.adoptStyles = {
|
|
|
383
402
|
if (joined) {
|
|
384
403
|
if (!joined.some((element) => bindings.has(styleRead(element)?.object?.name)))
|
|
385
404
|
return;
|
|
386
|
-
const list = joined
|
|
387
|
-
.map((element) => context.sourceCode.getText(element))
|
|
388
|
-
.join(', ');
|
|
405
|
+
const list = joined.map(renamedText).join(', ');
|
|
389
406
|
if (!intrinsic(node.parent) && coreLocal) {
|
|
390
407
|
context.report({
|
|
391
408
|
node: expression,
|
|
@@ -56,6 +56,23 @@ const isCreate = (node) => {
|
|
|
56
56
|
return path?.length === 2 && path[1] === 'create';
|
|
57
57
|
};
|
|
58
58
|
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
59
|
+
const walk = (node, visit) => {
|
|
60
|
+
if (!node || typeof node !== 'object')
|
|
61
|
+
return;
|
|
62
|
+
if (Array.isArray(node)) {
|
|
63
|
+
for (const child of node)
|
|
64
|
+
walk(child, visit);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (typeof node.type !== 'string')
|
|
68
|
+
return;
|
|
69
|
+
visit(node);
|
|
70
|
+
for (const key of Object.keys(node)) {
|
|
71
|
+
if (key === 'parent' || key === 'loc' || key === 'range')
|
|
72
|
+
continue;
|
|
73
|
+
walk(node[key], visit);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
59
76
|
const needsFilter = (node) => node.type === 'LogicalExpression' ||
|
|
60
77
|
node.type === 'ConditionalExpression' ||
|
|
61
78
|
node.type === 'SpreadElement';
|
|
@@ -82,6 +99,7 @@ exports.releaseStyles = {
|
|
|
82
99
|
use: 'Read the class name straight from the CSS Module.',
|
|
83
100
|
token: 'Inline the value this token resolved to.',
|
|
84
101
|
styleType: 'A released style is a class name.',
|
|
102
|
+
folded: 'This only named a style key, which the export has resolved.',
|
|
85
103
|
functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
|
|
86
104
|
unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
|
|
87
105
|
},
|
|
@@ -95,6 +113,7 @@ exports.releaseStyles = {
|
|
|
95
113
|
statics: { type: 'object' },
|
|
96
114
|
values: { type: 'object' },
|
|
97
115
|
complete: { type: 'boolean' },
|
|
116
|
+
constants: { type: 'object' },
|
|
98
117
|
styleProp: { type: 'string' },
|
|
99
118
|
active: { type: 'boolean' },
|
|
100
119
|
},
|
|
@@ -117,6 +136,7 @@ exports.releaseStyles = {
|
|
|
117
136
|
const styleProp = raw.styleProp ?? 'classStyle';
|
|
118
137
|
const sourceCode = context.sourceCode;
|
|
119
138
|
const moduleByLocal = new Map();
|
|
139
|
+
const styleLocals = new Set();
|
|
120
140
|
let active = Boolean(entry || raw.active || raw.complete);
|
|
121
141
|
let coreImport;
|
|
122
142
|
let coreLocal;
|
|
@@ -167,10 +187,14 @@ exports.releaseStyles = {
|
|
|
167
187
|
bindings.includes(candidate.imported.name ?? candidate.imported.value)));
|
|
168
188
|
};
|
|
169
189
|
const removeStatement = (fixer, node) => {
|
|
170
|
-
const body =
|
|
190
|
+
const body = (Array.isArray(node.parent?.body)
|
|
191
|
+
? node.parent.body
|
|
192
|
+
: sourceCode.ast.body);
|
|
171
193
|
const next = body.find((statement) => statement.range[0] >= node.range[1]);
|
|
172
194
|
if (!next)
|
|
173
|
-
return
|
|
195
|
+
return body === sourceCode.ast.body
|
|
196
|
+
? fixer.removeRange([node.range[0], sourceCode.text.length])
|
|
197
|
+
: fixer.remove(node);
|
|
174
198
|
if (removesStatement(next))
|
|
175
199
|
return fixer.remove(node);
|
|
176
200
|
const anchor = importAnchor();
|
|
@@ -279,6 +303,7 @@ exports.releaseStyles = {
|
|
|
279
303
|
const moduleOf = (node) => node?.type === 'MemberExpression' && node.object.type === 'Identifier'
|
|
280
304
|
? moduleByLocal.get(node.object.name)
|
|
281
305
|
: undefined;
|
|
306
|
+
const constants = raw.constants?.[context.filename] ?? {};
|
|
282
307
|
const styleKey = (node) => {
|
|
283
308
|
const module = moduleOf(node);
|
|
284
309
|
if (!module)
|
|
@@ -286,7 +311,9 @@ exports.releaseStyles = {
|
|
|
286
311
|
const written = node.computed
|
|
287
312
|
? node.property.type === 'Literal'
|
|
288
313
|
? String(node.property.value)
|
|
289
|
-
:
|
|
314
|
+
: node.property.type === 'Identifier'
|
|
315
|
+
? constants[node.property.name]
|
|
316
|
+
: undefined
|
|
290
317
|
: node.property.name;
|
|
291
318
|
if (written === undefined)
|
|
292
319
|
return undefined;
|
|
@@ -385,6 +412,8 @@ exports.releaseStyles = {
|
|
|
385
412
|
}
|
|
386
413
|
if (value.type === 'Literal' && !value.value)
|
|
387
414
|
return sourceCode.getText(value);
|
|
415
|
+
if (value.type === 'Identifier' && styleLocals.has(value.name))
|
|
416
|
+
return sourceCode.getText(value);
|
|
388
417
|
return classText(value);
|
|
389
418
|
};
|
|
390
419
|
const declarationsOf = (value) => {
|
|
@@ -447,6 +476,13 @@ exports.releaseStyles = {
|
|
|
447
476
|
moduleByLocal.set(local, { ...module, binding: local });
|
|
448
477
|
active = true;
|
|
449
478
|
}
|
|
479
|
+
walk(program.body, (node) => {
|
|
480
|
+
if (node.type !== 'VariableDeclarator' ||
|
|
481
|
+
node.id.type !== 'Identifier' ||
|
|
482
|
+
!styleKey(node.init))
|
|
483
|
+
return;
|
|
484
|
+
styleLocals.add(node.id.name);
|
|
485
|
+
});
|
|
450
486
|
const migrating = active || themeBindings.length > 0 || animationBindings.length > 0;
|
|
451
487
|
coreNeeded = keptDefinition || !migrating || survivesCore(program);
|
|
452
488
|
if (coreImport &&
|
|
@@ -585,6 +621,36 @@ exports.releaseStyles = {
|
|
|
585
621
|
fix: (fixer) => fixer.replaceText(node, literal),
|
|
586
622
|
});
|
|
587
623
|
},
|
|
624
|
+
VariableDeclarator(node) {
|
|
625
|
+
if (!active ||
|
|
626
|
+
keptDefinition ||
|
|
627
|
+
node.id.type !== 'Identifier' ||
|
|
628
|
+
node.init?.type !== 'Literal' ||
|
|
629
|
+
constants[node.id.name] !== node.init.value ||
|
|
630
|
+
node.parent.declarations.length !== 1)
|
|
631
|
+
return;
|
|
632
|
+
const scope = sourceCode.getScope(node);
|
|
633
|
+
const variable = scope.variables.find((candidate) => candidate.name === node.id.name);
|
|
634
|
+
if (!variable || variable.references.length === 0)
|
|
635
|
+
return;
|
|
636
|
+
const onlyNamesKeys = variable.references.every((reference) => {
|
|
637
|
+
if (reference.init)
|
|
638
|
+
return true;
|
|
639
|
+
const parent = reference.identifier.parent;
|
|
640
|
+
return (parent?.type === 'MemberExpression' &&
|
|
641
|
+
parent.computed &&
|
|
642
|
+
parent.property === reference.identifier &&
|
|
643
|
+
Boolean(moduleOf(parent)));
|
|
644
|
+
});
|
|
645
|
+
if (!onlyNamesKeys)
|
|
646
|
+
return;
|
|
647
|
+
const target = node.parent;
|
|
648
|
+
context.report({
|
|
649
|
+
node: target,
|
|
650
|
+
messageId: 'folded',
|
|
651
|
+
fix: (fixer) => removeStatement(fixer, target),
|
|
652
|
+
});
|
|
653
|
+
},
|
|
588
654
|
TSTypeReference(node) {
|
|
589
655
|
if (!complete || keptDefinition)
|
|
590
656
|
return;
|
|
@@ -629,7 +695,9 @@ exports.releaseStyles = {
|
|
|
629
695
|
const key = node.computed
|
|
630
696
|
? node.property.type === 'Literal'
|
|
631
697
|
? String(node.property.value)
|
|
632
|
-
:
|
|
698
|
+
: node.property.type === 'Identifier'
|
|
699
|
+
? constants[node.property.name]
|
|
700
|
+
: undefined
|
|
633
701
|
: node.property.name;
|
|
634
702
|
const className = key ? alias[key] : undefined;
|
|
635
703
|
if (!className)
|