@plumeria/codemod 18.2.20 → 18.2.22
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 +17 -2
- package/dist/cli.js +11 -3
- package/dist/release.d.ts +1 -0
- package/dist/release.js +127 -3
- package/dist/transforms/adopt-styles.js +55 -20
- package/dist/transforms/from-plumeria.d.ts +22 -2
- package/dist/transforms/from-plumeria.js +237 -46
- package/dist/transforms/release-styles.d.ts +2 -0
- package/dist/transforms/release-styles.js +84 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -194,8 +194,23 @@ under the same condition as the style that has to win:
|
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
Order is only forced where the two classes actually disagree, measured per
|
|
197
|
-
at-rule and per selector.
|
|
198
|
-
`
|
|
197
|
+
at-rule and per selector. Where one property covers another — `padding` against
|
|
198
|
+
`padding-top` — Plumeria settles it by specificity rather than order, so the
|
|
199
|
+
shorthand is written first and the array has no say. Anything left unsettled is
|
|
200
|
+
reported as `composition-order`.
|
|
201
|
+
|
|
202
|
+
A variant picked with a bracket is resolved to the class its key names, whether
|
|
203
|
+
the constant is declared in the same file or imported, and a constant left
|
|
204
|
+
naming nothing is removed with it:
|
|
205
|
+
|
|
206
|
+
```diff
|
|
207
|
+
- const size = 'small';
|
|
208
|
+
- <div classStyle={styles[size]} />
|
|
209
|
+
+ <div className={styles.small} />
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Only a `const` holding a string literal is followed. A key computed at runtime is
|
|
213
|
+
reported as `dynamic-style-access` and its definitions stay in Plumeria.
|
|
199
214
|
|
|
200
215
|
### Global styles
|
|
201
216
|
|
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,7 +310,61 @@ 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 seen = new Set();
|
|
330
|
+
const read = (statement, isExport) => {
|
|
331
|
+
if (seen.has(statement))
|
|
332
|
+
return;
|
|
333
|
+
seen.add(statement);
|
|
334
|
+
if (statement?.type !== 'VariableDeclaration' ||
|
|
335
|
+
statement.kind !== 'const')
|
|
336
|
+
return;
|
|
337
|
+
for (const declaration of statement.declarations) {
|
|
338
|
+
if (declaration.id.type !== 'Identifier' ||
|
|
339
|
+
declaration.init?.type !== 'Literal' ||
|
|
340
|
+
typeof declaration.init.value !== 'string')
|
|
341
|
+
continue;
|
|
342
|
+
if (held.has(declaration.id.name))
|
|
343
|
+
duplicated.add(declaration.id.name);
|
|
344
|
+
held.set(declaration.id.name, declaration.init.value);
|
|
345
|
+
if (isExport)
|
|
346
|
+
exported.set(declaration.id.name, declaration.init.value);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
walk(ast.body, (node) => {
|
|
350
|
+
if (node.type === 'ExportNamedDeclaration')
|
|
351
|
+
read(node.declaration, true);
|
|
352
|
+
else
|
|
353
|
+
read(node, false);
|
|
354
|
+
});
|
|
355
|
+
for (const name of duplicated) {
|
|
356
|
+
held.delete(name);
|
|
357
|
+
exported.delete(name);
|
|
358
|
+
}
|
|
359
|
+
if (held.size > 0)
|
|
360
|
+
stringConstants.set(source, held);
|
|
361
|
+
if (exported.size > 0)
|
|
362
|
+
exportedConstants.set(source, exported);
|
|
363
|
+
}
|
|
364
|
+
const resolvedConstants = new Map();
|
|
313
365
|
const compositions = new Map();
|
|
366
|
+
const foldings = new Map();
|
|
367
|
+
const reached = new Map();
|
|
314
368
|
for (const source of sources) {
|
|
315
369
|
let ast;
|
|
316
370
|
try {
|
|
@@ -324,6 +378,25 @@ function planRelease(targets) {
|
|
|
324
378
|
catch {
|
|
325
379
|
continue;
|
|
326
380
|
}
|
|
381
|
+
const constants = new Map(stringConstants.get(source) ?? []);
|
|
382
|
+
for (const node of ast.body) {
|
|
383
|
+
if (node.type !== 'ImportDeclaration')
|
|
384
|
+
continue;
|
|
385
|
+
const target = (0, resolve_1.resolveSourcePath)(String(node.source.value), source);
|
|
386
|
+
const available = target ? exportedConstants.get(target) : undefined;
|
|
387
|
+
if (!available)
|
|
388
|
+
continue;
|
|
389
|
+
for (const specifier of node.specifiers) {
|
|
390
|
+
if (specifier.type !== 'ImportSpecifier')
|
|
391
|
+
continue;
|
|
392
|
+
const imported = specifier.imported.name ?? specifier.imported.value;
|
|
393
|
+
const value = available.get(imported);
|
|
394
|
+
if (value !== undefined)
|
|
395
|
+
constants.set(specifier.local.name, value);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (constants.size > 0)
|
|
399
|
+
resolvedConstants.set(source, constants);
|
|
327
400
|
const owners = new Map();
|
|
328
401
|
for (const binding of createBindings.get(source) ?? [])
|
|
329
402
|
owners.set(binding, { source, binding });
|
|
@@ -354,6 +427,9 @@ function planRelease(targets) {
|
|
|
354
427
|
const owner = owners.get(node.object.name);
|
|
355
428
|
if (!owner)
|
|
356
429
|
return;
|
|
430
|
+
if (node.property.type === 'Identifier' &&
|
|
431
|
+
constants.has(node.property.name))
|
|
432
|
+
return;
|
|
357
433
|
note(owner.source, [
|
|
358
434
|
{
|
|
359
435
|
line: node.loc.start.line,
|
|
@@ -379,14 +455,43 @@ function planRelease(targets) {
|
|
|
379
455
|
if (owners_.some((candidate) => !candidate))
|
|
380
456
|
return;
|
|
381
457
|
const owner = owners_[0].source;
|
|
382
|
-
if (owners_.some((candidate) => candidate.source !== owner))
|
|
383
|
-
return;
|
|
384
458
|
const named = slots.map((slot) => slot.map((part) => ({
|
|
385
459
|
binding: owners.get(part.object).binding,
|
|
386
460
|
key: part.key,
|
|
387
461
|
})));
|
|
388
462
|
const collapsible = expression.elements.every((element) => memberOf(element)) &&
|
|
389
463
|
named.every((slot) => slot.length === 1);
|
|
464
|
+
if (owners_.some((candidate) => candidate.source !== owner)) {
|
|
465
|
+
if (!collapsible)
|
|
466
|
+
return;
|
|
467
|
+
const parts = slots.map((slot, index) => {
|
|
468
|
+
const held = owners.get(slot[0].object);
|
|
469
|
+
return {
|
|
470
|
+
binding: held.binding,
|
|
471
|
+
key: slot[0].key,
|
|
472
|
+
...(held.source === owner ? {} : { source: held.source }),
|
|
473
|
+
target: (0, exports.releasedPath)(held.source),
|
|
474
|
+
at: index,
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
foldings.set(owner, [
|
|
478
|
+
...(foldings.get(owner) ?? []),
|
|
479
|
+
{
|
|
480
|
+
signature: parts
|
|
481
|
+
.map((part) => `${part.target}#${part.key}`)
|
|
482
|
+
.join('|'),
|
|
483
|
+
parts: parts.map(({ binding, key, source }) => ({
|
|
484
|
+
binding,
|
|
485
|
+
key,
|
|
486
|
+
...(source ? { source } : {}),
|
|
487
|
+
})),
|
|
488
|
+
},
|
|
489
|
+
]);
|
|
490
|
+
for (const part of parts)
|
|
491
|
+
if (part.source)
|
|
492
|
+
reached.set(owner, (reached.get(owner) ?? new Set()).add(part.source));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
390
495
|
compositions.set(owner, [
|
|
391
496
|
...(compositions.get(owner) ?? []),
|
|
392
497
|
{
|
|
@@ -403,7 +508,22 @@ function planRelease(targets) {
|
|
|
403
508
|
for (const source of sources) {
|
|
404
509
|
let module;
|
|
405
510
|
try {
|
|
406
|
-
module = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source), compositions.get(source) ?? []
|
|
511
|
+
module = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source), compositions.get(source) ?? [], {
|
|
512
|
+
target: (0, exports.releasedPath)(source),
|
|
513
|
+
foldings: foldings.get(source) ?? [],
|
|
514
|
+
foreign: Object.fromEntries([...(reached.get(source) ?? [])].map((other) => [
|
|
515
|
+
other,
|
|
516
|
+
{
|
|
517
|
+
text: sourceText.get(other),
|
|
518
|
+
values: {
|
|
519
|
+
...importedPlumeriaValues(other),
|
|
520
|
+
...(staticValues.get(other) ?? {}),
|
|
521
|
+
...(themeValues.get(other) ?? {}),
|
|
522
|
+
...(animationValues.get(other) ?? {}),
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
])),
|
|
526
|
+
});
|
|
407
527
|
}
|
|
408
528
|
catch {
|
|
409
529
|
continue;
|
|
@@ -505,6 +625,10 @@ function planRelease(targets) {
|
|
|
505
625
|
statics,
|
|
506
626
|
values,
|
|
507
627
|
complete: stylesheets.every((sheet) => sheet.reports.length === 0),
|
|
628
|
+
constants: Object.fromEntries([...resolvedConstants].map(([source, held]) => [
|
|
629
|
+
source,
|
|
630
|
+
Object.fromEntries(held),
|
|
631
|
+
])),
|
|
508
632
|
global: globalRules.length > 0
|
|
509
633
|
? {
|
|
510
634
|
target: globalPath(targets),
|
|
@@ -80,6 +80,13 @@ exports.adoptStyles = {
|
|
|
80
80
|
let coreLocal;
|
|
81
81
|
let readsValue = false;
|
|
82
82
|
let several = false;
|
|
83
|
+
const moduleFor = (specifier) => {
|
|
84
|
+
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
85
|
+
if (resolved && modules[resolved])
|
|
86
|
+
return modules[resolved];
|
|
87
|
+
const named = Object.entries(modules).filter(([key]) => key === specifier || path.basename(key) === path.basename(specifier));
|
|
88
|
+
return named.length === 1 ? named[0][1] : undefined;
|
|
89
|
+
};
|
|
83
90
|
const stylingPosition = (node) => {
|
|
84
91
|
let current = node;
|
|
85
92
|
while (current.parent &&
|
|
@@ -119,6 +126,25 @@ exports.adoptStyles = {
|
|
|
119
126
|
const name = element?.name;
|
|
120
127
|
return name?.type === 'JSXIdentifier' && /^[a-z]/.test(String(name.name));
|
|
121
128
|
};
|
|
129
|
+
const renamedText = (element) => {
|
|
130
|
+
const read = styleRead(element);
|
|
131
|
+
const binding = read?.object?.type === 'Identifier'
|
|
132
|
+
? bindings.get(read.object.name)
|
|
133
|
+
: undefined;
|
|
134
|
+
if (!binding)
|
|
135
|
+
return context.sourceCode.getText(element);
|
|
136
|
+
const written = read.computed && read.property.type === 'Literal'
|
|
137
|
+
? String(read.property.value)
|
|
138
|
+
: !read.computed && read.property.type === 'Identifier'
|
|
139
|
+
? read.property.name
|
|
140
|
+
: undefined;
|
|
141
|
+
if (written === undefined)
|
|
142
|
+
return context.sourceCode.getText(element);
|
|
143
|
+
const access = `${binding.local}.${binding.entry.names[written] ?? written}`;
|
|
144
|
+
return element.type === 'LogicalExpression'
|
|
145
|
+
? `${context.sourceCode.getText(element.left)} ${element.operator} ${access}`
|
|
146
|
+
: access;
|
|
147
|
+
};
|
|
122
148
|
const styleRead = (node) => node?.type === 'LogicalExpression'
|
|
123
149
|
? styleRead(node.right)
|
|
124
150
|
: node?.type === 'MemberExpression'
|
|
@@ -152,8 +178,7 @@ exports.adoptStyles = {
|
|
|
152
178
|
coreLocal = specifier.local.name;
|
|
153
179
|
continue;
|
|
154
180
|
}
|
|
155
|
-
|
|
156
|
-
if (!target || !modules[target])
|
|
181
|
+
if (!moduleFor(String(statement.source.value)))
|
|
157
182
|
continue;
|
|
158
183
|
for (const specifier of statement.specifiers)
|
|
159
184
|
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
@@ -210,14 +235,12 @@ exports.adoptStyles = {
|
|
|
210
235
|
},
|
|
211
236
|
ImportDeclaration(node) {
|
|
212
237
|
const specifier = String(node.source.value);
|
|
213
|
-
const
|
|
214
|
-
const named = Object.entries(modules).filter(([key]) => key === specifier ||
|
|
215
|
-
path.basename(key) === path.basename(specifier));
|
|
216
|
-
const entry = (resolved ? modules[resolved] : undefined) ??
|
|
217
|
-
(named.length === 1 ? named[0][1] : undefined);
|
|
238
|
+
const entry = moduleFor(specifier);
|
|
218
239
|
if (!entry) {
|
|
219
|
-
const
|
|
220
|
-
|
|
240
|
+
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
241
|
+
const adopted = Object.values(modules).find((candidate) => candidate.target === resolved ||
|
|
242
|
+
candidate.source === specifier ||
|
|
243
|
+
path.basename(candidate.source) === path.basename(specifier));
|
|
221
244
|
if (!adopted)
|
|
222
245
|
return;
|
|
223
246
|
const already = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
@@ -268,14 +291,6 @@ exports.adoptStyles = {
|
|
|
268
291
|
: undefined;
|
|
269
292
|
if (!binding)
|
|
270
293
|
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
294
|
const written = node.computed && node.property.type === 'Literal'
|
|
280
295
|
? String(node.property.value)
|
|
281
296
|
: !node.computed && node.property.type === 'Identifier'
|
|
@@ -285,6 +300,14 @@ exports.adoptStyles = {
|
|
|
285
300
|
return;
|
|
286
301
|
const key = binding.entry.names[written] ?? written;
|
|
287
302
|
const local = binding.local;
|
|
303
|
+
if (coreLocal && !stylingPosition(node) && !insideUse(node)) {
|
|
304
|
+
context.report({
|
|
305
|
+
node,
|
|
306
|
+
messageId: 'value',
|
|
307
|
+
fix: (fixer) => fixer.replaceText(node, `${coreLocal}.use(${local}.${key})`),
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
288
311
|
if (key === written && local === node.object.name)
|
|
289
312
|
return;
|
|
290
313
|
context.report({
|
|
@@ -383,9 +406,21 @@ exports.adoptStyles = {
|
|
|
383
406
|
if (joined) {
|
|
384
407
|
if (!joined.some((element) => bindings.has(styleRead(element)?.object?.name)))
|
|
385
408
|
return;
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
409
|
+
const styles_ = joined.filter((element) => bindings.has(styleRead(element)?.object?.name));
|
|
410
|
+
const carried = joined.filter((element) => !styles_.includes(element));
|
|
411
|
+
const list = styles_.map(renamedText).join(', ');
|
|
412
|
+
if (carried.length > 0) {
|
|
413
|
+
const kept = carried
|
|
414
|
+
.map((element) => context.sourceCode.getText(element))
|
|
415
|
+
.join(', ');
|
|
416
|
+
context.report({
|
|
417
|
+
node,
|
|
418
|
+
messageId: 'prop',
|
|
419
|
+
data: { prop: styleProp },
|
|
420
|
+
fix: (fixer) => fixer.replaceText(node, `className={[${kept}].filter(Boolean).join(' ')} ${styleProp}={[${list}]}`),
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
389
424
|
if (!intrinsic(node.parent) && coreLocal) {
|
|
390
425
|
context.report({
|
|
391
426
|
node: expression,
|
|
@@ -12,6 +12,7 @@ export interface PlumeriaModule {
|
|
|
12
12
|
functions: Record<string, {
|
|
13
13
|
params: string[];
|
|
14
14
|
variables: string[];
|
|
15
|
+
lengths: boolean[];
|
|
15
16
|
}>;
|
|
16
17
|
aliases: Record<string, Record<string, string>>;
|
|
17
18
|
merges: Record<string, string>;
|
|
@@ -32,7 +33,22 @@ export interface PlumeriaComposition {
|
|
|
32
33
|
line: number;
|
|
33
34
|
column: number;
|
|
34
35
|
}
|
|
35
|
-
export
|
|
36
|
+
export interface PlumeriaFolding {
|
|
37
|
+
signature: string;
|
|
38
|
+
parts: {
|
|
39
|
+
binding: string;
|
|
40
|
+
key: string;
|
|
41
|
+
source?: string;
|
|
42
|
+
}[];
|
|
43
|
+
}
|
|
44
|
+
export interface PlumeriaForeign {
|
|
45
|
+
text: string;
|
|
46
|
+
values: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
export declare const compositionSignature: (parts: {
|
|
49
|
+
target: string;
|
|
50
|
+
className: string;
|
|
51
|
+
}[]) => string;
|
|
36
52
|
export interface ThemeExtraction {
|
|
37
53
|
bindings: Record<string, Record<string, string>>;
|
|
38
54
|
globalCss: string;
|
|
@@ -58,4 +74,8 @@ export declare const __private: {
|
|
|
58
74
|
export declare function extractPlumeriaThemes(source: string): ThemeExtraction;
|
|
59
75
|
export declare function extractPlumeriaStatics(source: string, externalValues?: Record<string, unknown>): StaticExtraction;
|
|
60
76
|
export declare function extractPlumeriaAnimations(source: string, externalValues?: Record<string, unknown>): AnimationExtraction;
|
|
61
|
-
export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown>, compositions?: PlumeriaComposition[]
|
|
77
|
+
export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown>, compositions?: PlumeriaComposition[], options?: {
|
|
78
|
+
target?: string;
|
|
79
|
+
foldings?: PlumeriaFolding[];
|
|
80
|
+
foreign?: Record<string, PlumeriaForeign>;
|
|
81
|
+
}): PlumeriaModule | null;
|
|
@@ -7,8 +7,87 @@ exports.extractPlumeriaAnimations = extractPlumeriaAnimations;
|
|
|
7
7
|
exports.convertPlumeriaModule = convertPlumeriaModule;
|
|
8
8
|
const parser_1 = require("@typescript-eslint/parser");
|
|
9
9
|
const zss_engine_1 = require("zss-engine");
|
|
10
|
-
const
|
|
10
|
+
const UNITLESS = new Set(zss_engine_1.exceptionCamelCase.map((property) => (0, zss_engine_1.camelToKebabCase)(property)));
|
|
11
|
+
const compositionSignature = (parts) => parts.map((part) => `${part.target}#${part.className}`).join('|');
|
|
11
12
|
exports.compositionSignature = compositionSignature;
|
|
13
|
+
const own = (target, classNames) => (0, exports.compositionSignature)(classNames.map((className) => ({ target, className })));
|
|
14
|
+
const DIRECT_SHORTHANDS = {};
|
|
15
|
+
for (const [shorthand, longhands] of Object.entries(zss_engine_1.DIRECT_LONGHANDS)) {
|
|
16
|
+
for (const longhand of longhands) {
|
|
17
|
+
if (!DIRECT_SHORTHANDS[longhand])
|
|
18
|
+
DIRECT_SHORTHANDS[longhand] = [];
|
|
19
|
+
DIRECT_SHORTHANDS[longhand].push(shorthand);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const depths = new Map();
|
|
23
|
+
const depthOf = (property) => {
|
|
24
|
+
const cached = depths.get(property);
|
|
25
|
+
if (cached !== undefined)
|
|
26
|
+
return cached;
|
|
27
|
+
depths.set(property, 0);
|
|
28
|
+
let depth = 0;
|
|
29
|
+
for (const shorthand of DIRECT_SHORTHANDS[property] || [])
|
|
30
|
+
depth = Math.max(depth, depthOf(shorthand) + 1);
|
|
31
|
+
depths.set(property, depth);
|
|
32
|
+
return depth;
|
|
33
|
+
};
|
|
34
|
+
const coverages = new Map();
|
|
35
|
+
const coverageOf = (property) => {
|
|
36
|
+
const cached = coverages.get(property);
|
|
37
|
+
if (cached)
|
|
38
|
+
return cached;
|
|
39
|
+
const longhands = zss_engine_1.DIRECT_LONGHANDS[property];
|
|
40
|
+
const coverage = longhands
|
|
41
|
+
? new Set(longhands.flatMap((longhand) => [...coverageOf(longhand)]))
|
|
42
|
+
: new Set([property]);
|
|
43
|
+
coverages.set(property, coverage);
|
|
44
|
+
return coverage;
|
|
45
|
+
};
|
|
46
|
+
const covers = (outer, inner) => {
|
|
47
|
+
if (outer === inner)
|
|
48
|
+
return false;
|
|
49
|
+
const wide = coverageOf(outer);
|
|
50
|
+
const narrow = coverageOf(inner);
|
|
51
|
+
return wide.size > narrow.size && [...narrow].every((leaf) => wide.has(leaf));
|
|
52
|
+
};
|
|
53
|
+
const overlaps = (first, second) => {
|
|
54
|
+
const left = coverageOf(first);
|
|
55
|
+
return [...coverageOf(second)].some((leaf) => left.has(leaf));
|
|
56
|
+
};
|
|
57
|
+
const byRank = (written) => {
|
|
58
|
+
const outranked = (later, earlier) => (later.held ?? []).some((mine) => (earlier.held ?? []).some((theirs) => later.suffix === earlier.suffix &&
|
|
59
|
+
overlaps(mine.property, theirs.property) &&
|
|
60
|
+
mine.rank > theirs.rank));
|
|
61
|
+
const ordered = [];
|
|
62
|
+
const pending = [...written];
|
|
63
|
+
while (pending.length > 0) {
|
|
64
|
+
const index = pending.findIndex((candidate, position) => !pending.some((other, held) => held !== position && outranked(candidate, other)));
|
|
65
|
+
ordered.push(...pending.splice(index === -1 ? 0 : index, 1));
|
|
66
|
+
}
|
|
67
|
+
return ordered;
|
|
68
|
+
};
|
|
69
|
+
const byImplication = (written) => {
|
|
70
|
+
const ordered = [];
|
|
71
|
+
const pending = [...written];
|
|
72
|
+
while (pending.length > 0) {
|
|
73
|
+
const index = pending.findIndex((candidate, position) => !pending.some((other, held) => held !== position &&
|
|
74
|
+
candidate.condition !== undefined &&
|
|
75
|
+
other.condition !== undefined &&
|
|
76
|
+
candidate.condition !== other.condition &&
|
|
77
|
+
(0, zss_engine_1.impliesCondition)(candidate.condition, other.condition)));
|
|
78
|
+
ordered.push(...pending.splice(index === -1 ? 0 : index, 1));
|
|
79
|
+
}
|
|
80
|
+
return ordered.map((entry) => entry.text);
|
|
81
|
+
};
|
|
82
|
+
const byContainment = (declared) => {
|
|
83
|
+
const ordered = [];
|
|
84
|
+
const pending = [...declared];
|
|
85
|
+
while (pending.length > 0) {
|
|
86
|
+
const index = pending.findIndex((candidate, position) => !pending.some((other, held) => held !== position && covers(other.property, candidate.property)));
|
|
87
|
+
ordered.push(...pending.splice(index === -1 ? 0 : index, 1));
|
|
88
|
+
}
|
|
89
|
+
return ordered.map((entry) => entry.text);
|
|
90
|
+
};
|
|
12
91
|
const toIdent = (value) => value.replace(/[^A-Za-z0-9-]/g, '');
|
|
13
92
|
const capitalize = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
14
93
|
const camelJoin = (names) => names
|
|
@@ -366,7 +445,8 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
|
|
|
366
445
|
reports,
|
|
367
446
|
};
|
|
368
447
|
}
|
|
369
|
-
function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
448
|
+
function convertPlumeriaModule(source, externalValues = {}, compositions = [], options = {}) {
|
|
449
|
+
const target = options.target ?? '';
|
|
370
450
|
const ast = (0, parser_1.parse)(source, {
|
|
371
451
|
sourceType: 'module',
|
|
372
452
|
ecmaFeatures: { jsx: true },
|
|
@@ -435,27 +515,37 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
435
515
|
const members = new Map();
|
|
436
516
|
const footprints = new Map();
|
|
437
517
|
const track = (className) => {
|
|
438
|
-
const
|
|
439
|
-
footprints.set(className,
|
|
440
|
-
return { root: `.${className}`,
|
|
518
|
+
const held = [];
|
|
519
|
+
footprints.set(className, held);
|
|
520
|
+
return { root: `.${className}`, held };
|
|
441
521
|
};
|
|
442
|
-
const
|
|
522
|
+
const relate = (first, second) => {
|
|
443
523
|
const left = footprints.get(first);
|
|
444
524
|
const right = footprints.get(second);
|
|
445
525
|
if (!left || !right)
|
|
446
|
-
return true;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
526
|
+
return { crossing: true };
|
|
527
|
+
let ranked;
|
|
528
|
+
let crossing = false;
|
|
529
|
+
for (const mine of left) {
|
|
530
|
+
for (const theirs of right) {
|
|
531
|
+
if (mine.suffix !== theirs.suffix)
|
|
532
|
+
continue;
|
|
533
|
+
if (!overlaps(mine.property, theirs.property))
|
|
534
|
+
continue;
|
|
535
|
+
if (mine.rank > theirs.rank)
|
|
536
|
+
ranked ??= [second, first];
|
|
537
|
+
else if (theirs.rank > mine.rank)
|
|
538
|
+
ranked ??= [first, second];
|
|
539
|
+
else
|
|
540
|
+
crossing = true;
|
|
541
|
+
}
|
|
454
542
|
}
|
|
455
|
-
return
|
|
543
|
+
return { ranked, crossing };
|
|
456
544
|
};
|
|
545
|
+
const crosses = (first, second) => relate(first, second).crossing;
|
|
457
546
|
const taken = new Set();
|
|
458
547
|
const functions = {};
|
|
548
|
+
const variableUses = new Map();
|
|
459
549
|
const classNameFor = (owner, key) => {
|
|
460
550
|
if (!taken.has(key))
|
|
461
551
|
return key;
|
|
@@ -477,7 +567,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
477
567
|
return;
|
|
478
568
|
}
|
|
479
569
|
const restrictRoot = restrict ? selectorRoot(selector) : 0;
|
|
480
|
-
const
|
|
570
|
+
const declared = [];
|
|
481
571
|
const nested = [];
|
|
482
572
|
const markers = [];
|
|
483
573
|
for (const property of style.properties) {
|
|
@@ -519,15 +609,24 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
519
609
|
.get(bucketOf(wrappers, selector.slice(restrictRoot)))
|
|
520
610
|
?.has(cssProperty))
|
|
521
611
|
continue;
|
|
612
|
+
if (typeof value === 'string')
|
|
613
|
+
for (const [, variable] of value.matchAll(/var\((--[\w-]+)\)/g))
|
|
614
|
+
variableUses.set(variable, (variableUses.get(variable) ?? new Set()).add(cssProperty));
|
|
522
615
|
if (footprint) {
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
616
|
+
const suffix = selector.slice(footprint.root.length);
|
|
617
|
+
footprint.held.push({
|
|
618
|
+
bucket: bucketOf(wrappers, suffix),
|
|
619
|
+
suffix,
|
|
620
|
+
property: cssProperty,
|
|
621
|
+
rank: depthOf(cssProperty) + (wrappers.length > 0 ? 1 : 0),
|
|
622
|
+
});
|
|
527
623
|
}
|
|
528
|
-
|
|
624
|
+
declared.push({
|
|
625
|
+
property: cssProperty,
|
|
626
|
+
text: ` ${cssProperty}: ${(0, zss_engine_1.applyCssValue)(value, cssProperty)};`,
|
|
627
|
+
});
|
|
529
628
|
}
|
|
530
|
-
const pushRule = (target, lines) => {
|
|
629
|
+
const pushRule = (target, lines, held = []) => {
|
|
531
630
|
if (lines.length === 0)
|
|
532
631
|
return;
|
|
533
632
|
let rule = `${target} {\n${lines.join('\n')}\n}`;
|
|
@@ -537,10 +636,18 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
537
636
|
.map((line) => ` ${line}`)
|
|
538
637
|
.join('\n')}\n}`;
|
|
539
638
|
}
|
|
540
|
-
rules.push(
|
|
639
|
+
rules.push({
|
|
640
|
+
text: rule,
|
|
641
|
+
condition: wrappers.length === 1 ? wrappers[0] : undefined,
|
|
642
|
+
suffix: target.slice(target.indexOf('.') + 1).replace(/^[\w-]+/, ''),
|
|
643
|
+
held,
|
|
644
|
+
});
|
|
541
645
|
};
|
|
542
646
|
if (phase !== 'nested')
|
|
543
|
-
pushRule(selector,
|
|
647
|
+
pushRule(selector, byContainment(declared), declared.map((entry) => ({
|
|
648
|
+
property: entry.property,
|
|
649
|
+
rank: depthOf(entry.property) + (wrappers.length > 0 ? 1 : 0),
|
|
650
|
+
})));
|
|
544
651
|
if (phase === 'declarations')
|
|
545
652
|
return;
|
|
546
653
|
for (const marker of markers) {
|
|
@@ -600,10 +707,19 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
600
707
|
params.forEach((parameter, index) => {
|
|
601
708
|
scope.set(parameter, `var(${variables[index]})`);
|
|
602
709
|
});
|
|
603
|
-
functions[className] = { params, variables };
|
|
604
710
|
keys.push(className);
|
|
605
711
|
openGroup(className);
|
|
606
712
|
emit(body, `.${className}`, [], scope, 'both', track(className));
|
|
713
|
+
functions[className] = {
|
|
714
|
+
params,
|
|
715
|
+
variables,
|
|
716
|
+
lengths: variables.map((variable) => {
|
|
717
|
+
const used = variableUses.get(variable);
|
|
718
|
+
return (used !== undefined &&
|
|
719
|
+
used.size > 0 &&
|
|
720
|
+
[...used].every((property) => !UNITLESS.has(property) && !property.startsWith('--')));
|
|
721
|
+
}),
|
|
722
|
+
};
|
|
607
723
|
continue;
|
|
608
724
|
}
|
|
609
725
|
keys.push(className);
|
|
@@ -631,6 +747,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
631
747
|
const sort = () => {
|
|
632
748
|
const edges = new Map();
|
|
633
749
|
const blame = new Map();
|
|
750
|
+
const link = (before, after) => edges.set(before, (edges.get(before) ?? new Set()).add(after));
|
|
634
751
|
for (const entry of resolved) {
|
|
635
752
|
if (inlined.has(entry))
|
|
636
753
|
continue;
|
|
@@ -638,9 +755,14 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
638
755
|
for (let second = first + 1; second < entry.slots.length; second++) {
|
|
639
756
|
for (const before of entry.slots[first]) {
|
|
640
757
|
for (const after of entry.slots[second]) {
|
|
641
|
-
if (before === after
|
|
758
|
+
if (before === after)
|
|
759
|
+
continue;
|
|
760
|
+
const { ranked, crossing } = relate(before, after);
|
|
761
|
+
if (ranked)
|
|
762
|
+
link(ranked[0], ranked[1]);
|
|
763
|
+
if (!crossing)
|
|
642
764
|
continue;
|
|
643
|
-
|
|
765
|
+
link(before, after);
|
|
644
766
|
blame.set(`${before}|${after}`, entry.composition);
|
|
645
767
|
}
|
|
646
768
|
}
|
|
@@ -693,7 +815,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
693
815
|
if (entry.slots.some((slot) => slot.length !== 1))
|
|
694
816
|
continue;
|
|
695
817
|
const names = entry.slots.map((slot) => slot[0]);
|
|
696
|
-
const signature = (
|
|
818
|
+
const signature = own(target, names);
|
|
697
819
|
if (overrides[signature])
|
|
698
820
|
continue;
|
|
699
821
|
const perSlot = {};
|
|
@@ -701,7 +823,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
701
823
|
const losers = names
|
|
702
824
|
.slice(0, slot)
|
|
703
825
|
.filter((earlier) => earlier !== names[slot] &&
|
|
704
|
-
|
|
826
|
+
crosses(earlier, names[slot]) &&
|
|
705
827
|
order.get(earlier) > order.get(names[slot]));
|
|
706
828
|
if (losers.length === 0)
|
|
707
829
|
continue;
|
|
@@ -721,17 +843,15 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
721
843
|
const theirs = footprints.get(loser);
|
|
722
844
|
if (!mine || !theirs)
|
|
723
845
|
continue;
|
|
724
|
-
for (const
|
|
725
|
-
const
|
|
726
|
-
|
|
846
|
+
for (const entry of mine) {
|
|
847
|
+
const crossed = theirs.some((candidate) => candidate.suffix === entry.suffix &&
|
|
848
|
+
candidate.rank === entry.rank &&
|
|
849
|
+
overlaps(entry.property, candidate.property));
|
|
850
|
+
if (!crossed)
|
|
727
851
|
continue;
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const held = disputed.get(bucket) ?? new Set();
|
|
732
|
-
held.add(property);
|
|
733
|
-
disputed.set(bucket, held);
|
|
734
|
-
}
|
|
852
|
+
const held = disputed.get(entry.bucket) ?? new Set();
|
|
853
|
+
held.add(entry.property);
|
|
854
|
+
disputed.set(entry.bucket, held);
|
|
735
855
|
}
|
|
736
856
|
}
|
|
737
857
|
if (disputed.size === 0)
|
|
@@ -747,7 +867,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
747
867
|
if (inlined.has(entry))
|
|
748
868
|
continue;
|
|
749
869
|
const signature = entry.slots.every((slot) => slot.length === 1) &&
|
|
750
|
-
(
|
|
870
|
+
own(target, entry.slots.map((slot) => slot[0]));
|
|
751
871
|
let unresolved;
|
|
752
872
|
for (let first = 0; first < entry.slots.length && !unresolved; first++) {
|
|
753
873
|
for (let second = first + 1; second < entry.slots.length; second++) {
|
|
@@ -755,7 +875,10 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
755
875
|
continue;
|
|
756
876
|
for (const before of entry.slots[first]) {
|
|
757
877
|
for (const after of entry.slots[second]) {
|
|
758
|
-
if (before === after
|
|
878
|
+
if (before === after)
|
|
879
|
+
continue;
|
|
880
|
+
const { ranked, crossing } = relate(before, after);
|
|
881
|
+
if (ranked || !crossing)
|
|
759
882
|
continue;
|
|
760
883
|
if (order.get(before) < order.get(after))
|
|
761
884
|
continue;
|
|
@@ -774,7 +897,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
774
897
|
});
|
|
775
898
|
}
|
|
776
899
|
for (const entry of collapsible) {
|
|
777
|
-
const signature = (
|
|
900
|
+
const signature = own(target, entry.parts.map((part) => part.className));
|
|
778
901
|
if (merges[signature])
|
|
779
902
|
continue;
|
|
780
903
|
const merged = classNameFor(binding, camelJoin(entry.parts.map((part) => part.className)));
|
|
@@ -787,13 +910,80 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
787
910
|
emit(part.value, `.${merged}`, [], values, 'nested');
|
|
788
911
|
}
|
|
789
912
|
else {
|
|
790
|
-
rules.push(
|
|
791
|
-
|
|
792
|
-
|
|
913
|
+
rules.push({
|
|
914
|
+
text: `.${merged} {\n composes: ${entry.parts
|
|
915
|
+
.map((part) => part.className)
|
|
916
|
+
.join(' ')};\n}`,
|
|
917
|
+
});
|
|
793
918
|
}
|
|
794
919
|
keys.push(merged);
|
|
795
920
|
merges[signature] = merged;
|
|
796
921
|
}
|
|
922
|
+
const parsed = new Map();
|
|
923
|
+
const styleOf = (part) => {
|
|
924
|
+
if (!part.source) {
|
|
925
|
+
const held = members.get(`${part.binding}.${part.key}`);
|
|
926
|
+
return held ? { value: held.value, scope: values } : undefined;
|
|
927
|
+
}
|
|
928
|
+
const held = options.foreign?.[part.source];
|
|
929
|
+
if (!held)
|
|
930
|
+
return undefined;
|
|
931
|
+
let tree = parsed.get(part.source);
|
|
932
|
+
if (!tree) {
|
|
933
|
+
try {
|
|
934
|
+
tree = (0, parser_1.parse)(held.text, {
|
|
935
|
+
sourceType: 'module',
|
|
936
|
+
ecmaFeatures: { jsx: true },
|
|
937
|
+
loc: true,
|
|
938
|
+
range: true,
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
catch {
|
|
942
|
+
return undefined;
|
|
943
|
+
}
|
|
944
|
+
parsed.set(part.source, tree);
|
|
945
|
+
}
|
|
946
|
+
for (const raw of tree.body) {
|
|
947
|
+
const statement = declarationOf(raw);
|
|
948
|
+
if (statement?.type !== 'VariableDeclaration')
|
|
949
|
+
continue;
|
|
950
|
+
for (const declaration of statement.declarations) {
|
|
951
|
+
if (declaration.id.name !== part.binding ||
|
|
952
|
+
!isCall(declaration.init, 'create'))
|
|
953
|
+
continue;
|
|
954
|
+
for (const property of declaration.init.arguments[0]?.properties ??
|
|
955
|
+
[]) {
|
|
956
|
+
if (property.type !== 'Property')
|
|
957
|
+
continue;
|
|
958
|
+
const name = property.computed
|
|
959
|
+
? undefined
|
|
960
|
+
: propertyKey(property.key);
|
|
961
|
+
if (name === part.key)
|
|
962
|
+
return {
|
|
963
|
+
value: property.value,
|
|
964
|
+
scope: new Map(Object.entries(held.values)),
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return undefined;
|
|
970
|
+
};
|
|
971
|
+
for (const folding of options.foldings ?? []) {
|
|
972
|
+
if (merges[folding.signature])
|
|
973
|
+
continue;
|
|
974
|
+
const parts = folding.parts.map(styleOf);
|
|
975
|
+
if (parts.some((part) => !part))
|
|
976
|
+
continue;
|
|
977
|
+
const merged = classNameFor(binding, camelJoin(folding.parts.map((part) => part.key)));
|
|
978
|
+
taken.add(merged);
|
|
979
|
+
openGroup(merged);
|
|
980
|
+
for (const part of parts)
|
|
981
|
+
emit(part.value, `.${merged}`, [], part.scope, 'declarations');
|
|
982
|
+
for (const part of parts)
|
|
983
|
+
emit(part.value, `.${merged}`, [], part.scope, 'nested');
|
|
984
|
+
keys.push(merged);
|
|
985
|
+
merges[folding.signature] = merged;
|
|
986
|
+
}
|
|
797
987
|
const emitted = [
|
|
798
988
|
...sorted.ordered,
|
|
799
989
|
...[...sorted.stuck],
|
|
@@ -804,7 +994,8 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
|
|
|
804
994
|
if (seen.has(name))
|
|
805
995
|
return [];
|
|
806
996
|
seen.add(name);
|
|
807
|
-
|
|
997
|
+
const group = groups.find((held) => held.name === name);
|
|
998
|
+
return group ? byImplication(byRank(group.rules)) : [];
|
|
808
999
|
});
|
|
809
1000
|
return {
|
|
810
1001
|
binding,
|
|
@@ -8,6 +8,7 @@ export interface ReleaseModule {
|
|
|
8
8
|
functions?: Record<string, {
|
|
9
9
|
params: string[];
|
|
10
10
|
variables: string[];
|
|
11
|
+
lengths?: boolean[];
|
|
11
12
|
}>;
|
|
12
13
|
merges?: Record<string, string>;
|
|
13
14
|
overrides?: Record<string, Record<number, string>>;
|
|
@@ -19,6 +20,7 @@ export interface ReleaseStylesOptions {
|
|
|
19
20
|
statics?: Record<string, string[]>;
|
|
20
21
|
values?: Record<string, Record<string, unknown>>;
|
|
21
22
|
complete?: boolean;
|
|
23
|
+
constants?: Record<string, Record<string, string>>;
|
|
22
24
|
styleProp?: string;
|
|
23
25
|
active?: boolean;
|
|
24
26
|
}
|
|
@@ -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;
|
|
@@ -360,7 +387,10 @@ exports.releaseStyles = {
|
|
|
360
387
|
className: accessOf(resolved.module, resolved.key),
|
|
361
388
|
declarations: func.variables.map((variable, index) => {
|
|
362
389
|
const argument = sourceCode.getText(value.arguments[index]);
|
|
363
|
-
|
|
390
|
+
const written = func.lengths?.[index]
|
|
391
|
+
? `typeof ${argument} === 'number' ? \`\${${argument}}px\` : ${argument}`
|
|
392
|
+
: argument;
|
|
393
|
+
return `['${variable}' as string]: ${written}`;
|
|
364
394
|
}),
|
|
365
395
|
};
|
|
366
396
|
};
|
|
@@ -385,6 +415,8 @@ exports.releaseStyles = {
|
|
|
385
415
|
}
|
|
386
416
|
if (value.type === 'Literal' && !value.value)
|
|
387
417
|
return sourceCode.getText(value);
|
|
418
|
+
if (value.type === 'Identifier' && styleLocals.has(value.name))
|
|
419
|
+
return sourceCode.getText(value);
|
|
388
420
|
return classText(value);
|
|
389
421
|
};
|
|
390
422
|
const declarationsOf = (value) => {
|
|
@@ -447,6 +479,13 @@ exports.releaseStyles = {
|
|
|
447
479
|
moduleByLocal.set(local, { ...module, binding: local });
|
|
448
480
|
active = true;
|
|
449
481
|
}
|
|
482
|
+
walk(program.body, (node) => {
|
|
483
|
+
if (node.type !== 'VariableDeclarator' ||
|
|
484
|
+
node.id.type !== 'Identifier' ||
|
|
485
|
+
!styleKey(node.init))
|
|
486
|
+
return;
|
|
487
|
+
styleLocals.add(node.id.name);
|
|
488
|
+
});
|
|
450
489
|
const migrating = active || themeBindings.length > 0 || animationBindings.length > 0;
|
|
451
490
|
coreNeeded = keptDefinition || !migrating || survivesCore(program);
|
|
452
491
|
if (coreImport &&
|
|
@@ -585,6 +624,37 @@ exports.releaseStyles = {
|
|
|
585
624
|
fix: (fixer) => fixer.replaceText(node, literal),
|
|
586
625
|
});
|
|
587
626
|
},
|
|
627
|
+
VariableDeclarator(node) {
|
|
628
|
+
if (!active ||
|
|
629
|
+
keptDefinition ||
|
|
630
|
+
node.id.type !== 'Identifier' ||
|
|
631
|
+
node.init?.type !== 'Literal' ||
|
|
632
|
+
constants[node.id.name] !== node.init.value ||
|
|
633
|
+
node.parent.declarations.length !== 1 ||
|
|
634
|
+
node.parent.parent?.type === 'ExportNamedDeclaration')
|
|
635
|
+
return;
|
|
636
|
+
const scope = sourceCode.getScope(node);
|
|
637
|
+
const variable = scope.variables.find((candidate) => candidate.name === node.id.name);
|
|
638
|
+
if (!variable || variable.references.length === 0)
|
|
639
|
+
return;
|
|
640
|
+
const onlyNamesKeys = variable.references.every((reference) => {
|
|
641
|
+
if (reference.init)
|
|
642
|
+
return true;
|
|
643
|
+
const parent = reference.identifier.parent;
|
|
644
|
+
return (parent?.type === 'MemberExpression' &&
|
|
645
|
+
parent.computed &&
|
|
646
|
+
parent.property === reference.identifier &&
|
|
647
|
+
Boolean(moduleOf(parent)));
|
|
648
|
+
});
|
|
649
|
+
if (!onlyNamesKeys)
|
|
650
|
+
return;
|
|
651
|
+
const target = node.parent;
|
|
652
|
+
context.report({
|
|
653
|
+
node: target,
|
|
654
|
+
messageId: 'folded',
|
|
655
|
+
fix: (fixer) => removeStatement(fixer, target),
|
|
656
|
+
});
|
|
657
|
+
},
|
|
588
658
|
TSTypeReference(node) {
|
|
589
659
|
if (!complete || keptDefinition)
|
|
590
660
|
return;
|
|
@@ -629,7 +699,9 @@ exports.releaseStyles = {
|
|
|
629
699
|
const key = node.computed
|
|
630
700
|
? node.property.type === 'Literal'
|
|
631
701
|
? String(node.property.value)
|
|
632
|
-
:
|
|
702
|
+
: node.property.type === 'Identifier'
|
|
703
|
+
? constants[node.property.name]
|
|
704
|
+
: undefined
|
|
633
705
|
: node.property.name;
|
|
634
706
|
const className = key ? alias[key] : undefined;
|
|
635
707
|
if (!className)
|
|
@@ -709,13 +781,14 @@ exports.releaseStyles = {
|
|
|
709
781
|
if (elements.length < 2)
|
|
710
782
|
return undefined;
|
|
711
783
|
const resolved = elements.map(styleKey);
|
|
712
|
-
if (resolved.some((part) => !part))
|
|
713
|
-
return undefined;
|
|
714
|
-
const module = resolved[0].module;
|
|
715
|
-
if (resolved.some((part) => part.module !== module))
|
|
784
|
+
if (resolved.some((part) => !part?.module.target))
|
|
716
785
|
return undefined;
|
|
717
|
-
const
|
|
718
|
-
|
|
786
|
+
const host = resolved[0].module;
|
|
787
|
+
const signature = resolved
|
|
788
|
+
.map((part) => `${part.module.target}#${part.key}`)
|
|
789
|
+
.join('|');
|
|
790
|
+
const name = host.merges?.[signature];
|
|
791
|
+
return name ? accessOf(host, name) : undefined;
|
|
719
792
|
};
|
|
720
793
|
const replaceWithFunctionStyle = (className, declarations) => {
|
|
721
794
|
const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
|