@plumeria/codemod 18.2.19 → 18.2.20
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 +48 -9
- package/dist/cli.js +5 -3
- package/dist/migrate.js +3 -1
- package/dist/release.d.ts +2 -0
- package/dist/release.js +295 -38
- package/dist/transforms/adopt-styles.d.ts +2 -0
- package/dist/transforms/adopt-styles.js +303 -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 +4 -0
- package/dist/transforms/release-styles.js +466 -66
- package/package.json +1 -1
|
@@ -79,6 +79,9 @@ exports.releaseStyles = {
|
|
|
79
79
|
core: 'Remove the Plumeria import after exporting the styles.',
|
|
80
80
|
prop: 'Use className after exporting this style to CSS Modules.',
|
|
81
81
|
composition: 'Join the composed CSS Module class names.',
|
|
82
|
+
use: 'Read the class name straight from the CSS Module.',
|
|
83
|
+
token: 'Inline the value this token resolved to.',
|
|
84
|
+
styleType: 'A released style is a class name.',
|
|
82
85
|
functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
|
|
83
86
|
unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
|
|
84
87
|
},
|
|
@@ -90,6 +93,8 @@ exports.releaseStyles = {
|
|
|
90
93
|
themes: { type: 'object' },
|
|
91
94
|
animations: { type: 'object' },
|
|
92
95
|
statics: { type: 'object' },
|
|
96
|
+
values: { type: 'object' },
|
|
97
|
+
complete: { type: 'boolean' },
|
|
93
98
|
styleProp: { type: 'string' },
|
|
94
99
|
active: { type: 'boolean' },
|
|
95
100
|
},
|
|
@@ -100,13 +105,85 @@ exports.releaseStyles = {
|
|
|
100
105
|
create(context) {
|
|
101
106
|
const raw = (context.options[0] ?? {});
|
|
102
107
|
const declared = raw.modules?.[context.filename];
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
108
|
+
const keptDefinition = declared?.definitionOnly === true;
|
|
109
|
+
const complete = raw.complete === true;
|
|
110
|
+
const entry = keptDefinition ? undefined : declared;
|
|
111
|
+
const themeBindings = keptDefinition
|
|
112
|
+
? []
|
|
113
|
+
: (raw.themes?.[context.filename] ?? []);
|
|
114
|
+
const animationBindings = keptDefinition
|
|
115
|
+
? []
|
|
116
|
+
: (raw.animations?.[context.filename] ?? []);
|
|
106
117
|
const styleProp = raw.styleProp ?? 'classStyle';
|
|
107
118
|
const sourceCode = context.sourceCode;
|
|
108
|
-
|
|
109
|
-
let
|
|
119
|
+
const moduleByLocal = new Map();
|
|
120
|
+
let active = Boolean(entry || raw.active || raw.complete);
|
|
121
|
+
let coreImport;
|
|
122
|
+
let coreLocal;
|
|
123
|
+
let coreNeeded = true;
|
|
124
|
+
const declarationOf = (statement) => statement.type === 'ExportNamedDeclaration'
|
|
125
|
+
? statement.declaration
|
|
126
|
+
: statement;
|
|
127
|
+
const removesDeclaration = (statement) => {
|
|
128
|
+
const declaration = declarationOf(statement);
|
|
129
|
+
if (declaration?.type !== 'VariableDeclaration' ||
|
|
130
|
+
declaration.declarations.length !== 1)
|
|
131
|
+
return false;
|
|
132
|
+
const only = declaration.declarations[0];
|
|
133
|
+
if (only.id.type !== 'Identifier')
|
|
134
|
+
return false;
|
|
135
|
+
const called = only.init?.type === 'CallExpression'
|
|
136
|
+
? memberPath(only.init.callee)
|
|
137
|
+
: undefined;
|
|
138
|
+
if (called?.length !== 2)
|
|
139
|
+
return false;
|
|
140
|
+
if (called[1] === 'createStatic')
|
|
141
|
+
return Boolean(entry);
|
|
142
|
+
if (called[1] === 'createTheme')
|
|
143
|
+
return themeBindings.includes(only.id.name);
|
|
144
|
+
if (called[1] === 'keyframes' || called[1] === 'viewTransition')
|
|
145
|
+
return animationBindings.includes(only.id.name);
|
|
146
|
+
if (called[1] === 'create')
|
|
147
|
+
return Boolean(entry &&
|
|
148
|
+
(only.id.name === entry.binding || entry.aliases?.[only.id.name]));
|
|
149
|
+
return false;
|
|
150
|
+
};
|
|
151
|
+
const removesStatement = (statement) => {
|
|
152
|
+
if (keptDefinition)
|
|
153
|
+
return false;
|
|
154
|
+
if (statement.type !== 'ImportDeclaration')
|
|
155
|
+
return removesDeclaration(statement);
|
|
156
|
+
const specifier = String(statement.source.value);
|
|
157
|
+
if (specifier === '@plumeria/core')
|
|
158
|
+
return !coreNeeded;
|
|
159
|
+
const bindings = [
|
|
160
|
+
...(importedAnimationBindings(specifier) ?? []),
|
|
161
|
+
...(importedThemeBindings(specifier) ?? []),
|
|
162
|
+
...(importedStaticBindings(specifier) ?? []),
|
|
163
|
+
];
|
|
164
|
+
return (bindings.length > 0 &&
|
|
165
|
+
statement.specifiers.length > 0 &&
|
|
166
|
+
statement.specifiers.every((candidate) => candidate.type === 'ImportSpecifier' &&
|
|
167
|
+
bindings.includes(candidate.imported.name ?? candidate.imported.value)));
|
|
168
|
+
};
|
|
169
|
+
const removeStatement = (fixer, node) => {
|
|
170
|
+
const body = sourceCode.ast.body;
|
|
171
|
+
const next = body.find((statement) => statement.range[0] >= node.range[1]);
|
|
172
|
+
if (!next)
|
|
173
|
+
return fixer.removeRange([node.range[0], sourceCode.text.length]);
|
|
174
|
+
if (removesStatement(next))
|
|
175
|
+
return fixer.remove(node);
|
|
176
|
+
const anchor = importAnchor();
|
|
177
|
+
if (node.range[1] <= anchor && next.range[0] >= anchor)
|
|
178
|
+
return fixer.remove(node);
|
|
179
|
+
return fixer.removeRange([node.range[0], next.range[0]]);
|
|
180
|
+
};
|
|
181
|
+
const importAnchor = () => {
|
|
182
|
+
const imports = sourceCode.ast.body.filter((statement) => statement.type === 'ImportDeclaration');
|
|
183
|
+
return imports.length > 0
|
|
184
|
+
? Math.min(imports[imports.length - 1].range[1] + 1, sourceCode.text.length)
|
|
185
|
+
: 0;
|
|
186
|
+
};
|
|
110
187
|
const specifierFor = (module) => {
|
|
111
188
|
if (!module.target)
|
|
112
189
|
return module.source;
|
|
@@ -133,10 +210,23 @@ exports.releaseStyles = {
|
|
|
133
210
|
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
134
211
|
return resolved ? table?.[resolved] : undefined;
|
|
135
212
|
};
|
|
136
|
-
const
|
|
213
|
+
const byTarget = new Map(Object.values(raw.modules ?? {}).flatMap((module) => module.target ? [[module.target, module]] : []));
|
|
214
|
+
const importedReleasedModule = (specifier) => {
|
|
215
|
+
const declared = importedFrom(specifier, raw.modules);
|
|
216
|
+
if (declared)
|
|
217
|
+
return declared;
|
|
218
|
+
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
219
|
+
return resolved ? byTarget.get(resolved) : undefined;
|
|
220
|
+
};
|
|
137
221
|
const importedThemeBindings = (specifier) => importedFrom(specifier, raw.themes);
|
|
138
222
|
const importedAnimationBindings = (specifier) => importedFrom(specifier, raw.animations);
|
|
139
223
|
const importedStaticBindings = (specifier) => importedFrom(specifier, raw.statics);
|
|
224
|
+
const inlineByLocal = new Map();
|
|
225
|
+
const literalOf = (value) => typeof value === 'string'
|
|
226
|
+
? `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
|
|
227
|
+
: typeof value === 'number'
|
|
228
|
+
? String(value)
|
|
229
|
+
: undefined;
|
|
140
230
|
const releasedRange = () => {
|
|
141
231
|
if (!entry)
|
|
142
232
|
return undefined;
|
|
@@ -181,19 +271,204 @@ exports.releaseStyles = {
|
|
|
181
271
|
node,
|
|
182
272
|
messageId,
|
|
183
273
|
fix: (fixer) => retained.length === 0
|
|
184
|
-
? fixer
|
|
274
|
+
? removeStatement(fixer, node)
|
|
185
275
|
: fixer.replaceText(node, importOf(retained, node.source)),
|
|
186
276
|
});
|
|
187
277
|
return true;
|
|
188
278
|
};
|
|
279
|
+
const moduleOf = (node) => node?.type === 'MemberExpression' && node.object.type === 'Identifier'
|
|
280
|
+
? moduleByLocal.get(node.object.name)
|
|
281
|
+
: undefined;
|
|
282
|
+
const styleKey = (node) => {
|
|
283
|
+
const module = moduleOf(node);
|
|
284
|
+
if (!module)
|
|
285
|
+
return undefined;
|
|
286
|
+
const written = node.computed
|
|
287
|
+
? node.property.type === 'Literal'
|
|
288
|
+
? String(node.property.value)
|
|
289
|
+
: undefined
|
|
290
|
+
: node.property.name;
|
|
291
|
+
if (written === undefined)
|
|
292
|
+
return undefined;
|
|
293
|
+
const alias = module.aliases?.[node.object.name];
|
|
294
|
+
const key = alias ? alias[written] : written;
|
|
295
|
+
return key === undefined ? undefined : { module, key };
|
|
296
|
+
};
|
|
297
|
+
const accessOf = (module, key) => `${module.binding}${IDENTIFIER.test(key) ? `.${key}` : `['${key}']`}`;
|
|
298
|
+
const classText = (node) => {
|
|
299
|
+
const resolved = styleKey(node);
|
|
300
|
+
return resolved ? accessOf(resolved.module, resolved.key) : undefined;
|
|
301
|
+
};
|
|
302
|
+
const releasedUse = (node, parent) => parent?.type === 'CallExpression' &&
|
|
303
|
+
parent.callee === node &&
|
|
304
|
+
parent.arguments.length > 0 &&
|
|
305
|
+
parent.arguments.every((argument) => classText(argument) !== undefined);
|
|
306
|
+
const removesCall = (method) => method === 'create' || method === 'createStatic'
|
|
307
|
+
? Boolean(entry)
|
|
308
|
+
: method === 'createTheme'
|
|
309
|
+
? themeBindings.length > 0
|
|
310
|
+
: method === 'keyframes' || method === 'viewTransition'
|
|
311
|
+
? animationBindings.length > 0
|
|
312
|
+
: false;
|
|
313
|
+
const survivesCore = (program) => {
|
|
314
|
+
if (!coreLocal)
|
|
315
|
+
return false;
|
|
316
|
+
let survives = false;
|
|
317
|
+
const visit = (node, parent) => {
|
|
318
|
+
if (survives || !node || typeof node !== 'object')
|
|
319
|
+
return;
|
|
320
|
+
if (Array.isArray(node)) {
|
|
321
|
+
for (const child of node)
|
|
322
|
+
visit(child, parent);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (typeof node.type !== 'string')
|
|
326
|
+
return;
|
|
327
|
+
if (node.type === 'MemberExpression' &&
|
|
328
|
+
node.object.type === 'Identifier' &&
|
|
329
|
+
node.object.name === coreLocal) {
|
|
330
|
+
const method = node.computed
|
|
331
|
+
? node.property.type === 'Literal'
|
|
332
|
+
? String(node.property.value)
|
|
333
|
+
: ''
|
|
334
|
+
: node.property.name;
|
|
335
|
+
survives =
|
|
336
|
+
method === 'use'
|
|
337
|
+
? !releasedUse(node, parent)
|
|
338
|
+
: !removesCall(method);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
for (const key of Object.keys(node)) {
|
|
342
|
+
if (key === 'parent' || key === 'loc' || key === 'range')
|
|
343
|
+
continue;
|
|
344
|
+
visit(node[key], node);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
visit(program.body, undefined);
|
|
348
|
+
return survives;
|
|
349
|
+
};
|
|
350
|
+
const functionStyle = (value) => {
|
|
351
|
+
if (value?.type !== 'CallExpression')
|
|
352
|
+
return undefined;
|
|
353
|
+
const resolved = styleKey(value.callee);
|
|
354
|
+
if (!resolved)
|
|
355
|
+
return undefined;
|
|
356
|
+
const func = resolved.module.functions?.[resolved.key];
|
|
357
|
+
if (!func || func.variables.length !== value.arguments.length)
|
|
358
|
+
return undefined;
|
|
359
|
+
return {
|
|
360
|
+
className: accessOf(resolved.module, resolved.key),
|
|
361
|
+
declarations: func.variables.map((variable, index) => {
|
|
362
|
+
const argument = sourceCode.getText(value.arguments[index]);
|
|
363
|
+
return `['${variable}' as string]: ${argument}`;
|
|
364
|
+
}),
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
const resolveText = (value) => {
|
|
368
|
+
if (!value)
|
|
369
|
+
return undefined;
|
|
370
|
+
const called = functionStyle(value);
|
|
371
|
+
if (called)
|
|
372
|
+
return called.className;
|
|
373
|
+
if (value.type === 'LogicalExpression') {
|
|
374
|
+
const right = resolveText(value.right);
|
|
375
|
+
return right === undefined
|
|
376
|
+
? undefined
|
|
377
|
+
: `${sourceCode.getText(value.left)} ${value.operator} ${right}`;
|
|
378
|
+
}
|
|
379
|
+
if (value.type === 'ConditionalExpression') {
|
|
380
|
+
const consequent = resolveText(value.consequent);
|
|
381
|
+
const alternate = resolveText(value.alternate);
|
|
382
|
+
return consequent === undefined || alternate === undefined
|
|
383
|
+
? undefined
|
|
384
|
+
: `${sourceCode.getText(value.test)} ? ${consequent} : ${alternate}`;
|
|
385
|
+
}
|
|
386
|
+
if (value.type === 'Literal' && !value.value)
|
|
387
|
+
return sourceCode.getText(value);
|
|
388
|
+
return classText(value);
|
|
389
|
+
};
|
|
390
|
+
const declarationsOf = (value) => {
|
|
391
|
+
if (!value)
|
|
392
|
+
return [];
|
|
393
|
+
const called = functionStyle(value);
|
|
394
|
+
if (called)
|
|
395
|
+
return called.declarations;
|
|
396
|
+
if (value.type === 'LogicalExpression')
|
|
397
|
+
return declarationsOf(value.right);
|
|
398
|
+
if (value.type === 'ConditionalExpression')
|
|
399
|
+
return [
|
|
400
|
+
...declarationsOf(value.consequent),
|
|
401
|
+
...declarationsOf(value.alternate),
|
|
402
|
+
];
|
|
403
|
+
return [];
|
|
404
|
+
};
|
|
189
405
|
return {
|
|
406
|
+
Program(program) {
|
|
407
|
+
if (entry) {
|
|
408
|
+
moduleByLocal.set(entry.binding, entry);
|
|
409
|
+
for (const owner of Object.keys(entry.aliases ?? {}))
|
|
410
|
+
moduleByLocal.set(owner, entry);
|
|
411
|
+
}
|
|
412
|
+
for (const node of program.body) {
|
|
413
|
+
if (node.type !== 'ImportDeclaration')
|
|
414
|
+
continue;
|
|
415
|
+
const specifier = String(node.source.value);
|
|
416
|
+
if (specifier === '@plumeria/core') {
|
|
417
|
+
coreImport = node;
|
|
418
|
+
for (const candidate of node.specifiers) {
|
|
419
|
+
if (candidate.type === 'ImportNamespaceSpecifier')
|
|
420
|
+
coreLocal = candidate.local.name;
|
|
421
|
+
else if (candidate.type === 'ImportSpecifier' &&
|
|
422
|
+
(candidate.imported.name ?? candidate.imported.value) === 'css')
|
|
423
|
+
coreLocal = candidate.local.name;
|
|
424
|
+
}
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const table = importedFrom(specifier, raw.values);
|
|
428
|
+
if (table)
|
|
429
|
+
for (const candidate of node.specifiers) {
|
|
430
|
+
if (candidate.type !== 'ImportSpecifier')
|
|
431
|
+
continue;
|
|
432
|
+
const imported = candidate.imported.name ?? candidate.imported.value;
|
|
433
|
+
if (imported in table)
|
|
434
|
+
inlineByLocal.set(candidate.local.name, table[imported]);
|
|
435
|
+
}
|
|
436
|
+
const module = importedReleasedModule(specifier);
|
|
437
|
+
if (!module)
|
|
438
|
+
continue;
|
|
439
|
+
const importedBinding = node.specifiers.find((candidate) => (candidate.type === 'ImportSpecifier' &&
|
|
440
|
+
(candidate.imported.name ?? candidate.imported.value) ===
|
|
441
|
+
module.binding) ||
|
|
442
|
+
(candidate.type === 'ImportDefaultSpecifier' &&
|
|
443
|
+
specifier.endsWith('.css')));
|
|
444
|
+
if (!importedBinding)
|
|
445
|
+
continue;
|
|
446
|
+
const local = importedBinding.local.name;
|
|
447
|
+
moduleByLocal.set(local, { ...module, binding: local });
|
|
448
|
+
active = true;
|
|
449
|
+
}
|
|
450
|
+
const migrating = active || themeBindings.length > 0 || animationBindings.length > 0;
|
|
451
|
+
coreNeeded = keptDefinition || !migrating || survivesCore(program);
|
|
452
|
+
if (coreImport &&
|
|
453
|
+
!coreNeeded &&
|
|
454
|
+
!entry &&
|
|
455
|
+
themeBindings.length === 0 &&
|
|
456
|
+
animationBindings.length === 0) {
|
|
457
|
+
context.report({
|
|
458
|
+
node: coreImport,
|
|
459
|
+
messageId: 'core',
|
|
460
|
+
fix: (fixer) => removeStatement(fixer, coreImport),
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
},
|
|
190
464
|
ImportDeclaration(node) {
|
|
191
|
-
if (
|
|
465
|
+
if (!coreNeeded &&
|
|
466
|
+
(entry || themeBindings.length > 0 || animationBindings.length > 0) &&
|
|
192
467
|
node.source.value === '@plumeria/core') {
|
|
193
468
|
context.report({
|
|
194
469
|
node,
|
|
195
470
|
messageId: 'core',
|
|
196
|
-
fix: (fixer) => fixer
|
|
471
|
+
fix: (fixer) => removeStatement(fixer, node),
|
|
197
472
|
});
|
|
198
473
|
return;
|
|
199
474
|
}
|
|
@@ -214,12 +489,10 @@ exports.releaseStyles = {
|
|
|
214
489
|
}
|
|
215
490
|
const importedModule = importedReleasedModule(String(node.source.value));
|
|
216
491
|
if (importedModule) {
|
|
217
|
-
active = true;
|
|
218
492
|
const importedBinding = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
219
493
|
(specifier.imported.name ?? specifier.imported.value) ===
|
|
220
494
|
importedModule.binding);
|
|
221
495
|
const local = importedBinding?.local.name ?? importedModule.binding;
|
|
222
|
-
activeModule = { ...importedModule, binding: local };
|
|
223
496
|
if (importedModule.definitionOnly && importedBinding) {
|
|
224
497
|
const retained = node.specifiers.filter((specifier) => specifier !== importedBinding);
|
|
225
498
|
const released = `import ${local} from '${specifierFor(importedModule)}';`;
|
|
@@ -256,7 +529,7 @@ exports.releaseStyles = {
|
|
|
256
529
|
: path[1] === 'keyframes' || path[1] === 'viewTransition'
|
|
257
530
|
? 'animation'
|
|
258
531
|
: 'static',
|
|
259
|
-
fix: (fixer) => fixer
|
|
532
|
+
fix: (fixer) => removeStatement(fixer, target),
|
|
260
533
|
});
|
|
261
534
|
return;
|
|
262
535
|
}
|
|
@@ -269,7 +542,7 @@ exports.releaseStyles = {
|
|
|
269
542
|
context.report({
|
|
270
543
|
node: aliased,
|
|
271
544
|
messageId: 'definition',
|
|
272
|
-
fix: (fixer) => fixer
|
|
545
|
+
fix: (fixer) => removeStatement(fixer, aliased),
|
|
273
546
|
});
|
|
274
547
|
return;
|
|
275
548
|
}
|
|
@@ -281,13 +554,73 @@ exports.releaseStyles = {
|
|
|
281
554
|
const replacement = exported
|
|
282
555
|
? `${imported}\nexport { ${entry.binding} };`
|
|
283
556
|
: imported;
|
|
557
|
+
const anchor = importAnchor();
|
|
284
558
|
context.report({
|
|
285
559
|
node: target,
|
|
286
560
|
messageId: 'definition',
|
|
287
|
-
fix: (fixer) =>
|
|
561
|
+
fix: (fixer) => anchor >= target.range[0] && anchor <= target.range[1]
|
|
562
|
+
? fixer.replaceText(target, replacement)
|
|
563
|
+
: [
|
|
564
|
+
fixer.insertTextBeforeRange([anchor, anchor], `${replacement}\n`),
|
|
565
|
+
removeStatement(fixer, target),
|
|
566
|
+
],
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
Identifier(node) {
|
|
570
|
+
const value = inlineByLocal.get(node.name);
|
|
571
|
+
const literal = literalOf(value);
|
|
572
|
+
if (literal === undefined)
|
|
573
|
+
return;
|
|
574
|
+
const parent = node.parent;
|
|
575
|
+
if (parent?.type === 'ImportSpecifier' ||
|
|
576
|
+
parent?.type === 'ExportSpecifier' ||
|
|
577
|
+
(parent?.type === 'MemberExpression' && parent.property === node) ||
|
|
578
|
+
(parent?.type === 'Property' &&
|
|
579
|
+
parent.key === node &&
|
|
580
|
+
!parent.computed))
|
|
581
|
+
return;
|
|
582
|
+
context.report({
|
|
583
|
+
node,
|
|
584
|
+
messageId: 'token',
|
|
585
|
+
fix: (fixer) => fixer.replaceText(node, literal),
|
|
586
|
+
});
|
|
587
|
+
},
|
|
588
|
+
TSTypeReference(node) {
|
|
589
|
+
if (!complete || keptDefinition)
|
|
590
|
+
return;
|
|
591
|
+
const name = node.typeName.type === 'TSQualifiedName'
|
|
592
|
+
? node.typeName.right.name
|
|
593
|
+
: node.typeName.name;
|
|
594
|
+
if (name !== 'Style')
|
|
595
|
+
return;
|
|
596
|
+
context.report({
|
|
597
|
+
node,
|
|
598
|
+
messageId: 'styleType',
|
|
599
|
+
fix: (fixer) => fixer.replaceText(node, 'string'),
|
|
288
600
|
});
|
|
289
601
|
},
|
|
290
602
|
MemberExpression(node) {
|
|
603
|
+
if (node.object.type === 'Identifier') {
|
|
604
|
+
const table = inlineByLocal.get(node.object.name);
|
|
605
|
+
if (table && typeof table === 'object') {
|
|
606
|
+
const key = node.computed
|
|
607
|
+
? node.property.type === 'Literal'
|
|
608
|
+
? String(node.property.value)
|
|
609
|
+
: undefined
|
|
610
|
+
: node.property.name;
|
|
611
|
+
const literal = key === undefined
|
|
612
|
+
? undefined
|
|
613
|
+
: literalOf(table[key]);
|
|
614
|
+
if (literal !== undefined) {
|
|
615
|
+
context.report({
|
|
616
|
+
node,
|
|
617
|
+
messageId: 'token',
|
|
618
|
+
fix: (fixer) => fixer.replaceText(node, literal),
|
|
619
|
+
});
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
291
624
|
if (!entry?.aliases || node.object.type !== 'Identifier')
|
|
292
625
|
return;
|
|
293
626
|
const alias = entry.aliases[node.object.name];
|
|
@@ -310,6 +643,28 @@ exports.releaseStyles = {
|
|
|
310
643
|
fix: (fixer) => fixer.replaceText(node, `${entry.binding}${access}`),
|
|
311
644
|
});
|
|
312
645
|
},
|
|
646
|
+
CallExpression(node) {
|
|
647
|
+
if (node.callee.type !== 'MemberExpression' ||
|
|
648
|
+
node.callee.object.type !== 'Identifier' ||
|
|
649
|
+
node.callee.property.name !== 'use')
|
|
650
|
+
return;
|
|
651
|
+
const reader = node.callee.object.name;
|
|
652
|
+
if (coreLocal ? reader !== coreLocal : moduleByLocal.has(reader))
|
|
653
|
+
return;
|
|
654
|
+
const texts = node.arguments.map(resolveText);
|
|
655
|
+
if (texts.length === 0 || texts.some((text) => text === undefined))
|
|
656
|
+
return;
|
|
657
|
+
const replacement = texts.length === 1 && !node.arguments.some(needsFilter)
|
|
658
|
+
? texts[0]
|
|
659
|
+
: node.arguments.some(needsFilter)
|
|
660
|
+
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
661
|
+
: `[${texts.join(', ')}].join(' ')`;
|
|
662
|
+
context.report({
|
|
663
|
+
node,
|
|
664
|
+
messageId: 'use',
|
|
665
|
+
fix: (fixer) => fixer.replaceText(node, replacement),
|
|
666
|
+
});
|
|
667
|
+
},
|
|
313
668
|
JSXAttribute(node) {
|
|
314
669
|
if (!active)
|
|
315
670
|
return;
|
|
@@ -318,40 +673,49 @@ exports.releaseStyles = {
|
|
|
318
673
|
const expression = node.value?.type === 'JSXExpressionContainer'
|
|
319
674
|
? node.value.expression
|
|
320
675
|
: undefined;
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
676
|
+
const carried = node.parent.attributes.find((attribute) => attribute !== node &&
|
|
677
|
+
attribute.type === 'JSXAttribute' &&
|
|
678
|
+
attribute.name?.type === 'JSXIdentifier' &&
|
|
679
|
+
attribute.name.name === 'className');
|
|
680
|
+
const takeProp = (valueText) => {
|
|
681
|
+
if (!carried) {
|
|
682
|
+
context.report({
|
|
683
|
+
node: node.name,
|
|
684
|
+
messageId: 'prop',
|
|
685
|
+
fix: (fixer) => fixer.replaceText(node.name, 'className'),
|
|
686
|
+
});
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const ours = valueText ??
|
|
690
|
+
(node.value ? sourceCode.getText(node.value).slice(1, -1) : '');
|
|
691
|
+
const theirs = carried.value?.type === 'JSXExpressionContainer'
|
|
692
|
+
? sourceCode.getText(carried.value.expression)
|
|
693
|
+
: carried.value
|
|
694
|
+
? sourceCode.getText(carried.value)
|
|
695
|
+
: "''";
|
|
696
|
+
context.report({
|
|
697
|
+
node,
|
|
698
|
+
messageId: 'prop',
|
|
699
|
+
fix: (fixer) => [
|
|
700
|
+
fixer.replaceText(carried, `className={[${theirs}, ${ours}].filter(Boolean).join(' ')}`),
|
|
701
|
+
fixer.removeRange([
|
|
702
|
+
Math.min(node.range[0], carried.range[1]),
|
|
703
|
+
node.range[1],
|
|
704
|
+
]),
|
|
705
|
+
],
|
|
706
|
+
});
|
|
339
707
|
};
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
708
|
+
const mergedClass = (elements) => {
|
|
709
|
+
if (elements.length < 2)
|
|
710
|
+
return undefined;
|
|
711
|
+
const resolved = elements.map(styleKey);
|
|
712
|
+
if (resolved.some((part) => !part))
|
|
342
713
|
return undefined;
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
if (!key || !func || func.variables.length !== value.arguments.length)
|
|
714
|
+
const module = resolved[0].module;
|
|
715
|
+
if (resolved.some((part) => part.module !== module))
|
|
346
716
|
return undefined;
|
|
347
|
-
const
|
|
348
|
-
return
|
|
349
|
-
className: `${moduleInfo.binding}${access}`,
|
|
350
|
-
declarations: func.variables.map((variable, index) => {
|
|
351
|
-
const argument = sourceCode.getText(value.arguments[index]);
|
|
352
|
-
return `'${variable}': ${argument}`;
|
|
353
|
-
}),
|
|
354
|
-
};
|
|
717
|
+
const name = module.merges?.[resolved.map((part) => part.key).join('|')];
|
|
718
|
+
return name ? accessOf(module, name) : undefined;
|
|
355
719
|
};
|
|
356
720
|
const replaceWithFunctionStyle = (className, declarations) => {
|
|
357
721
|
const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
|
|
@@ -387,7 +751,7 @@ exports.releaseStyles = {
|
|
|
387
751
|
return;
|
|
388
752
|
}
|
|
389
753
|
if (expression?.type === 'CallExpression' &&
|
|
390
|
-
|
|
754
|
+
moduleOf(expression.callee)) {
|
|
391
755
|
context.report({
|
|
392
756
|
node,
|
|
393
757
|
messageId: 'unsupportedFunctionStyle',
|
|
@@ -396,34 +760,70 @@ exports.releaseStyles = {
|
|
|
396
760
|
}
|
|
397
761
|
if (expression?.type === 'ArrayExpression') {
|
|
398
762
|
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);
|
|
763
|
+
const merged = mergedClass(elements);
|
|
764
|
+
if (merged) {
|
|
765
|
+
if (carried) {
|
|
766
|
+
takeProp(merged);
|
|
413
767
|
return;
|
|
414
768
|
}
|
|
415
769
|
context.report({
|
|
416
770
|
node: expression,
|
|
417
771
|
messageId: 'composition',
|
|
418
|
-
fix: (fixer) => fixer.replaceText(expression,
|
|
772
|
+
fix: (fixer) => fixer.replaceText(expression, merged),
|
|
419
773
|
});
|
|
774
|
+
takeProp();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (elements.length === 0) {
|
|
778
|
+
takeProp();
|
|
779
|
+
return;
|
|
420
780
|
}
|
|
781
|
+
const texts = elements.map(resolveText);
|
|
782
|
+
if (texts.some((text) => text === undefined))
|
|
783
|
+
return;
|
|
784
|
+
const ranked = elements.map((element) => element.type === 'LogicalExpression'
|
|
785
|
+
? styleKey(element.right)
|
|
786
|
+
: styleKey(element));
|
|
787
|
+
const module = ranked[0]?.module;
|
|
788
|
+
const perSlot = module && ranked.every((part) => part?.module === module)
|
|
789
|
+
? module.overrides?.[ranked.map((part) => part.key).join('|')]
|
|
790
|
+
: undefined;
|
|
791
|
+
if (perSlot) {
|
|
792
|
+
for (let slot = elements.length - 1; slot >= 0; slot--) {
|
|
793
|
+
const name = perSlot[slot];
|
|
794
|
+
if (!name)
|
|
795
|
+
continue;
|
|
796
|
+
const element = elements[slot];
|
|
797
|
+
const access = accessOf(module, name);
|
|
798
|
+
texts.splice(slot + 1, 0, element.type === 'LogicalExpression'
|
|
799
|
+
? `${sourceCode.getText(element.left)} ${element.operator} ${access}`
|
|
800
|
+
: access);
|
|
801
|
+
elements.splice(slot + 1, 0, element);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const declarations = elements.flatMap(declarationsOf);
|
|
805
|
+
const replacement = elements.some(needsFilter)
|
|
806
|
+
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
807
|
+
: `[${texts.join(', ')}].join(' ')`;
|
|
808
|
+
if (declarations.length > 0) {
|
|
809
|
+
replaceWithFunctionStyle(replacement, declarations);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (carried) {
|
|
813
|
+
takeProp(replacement);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
context.report({
|
|
817
|
+
node: expression,
|
|
818
|
+
messageId: 'composition',
|
|
819
|
+
fix: (fixer) => fixer.replaceText(expression, replacement),
|
|
820
|
+
});
|
|
821
|
+
takeProp();
|
|
822
|
+
return;
|
|
421
823
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
fix: (fixer) => fixer.replaceText(node.name, 'className'),
|
|
426
|
-
});
|
|
824
|
+
if (!complete && expression && resolveText(expression) === undefined)
|
|
825
|
+
return;
|
|
826
|
+
takeProp();
|
|
427
827
|
},
|
|
428
828
|
};
|
|
429
829
|
},
|