@plumeria/codemod 18.2.19 → 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 +61 -9
- package/dist/cli.js +13 -3
- package/dist/migrate.js +3 -1
- package/dist/release.d.ts +3 -0
- package/dist/release.js +369 -38
- package/dist/transforms/adopt-styles.d.ts +2 -0
- package/dist/transforms/adopt-styles.js +320 -10
- package/dist/transforms/css-modules.d.ts +5 -1
- package/dist/transforms/css-modules.js +59 -5
- package/dist/transforms/from-plumeria.d.ts +17 -1
- package/dist/transforms/from-plumeria.js +276 -10
- package/dist/transforms/release-styles.d.ts +5 -0
- package/dist/transforms/release-styles.js +535 -67
- package/package.json +1 -1
|
@@ -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';
|
|
@@ -79,6 +96,10 @@ exports.releaseStyles = {
|
|
|
79
96
|
core: 'Remove the Plumeria import after exporting the styles.',
|
|
80
97
|
prop: 'Use className after exporting this style to CSS Modules.',
|
|
81
98
|
composition: 'Join the composed CSS Module class names.',
|
|
99
|
+
use: 'Read the class name straight from the CSS Module.',
|
|
100
|
+
token: 'Inline the value this token resolved to.',
|
|
101
|
+
styleType: 'A released style is a class name.',
|
|
102
|
+
folded: 'This only named a style key, which the export has resolved.',
|
|
82
103
|
functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
|
|
83
104
|
unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
|
|
84
105
|
},
|
|
@@ -90,6 +111,9 @@ exports.releaseStyles = {
|
|
|
90
111
|
themes: { type: 'object' },
|
|
91
112
|
animations: { type: 'object' },
|
|
92
113
|
statics: { type: 'object' },
|
|
114
|
+
values: { type: 'object' },
|
|
115
|
+
complete: { type: 'boolean' },
|
|
116
|
+
constants: { type: 'object' },
|
|
93
117
|
styleProp: { type: 'string' },
|
|
94
118
|
active: { type: 'boolean' },
|
|
95
119
|
},
|
|
@@ -100,13 +124,90 @@ exports.releaseStyles = {
|
|
|
100
124
|
create(context) {
|
|
101
125
|
const raw = (context.options[0] ?? {});
|
|
102
126
|
const declared = raw.modules?.[context.filename];
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
127
|
+
const keptDefinition = declared?.definitionOnly === true;
|
|
128
|
+
const complete = raw.complete === true;
|
|
129
|
+
const entry = keptDefinition ? undefined : declared;
|
|
130
|
+
const themeBindings = keptDefinition
|
|
131
|
+
? []
|
|
132
|
+
: (raw.themes?.[context.filename] ?? []);
|
|
133
|
+
const animationBindings = keptDefinition
|
|
134
|
+
? []
|
|
135
|
+
: (raw.animations?.[context.filename] ?? []);
|
|
106
136
|
const styleProp = raw.styleProp ?? 'classStyle';
|
|
107
137
|
const sourceCode = context.sourceCode;
|
|
108
|
-
|
|
109
|
-
|
|
138
|
+
const moduleByLocal = new Map();
|
|
139
|
+
const styleLocals = new Set();
|
|
140
|
+
let active = Boolean(entry || raw.active || raw.complete);
|
|
141
|
+
let coreImport;
|
|
142
|
+
let coreLocal;
|
|
143
|
+
let coreNeeded = true;
|
|
144
|
+
const declarationOf = (statement) => statement.type === 'ExportNamedDeclaration'
|
|
145
|
+
? statement.declaration
|
|
146
|
+
: statement;
|
|
147
|
+
const removesDeclaration = (statement) => {
|
|
148
|
+
const declaration = declarationOf(statement);
|
|
149
|
+
if (declaration?.type !== 'VariableDeclaration' ||
|
|
150
|
+
declaration.declarations.length !== 1)
|
|
151
|
+
return false;
|
|
152
|
+
const only = declaration.declarations[0];
|
|
153
|
+
if (only.id.type !== 'Identifier')
|
|
154
|
+
return false;
|
|
155
|
+
const called = only.init?.type === 'CallExpression'
|
|
156
|
+
? memberPath(only.init.callee)
|
|
157
|
+
: undefined;
|
|
158
|
+
if (called?.length !== 2)
|
|
159
|
+
return false;
|
|
160
|
+
if (called[1] === 'createStatic')
|
|
161
|
+
return Boolean(entry);
|
|
162
|
+
if (called[1] === 'createTheme')
|
|
163
|
+
return themeBindings.includes(only.id.name);
|
|
164
|
+
if (called[1] === 'keyframes' || called[1] === 'viewTransition')
|
|
165
|
+
return animationBindings.includes(only.id.name);
|
|
166
|
+
if (called[1] === 'create')
|
|
167
|
+
return Boolean(entry &&
|
|
168
|
+
(only.id.name === entry.binding || entry.aliases?.[only.id.name]));
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
const removesStatement = (statement) => {
|
|
172
|
+
if (keptDefinition)
|
|
173
|
+
return false;
|
|
174
|
+
if (statement.type !== 'ImportDeclaration')
|
|
175
|
+
return removesDeclaration(statement);
|
|
176
|
+
const specifier = String(statement.source.value);
|
|
177
|
+
if (specifier === '@plumeria/core')
|
|
178
|
+
return !coreNeeded;
|
|
179
|
+
const bindings = [
|
|
180
|
+
...(importedAnimationBindings(specifier) ?? []),
|
|
181
|
+
...(importedThemeBindings(specifier) ?? []),
|
|
182
|
+
...(importedStaticBindings(specifier) ?? []),
|
|
183
|
+
];
|
|
184
|
+
return (bindings.length > 0 &&
|
|
185
|
+
statement.specifiers.length > 0 &&
|
|
186
|
+
statement.specifiers.every((candidate) => candidate.type === 'ImportSpecifier' &&
|
|
187
|
+
bindings.includes(candidate.imported.name ?? candidate.imported.value)));
|
|
188
|
+
};
|
|
189
|
+
const removeStatement = (fixer, node) => {
|
|
190
|
+
const body = (Array.isArray(node.parent?.body)
|
|
191
|
+
? node.parent.body
|
|
192
|
+
: sourceCode.ast.body);
|
|
193
|
+
const next = body.find((statement) => statement.range[0] >= node.range[1]);
|
|
194
|
+
if (!next)
|
|
195
|
+
return body === sourceCode.ast.body
|
|
196
|
+
? fixer.removeRange([node.range[0], sourceCode.text.length])
|
|
197
|
+
: fixer.remove(node);
|
|
198
|
+
if (removesStatement(next))
|
|
199
|
+
return fixer.remove(node);
|
|
200
|
+
const anchor = importAnchor();
|
|
201
|
+
if (node.range[1] <= anchor && next.range[0] >= anchor)
|
|
202
|
+
return fixer.remove(node);
|
|
203
|
+
return fixer.removeRange([node.range[0], next.range[0]]);
|
|
204
|
+
};
|
|
205
|
+
const importAnchor = () => {
|
|
206
|
+
const imports = sourceCode.ast.body.filter((statement) => statement.type === 'ImportDeclaration');
|
|
207
|
+
return imports.length > 0
|
|
208
|
+
? Math.min(imports[imports.length - 1].range[1] + 1, sourceCode.text.length)
|
|
209
|
+
: 0;
|
|
210
|
+
};
|
|
110
211
|
const specifierFor = (module) => {
|
|
111
212
|
if (!module.target)
|
|
112
213
|
return module.source;
|
|
@@ -133,10 +234,23 @@ exports.releaseStyles = {
|
|
|
133
234
|
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
134
235
|
return resolved ? table?.[resolved] : undefined;
|
|
135
236
|
};
|
|
136
|
-
const
|
|
237
|
+
const byTarget = new Map(Object.values(raw.modules ?? {}).flatMap((module) => module.target ? [[module.target, module]] : []));
|
|
238
|
+
const importedReleasedModule = (specifier) => {
|
|
239
|
+
const declared = importedFrom(specifier, raw.modules);
|
|
240
|
+
if (declared)
|
|
241
|
+
return declared;
|
|
242
|
+
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
243
|
+
return resolved ? byTarget.get(resolved) : undefined;
|
|
244
|
+
};
|
|
137
245
|
const importedThemeBindings = (specifier) => importedFrom(specifier, raw.themes);
|
|
138
246
|
const importedAnimationBindings = (specifier) => importedFrom(specifier, raw.animations);
|
|
139
247
|
const importedStaticBindings = (specifier) => importedFrom(specifier, raw.statics);
|
|
248
|
+
const inlineByLocal = new Map();
|
|
249
|
+
const literalOf = (value) => typeof value === 'string'
|
|
250
|
+
? `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
|
|
251
|
+
: typeof value === 'number'
|
|
252
|
+
? String(value)
|
|
253
|
+
: undefined;
|
|
140
254
|
const releasedRange = () => {
|
|
141
255
|
if (!entry)
|
|
142
256
|
return undefined;
|
|
@@ -181,19 +295,216 @@ exports.releaseStyles = {
|
|
|
181
295
|
node,
|
|
182
296
|
messageId,
|
|
183
297
|
fix: (fixer) => retained.length === 0
|
|
184
|
-
? fixer
|
|
298
|
+
? removeStatement(fixer, node)
|
|
185
299
|
: fixer.replaceText(node, importOf(retained, node.source)),
|
|
186
300
|
});
|
|
187
301
|
return true;
|
|
188
302
|
};
|
|
303
|
+
const moduleOf = (node) => node?.type === 'MemberExpression' && node.object.type === 'Identifier'
|
|
304
|
+
? moduleByLocal.get(node.object.name)
|
|
305
|
+
: undefined;
|
|
306
|
+
const constants = raw.constants?.[context.filename] ?? {};
|
|
307
|
+
const styleKey = (node) => {
|
|
308
|
+
const module = moduleOf(node);
|
|
309
|
+
if (!module)
|
|
310
|
+
return undefined;
|
|
311
|
+
const written = node.computed
|
|
312
|
+
? node.property.type === 'Literal'
|
|
313
|
+
? String(node.property.value)
|
|
314
|
+
: node.property.type === 'Identifier'
|
|
315
|
+
? constants[node.property.name]
|
|
316
|
+
: undefined
|
|
317
|
+
: node.property.name;
|
|
318
|
+
if (written === undefined)
|
|
319
|
+
return undefined;
|
|
320
|
+
const alias = module.aliases?.[node.object.name];
|
|
321
|
+
const key = alias ? alias[written] : written;
|
|
322
|
+
return key === undefined ? undefined : { module, key };
|
|
323
|
+
};
|
|
324
|
+
const accessOf = (module, key) => `${module.binding}${IDENTIFIER.test(key) ? `.${key}` : `['${key}']`}`;
|
|
325
|
+
const classText = (node) => {
|
|
326
|
+
const resolved = styleKey(node);
|
|
327
|
+
return resolved ? accessOf(resolved.module, resolved.key) : undefined;
|
|
328
|
+
};
|
|
329
|
+
const releasedUse = (node, parent) => parent?.type === 'CallExpression' &&
|
|
330
|
+
parent.callee === node &&
|
|
331
|
+
parent.arguments.length > 0 &&
|
|
332
|
+
parent.arguments.every((argument) => classText(argument) !== undefined);
|
|
333
|
+
const removesCall = (method) => method === 'create' || method === 'createStatic'
|
|
334
|
+
? Boolean(entry)
|
|
335
|
+
: method === 'createTheme'
|
|
336
|
+
? themeBindings.length > 0
|
|
337
|
+
: method === 'keyframes' || method === 'viewTransition'
|
|
338
|
+
? animationBindings.length > 0
|
|
339
|
+
: false;
|
|
340
|
+
const survivesCore = (program) => {
|
|
341
|
+
if (!coreLocal)
|
|
342
|
+
return false;
|
|
343
|
+
let survives = false;
|
|
344
|
+
const visit = (node, parent) => {
|
|
345
|
+
if (survives || !node || typeof node !== 'object')
|
|
346
|
+
return;
|
|
347
|
+
if (Array.isArray(node)) {
|
|
348
|
+
for (const child of node)
|
|
349
|
+
visit(child, parent);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (typeof node.type !== 'string')
|
|
353
|
+
return;
|
|
354
|
+
if (node.type === 'MemberExpression' &&
|
|
355
|
+
node.object.type === 'Identifier' &&
|
|
356
|
+
node.object.name === coreLocal) {
|
|
357
|
+
const method = node.computed
|
|
358
|
+
? node.property.type === 'Literal'
|
|
359
|
+
? String(node.property.value)
|
|
360
|
+
: ''
|
|
361
|
+
: node.property.name;
|
|
362
|
+
survives =
|
|
363
|
+
method === 'use'
|
|
364
|
+
? !releasedUse(node, parent)
|
|
365
|
+
: !removesCall(method);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
for (const key of Object.keys(node)) {
|
|
369
|
+
if (key === 'parent' || key === 'loc' || key === 'range')
|
|
370
|
+
continue;
|
|
371
|
+
visit(node[key], node);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
visit(program.body, undefined);
|
|
375
|
+
return survives;
|
|
376
|
+
};
|
|
377
|
+
const functionStyle = (value) => {
|
|
378
|
+
if (value?.type !== 'CallExpression')
|
|
379
|
+
return undefined;
|
|
380
|
+
const resolved = styleKey(value.callee);
|
|
381
|
+
if (!resolved)
|
|
382
|
+
return undefined;
|
|
383
|
+
const func = resolved.module.functions?.[resolved.key];
|
|
384
|
+
if (!func || func.variables.length !== value.arguments.length)
|
|
385
|
+
return undefined;
|
|
386
|
+
return {
|
|
387
|
+
className: accessOf(resolved.module, resolved.key),
|
|
388
|
+
declarations: func.variables.map((variable, index) => {
|
|
389
|
+
const argument = sourceCode.getText(value.arguments[index]);
|
|
390
|
+
return `['${variable}' as string]: ${argument}`;
|
|
391
|
+
}),
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
const resolveText = (value) => {
|
|
395
|
+
if (!value)
|
|
396
|
+
return undefined;
|
|
397
|
+
const called = functionStyle(value);
|
|
398
|
+
if (called)
|
|
399
|
+
return called.className;
|
|
400
|
+
if (value.type === 'LogicalExpression') {
|
|
401
|
+
const right = resolveText(value.right);
|
|
402
|
+
return right === undefined
|
|
403
|
+
? undefined
|
|
404
|
+
: `${sourceCode.getText(value.left)} ${value.operator} ${right}`;
|
|
405
|
+
}
|
|
406
|
+
if (value.type === 'ConditionalExpression') {
|
|
407
|
+
const consequent = resolveText(value.consequent);
|
|
408
|
+
const alternate = resolveText(value.alternate);
|
|
409
|
+
return consequent === undefined || alternate === undefined
|
|
410
|
+
? undefined
|
|
411
|
+
: `${sourceCode.getText(value.test)} ? ${consequent} : ${alternate}`;
|
|
412
|
+
}
|
|
413
|
+
if (value.type === 'Literal' && !value.value)
|
|
414
|
+
return sourceCode.getText(value);
|
|
415
|
+
if (value.type === 'Identifier' && styleLocals.has(value.name))
|
|
416
|
+
return sourceCode.getText(value);
|
|
417
|
+
return classText(value);
|
|
418
|
+
};
|
|
419
|
+
const declarationsOf = (value) => {
|
|
420
|
+
if (!value)
|
|
421
|
+
return [];
|
|
422
|
+
const called = functionStyle(value);
|
|
423
|
+
if (called)
|
|
424
|
+
return called.declarations;
|
|
425
|
+
if (value.type === 'LogicalExpression')
|
|
426
|
+
return declarationsOf(value.right);
|
|
427
|
+
if (value.type === 'ConditionalExpression')
|
|
428
|
+
return [
|
|
429
|
+
...declarationsOf(value.consequent),
|
|
430
|
+
...declarationsOf(value.alternate),
|
|
431
|
+
];
|
|
432
|
+
return [];
|
|
433
|
+
};
|
|
189
434
|
return {
|
|
435
|
+
Program(program) {
|
|
436
|
+
if (entry) {
|
|
437
|
+
moduleByLocal.set(entry.binding, entry);
|
|
438
|
+
for (const owner of Object.keys(entry.aliases ?? {}))
|
|
439
|
+
moduleByLocal.set(owner, entry);
|
|
440
|
+
}
|
|
441
|
+
for (const node of program.body) {
|
|
442
|
+
if (node.type !== 'ImportDeclaration')
|
|
443
|
+
continue;
|
|
444
|
+
const specifier = String(node.source.value);
|
|
445
|
+
if (specifier === '@plumeria/core') {
|
|
446
|
+
coreImport = node;
|
|
447
|
+
for (const candidate of node.specifiers) {
|
|
448
|
+
if (candidate.type === 'ImportNamespaceSpecifier')
|
|
449
|
+
coreLocal = candidate.local.name;
|
|
450
|
+
else if (candidate.type === 'ImportSpecifier' &&
|
|
451
|
+
(candidate.imported.name ?? candidate.imported.value) === 'css')
|
|
452
|
+
coreLocal = candidate.local.name;
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const table = importedFrom(specifier, raw.values);
|
|
457
|
+
if (table)
|
|
458
|
+
for (const candidate of node.specifiers) {
|
|
459
|
+
if (candidate.type !== 'ImportSpecifier')
|
|
460
|
+
continue;
|
|
461
|
+
const imported = candidate.imported.name ?? candidate.imported.value;
|
|
462
|
+
if (imported in table)
|
|
463
|
+
inlineByLocal.set(candidate.local.name, table[imported]);
|
|
464
|
+
}
|
|
465
|
+
const module = importedReleasedModule(specifier);
|
|
466
|
+
if (!module)
|
|
467
|
+
continue;
|
|
468
|
+
const importedBinding = node.specifiers.find((candidate) => (candidate.type === 'ImportSpecifier' &&
|
|
469
|
+
(candidate.imported.name ?? candidate.imported.value) ===
|
|
470
|
+
module.binding) ||
|
|
471
|
+
(candidate.type === 'ImportDefaultSpecifier' &&
|
|
472
|
+
specifier.endsWith('.css')));
|
|
473
|
+
if (!importedBinding)
|
|
474
|
+
continue;
|
|
475
|
+
const local = importedBinding.local.name;
|
|
476
|
+
moduleByLocal.set(local, { ...module, binding: local });
|
|
477
|
+
active = true;
|
|
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
|
+
});
|
|
486
|
+
const migrating = active || themeBindings.length > 0 || animationBindings.length > 0;
|
|
487
|
+
coreNeeded = keptDefinition || !migrating || survivesCore(program);
|
|
488
|
+
if (coreImport &&
|
|
489
|
+
!coreNeeded &&
|
|
490
|
+
!entry &&
|
|
491
|
+
themeBindings.length === 0 &&
|
|
492
|
+
animationBindings.length === 0) {
|
|
493
|
+
context.report({
|
|
494
|
+
node: coreImport,
|
|
495
|
+
messageId: 'core',
|
|
496
|
+
fix: (fixer) => removeStatement(fixer, coreImport),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
},
|
|
190
500
|
ImportDeclaration(node) {
|
|
191
|
-
if (
|
|
501
|
+
if (!coreNeeded &&
|
|
502
|
+
(entry || themeBindings.length > 0 || animationBindings.length > 0) &&
|
|
192
503
|
node.source.value === '@plumeria/core') {
|
|
193
504
|
context.report({
|
|
194
505
|
node,
|
|
195
506
|
messageId: 'core',
|
|
196
|
-
fix: (fixer) => fixer
|
|
507
|
+
fix: (fixer) => removeStatement(fixer, node),
|
|
197
508
|
});
|
|
198
509
|
return;
|
|
199
510
|
}
|
|
@@ -214,12 +525,10 @@ exports.releaseStyles = {
|
|
|
214
525
|
}
|
|
215
526
|
const importedModule = importedReleasedModule(String(node.source.value));
|
|
216
527
|
if (importedModule) {
|
|
217
|
-
active = true;
|
|
218
528
|
const importedBinding = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
219
529
|
(specifier.imported.name ?? specifier.imported.value) ===
|
|
220
530
|
importedModule.binding);
|
|
221
531
|
const local = importedBinding?.local.name ?? importedModule.binding;
|
|
222
|
-
activeModule = { ...importedModule, binding: local };
|
|
223
532
|
if (importedModule.definitionOnly && importedBinding) {
|
|
224
533
|
const retained = node.specifiers.filter((specifier) => specifier !== importedBinding);
|
|
225
534
|
const released = `import ${local} from '${specifierFor(importedModule)}';`;
|
|
@@ -256,7 +565,7 @@ exports.releaseStyles = {
|
|
|
256
565
|
: path[1] === 'keyframes' || path[1] === 'viewTransition'
|
|
257
566
|
? 'animation'
|
|
258
567
|
: 'static',
|
|
259
|
-
fix: (fixer) => fixer
|
|
568
|
+
fix: (fixer) => removeStatement(fixer, target),
|
|
260
569
|
});
|
|
261
570
|
return;
|
|
262
571
|
}
|
|
@@ -269,7 +578,7 @@ exports.releaseStyles = {
|
|
|
269
578
|
context.report({
|
|
270
579
|
node: aliased,
|
|
271
580
|
messageId: 'definition',
|
|
272
|
-
fix: (fixer) => fixer
|
|
581
|
+
fix: (fixer) => removeStatement(fixer, aliased),
|
|
273
582
|
});
|
|
274
583
|
return;
|
|
275
584
|
}
|
|
@@ -281,13 +590,103 @@ exports.releaseStyles = {
|
|
|
281
590
|
const replacement = exported
|
|
282
591
|
? `${imported}\nexport { ${entry.binding} };`
|
|
283
592
|
: imported;
|
|
593
|
+
const anchor = importAnchor();
|
|
284
594
|
context.report({
|
|
285
595
|
node: target,
|
|
286
596
|
messageId: 'definition',
|
|
287
|
-
fix: (fixer) =>
|
|
597
|
+
fix: (fixer) => anchor >= target.range[0] && anchor <= target.range[1]
|
|
598
|
+
? fixer.replaceText(target, replacement)
|
|
599
|
+
: [
|
|
600
|
+
fixer.insertTextBeforeRange([anchor, anchor], `${replacement}\n`),
|
|
601
|
+
removeStatement(fixer, target),
|
|
602
|
+
],
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
Identifier(node) {
|
|
606
|
+
const value = inlineByLocal.get(node.name);
|
|
607
|
+
const literal = literalOf(value);
|
|
608
|
+
if (literal === undefined)
|
|
609
|
+
return;
|
|
610
|
+
const parent = node.parent;
|
|
611
|
+
if (parent?.type === 'ImportSpecifier' ||
|
|
612
|
+
parent?.type === 'ExportSpecifier' ||
|
|
613
|
+
(parent?.type === 'MemberExpression' && parent.property === node) ||
|
|
614
|
+
(parent?.type === 'Property' &&
|
|
615
|
+
parent.key === node &&
|
|
616
|
+
!parent.computed))
|
|
617
|
+
return;
|
|
618
|
+
context.report({
|
|
619
|
+
node,
|
|
620
|
+
messageId: 'token',
|
|
621
|
+
fix: (fixer) => fixer.replaceText(node, literal),
|
|
622
|
+
});
|
|
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
|
+
},
|
|
654
|
+
TSTypeReference(node) {
|
|
655
|
+
if (!complete || keptDefinition)
|
|
656
|
+
return;
|
|
657
|
+
const name = node.typeName.type === 'TSQualifiedName'
|
|
658
|
+
? node.typeName.right.name
|
|
659
|
+
: node.typeName.name;
|
|
660
|
+
if (name !== 'Style')
|
|
661
|
+
return;
|
|
662
|
+
context.report({
|
|
663
|
+
node,
|
|
664
|
+
messageId: 'styleType',
|
|
665
|
+
fix: (fixer) => fixer.replaceText(node, 'string'),
|
|
288
666
|
});
|
|
289
667
|
},
|
|
290
668
|
MemberExpression(node) {
|
|
669
|
+
if (node.object.type === 'Identifier') {
|
|
670
|
+
const table = inlineByLocal.get(node.object.name);
|
|
671
|
+
if (table && typeof table === 'object') {
|
|
672
|
+
const key = node.computed
|
|
673
|
+
? node.property.type === 'Literal'
|
|
674
|
+
? String(node.property.value)
|
|
675
|
+
: undefined
|
|
676
|
+
: node.property.name;
|
|
677
|
+
const literal = key === undefined
|
|
678
|
+
? undefined
|
|
679
|
+
: literalOf(table[key]);
|
|
680
|
+
if (literal !== undefined) {
|
|
681
|
+
context.report({
|
|
682
|
+
node,
|
|
683
|
+
messageId: 'token',
|
|
684
|
+
fix: (fixer) => fixer.replaceText(node, literal),
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
291
690
|
if (!entry?.aliases || node.object.type !== 'Identifier')
|
|
292
691
|
return;
|
|
293
692
|
const alias = entry.aliases[node.object.name];
|
|
@@ -296,7 +695,9 @@ exports.releaseStyles = {
|
|
|
296
695
|
const key = node.computed
|
|
297
696
|
? node.property.type === 'Literal'
|
|
298
697
|
? String(node.property.value)
|
|
299
|
-
:
|
|
698
|
+
: node.property.type === 'Identifier'
|
|
699
|
+
? constants[node.property.name]
|
|
700
|
+
: undefined
|
|
300
701
|
: node.property.name;
|
|
301
702
|
const className = key ? alias[key] : undefined;
|
|
302
703
|
if (!className)
|
|
@@ -310,6 +711,28 @@ exports.releaseStyles = {
|
|
|
310
711
|
fix: (fixer) => fixer.replaceText(node, `${entry.binding}${access}`),
|
|
311
712
|
});
|
|
312
713
|
},
|
|
714
|
+
CallExpression(node) {
|
|
715
|
+
if (node.callee.type !== 'MemberExpression' ||
|
|
716
|
+
node.callee.object.type !== 'Identifier' ||
|
|
717
|
+
node.callee.property.name !== 'use')
|
|
718
|
+
return;
|
|
719
|
+
const reader = node.callee.object.name;
|
|
720
|
+
if (coreLocal ? reader !== coreLocal : moduleByLocal.has(reader))
|
|
721
|
+
return;
|
|
722
|
+
const texts = node.arguments.map(resolveText);
|
|
723
|
+
if (texts.length === 0 || texts.some((text) => text === undefined))
|
|
724
|
+
return;
|
|
725
|
+
const replacement = texts.length === 1 && !node.arguments.some(needsFilter)
|
|
726
|
+
? texts[0]
|
|
727
|
+
: node.arguments.some(needsFilter)
|
|
728
|
+
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
729
|
+
: `[${texts.join(', ')}].join(' ')`;
|
|
730
|
+
context.report({
|
|
731
|
+
node,
|
|
732
|
+
messageId: 'use',
|
|
733
|
+
fix: (fixer) => fixer.replaceText(node, replacement),
|
|
734
|
+
});
|
|
735
|
+
},
|
|
313
736
|
JSXAttribute(node) {
|
|
314
737
|
if (!active)
|
|
315
738
|
return;
|
|
@@ -318,40 +741,49 @@ exports.releaseStyles = {
|
|
|
318
741
|
const expression = node.value?.type === 'JSXExpressionContainer'
|
|
319
742
|
? node.value.expression
|
|
320
743
|
: undefined;
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
744
|
+
const carried = node.parent.attributes.find((attribute) => attribute !== node &&
|
|
745
|
+
attribute.type === 'JSXAttribute' &&
|
|
746
|
+
attribute.name?.type === 'JSXIdentifier' &&
|
|
747
|
+
attribute.name.name === 'className');
|
|
748
|
+
const takeProp = (valueText) => {
|
|
749
|
+
if (!carried) {
|
|
750
|
+
context.report({
|
|
751
|
+
node: node.name,
|
|
752
|
+
messageId: 'prop',
|
|
753
|
+
fix: (fixer) => fixer.replaceText(node.name, 'className'),
|
|
754
|
+
});
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const ours = valueText ??
|
|
758
|
+
(node.value ? sourceCode.getText(node.value).slice(1, -1) : '');
|
|
759
|
+
const theirs = carried.value?.type === 'JSXExpressionContainer'
|
|
760
|
+
? sourceCode.getText(carried.value.expression)
|
|
761
|
+
: carried.value
|
|
762
|
+
? sourceCode.getText(carried.value)
|
|
763
|
+
: "''";
|
|
764
|
+
context.report({
|
|
765
|
+
node,
|
|
766
|
+
messageId: 'prop',
|
|
767
|
+
fix: (fixer) => [
|
|
768
|
+
fixer.replaceText(carried, `className={[${theirs}, ${ours}].filter(Boolean).join(' ')}`),
|
|
769
|
+
fixer.removeRange([
|
|
770
|
+
Math.min(node.range[0], carried.range[1]),
|
|
771
|
+
node.range[1],
|
|
772
|
+
]),
|
|
773
|
+
],
|
|
774
|
+
});
|
|
339
775
|
};
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
776
|
+
const mergedClass = (elements) => {
|
|
777
|
+
if (elements.length < 2)
|
|
342
778
|
return undefined;
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
if (!key || !func || func.variables.length !== value.arguments.length)
|
|
779
|
+
const resolved = elements.map(styleKey);
|
|
780
|
+
if (resolved.some((part) => !part))
|
|
346
781
|
return undefined;
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return `'${variable}': ${argument}`;
|
|
353
|
-
}),
|
|
354
|
-
};
|
|
782
|
+
const module = resolved[0].module;
|
|
783
|
+
if (resolved.some((part) => part.module !== module))
|
|
784
|
+
return undefined;
|
|
785
|
+
const name = module.merges?.[resolved.map((part) => part.key).join('|')];
|
|
786
|
+
return name ? accessOf(module, name) : undefined;
|
|
355
787
|
};
|
|
356
788
|
const replaceWithFunctionStyle = (className, declarations) => {
|
|
357
789
|
const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
|
|
@@ -387,7 +819,7 @@ exports.releaseStyles = {
|
|
|
387
819
|
return;
|
|
388
820
|
}
|
|
389
821
|
if (expression?.type === 'CallExpression' &&
|
|
390
|
-
|
|
822
|
+
moduleOf(expression.callee)) {
|
|
391
823
|
context.report({
|
|
392
824
|
node,
|
|
393
825
|
messageId: 'unsupportedFunctionStyle',
|
|
@@ -396,34 +828,70 @@ exports.releaseStyles = {
|
|
|
396
828
|
}
|
|
397
829
|
if (expression?.type === 'ArrayExpression') {
|
|
398
830
|
const elements = expression.elements.filter(Boolean);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (!resolved)
|
|
404
|
-
return sourceCode.getText(element);
|
|
405
|
-
declarations.push(...resolved.declarations);
|
|
406
|
-
return resolved.className;
|
|
407
|
-
});
|
|
408
|
-
const replacement = elements.some(needsFilter)
|
|
409
|
-
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
410
|
-
: `\`${texts.map((text) => `\${${text}}`).join(' ')}\``;
|
|
411
|
-
if (declarations.length > 0) {
|
|
412
|
-
replaceWithFunctionStyle(replacement, declarations);
|
|
831
|
+
const merged = mergedClass(elements);
|
|
832
|
+
if (merged) {
|
|
833
|
+
if (carried) {
|
|
834
|
+
takeProp(merged);
|
|
413
835
|
return;
|
|
414
836
|
}
|
|
415
837
|
context.report({
|
|
416
838
|
node: expression,
|
|
417
839
|
messageId: 'composition',
|
|
418
|
-
fix: (fixer) => fixer.replaceText(expression,
|
|
840
|
+
fix: (fixer) => fixer.replaceText(expression, merged),
|
|
419
841
|
});
|
|
842
|
+
takeProp();
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (elements.length === 0) {
|
|
846
|
+
takeProp();
|
|
847
|
+
return;
|
|
420
848
|
}
|
|
849
|
+
const texts = elements.map(resolveText);
|
|
850
|
+
if (texts.some((text) => text === undefined))
|
|
851
|
+
return;
|
|
852
|
+
const ranked = elements.map((element) => element.type === 'LogicalExpression'
|
|
853
|
+
? styleKey(element.right)
|
|
854
|
+
: styleKey(element));
|
|
855
|
+
const module = ranked[0]?.module;
|
|
856
|
+
const perSlot = module && ranked.every((part) => part?.module === module)
|
|
857
|
+
? module.overrides?.[ranked.map((part) => part.key).join('|')]
|
|
858
|
+
: undefined;
|
|
859
|
+
if (perSlot) {
|
|
860
|
+
for (let slot = elements.length - 1; slot >= 0; slot--) {
|
|
861
|
+
const name = perSlot[slot];
|
|
862
|
+
if (!name)
|
|
863
|
+
continue;
|
|
864
|
+
const element = elements[slot];
|
|
865
|
+
const access = accessOf(module, name);
|
|
866
|
+
texts.splice(slot + 1, 0, element.type === 'LogicalExpression'
|
|
867
|
+
? `${sourceCode.getText(element.left)} ${element.operator} ${access}`
|
|
868
|
+
: access);
|
|
869
|
+
elements.splice(slot + 1, 0, element);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const declarations = elements.flatMap(declarationsOf);
|
|
873
|
+
const replacement = elements.some(needsFilter)
|
|
874
|
+
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
875
|
+
: `[${texts.join(', ')}].join(' ')`;
|
|
876
|
+
if (declarations.length > 0) {
|
|
877
|
+
replaceWithFunctionStyle(replacement, declarations);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (carried) {
|
|
881
|
+
takeProp(replacement);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
context.report({
|
|
885
|
+
node: expression,
|
|
886
|
+
messageId: 'composition',
|
|
887
|
+
fix: (fixer) => fixer.replaceText(expression, replacement),
|
|
888
|
+
});
|
|
889
|
+
takeProp();
|
|
890
|
+
return;
|
|
421
891
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
fix: (fixer) => fixer.replaceText(node.name, 'className'),
|
|
426
|
-
});
|
|
892
|
+
if (!complete && expression && resolveText(expression) === undefined)
|
|
893
|
+
return;
|
|
894
|
+
takeProp();
|
|
427
895
|
},
|
|
428
896
|
};
|
|
429
897
|
},
|