@plumeria/codemod 18.2.18 → 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 +8 -3
- package/dist/migrate.js +3 -1
- package/dist/release.d.ts +3 -0
- package/dist/release.js +335 -53
- package/dist/resolve.d.ts +2 -0
- package/dist/resolve.js +104 -0
- 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 +25 -1
- package/dist/transforms/from-plumeria.js +436 -51
- package/dist/transforms/release-styles.d.ts +8 -0
- package/dist/transforms/release-styles.js +582 -96
- package/package.json +2 -1
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.releaseStyles = exports.__private = void 0;
|
|
37
37
|
const path = __importStar(require("node:path"));
|
|
38
|
+
const resolve_1 = require("../resolve");
|
|
38
39
|
const memberPath = (node) => {
|
|
39
40
|
if (node?.type === 'Identifier')
|
|
40
41
|
return [node.name];
|
|
@@ -54,6 +55,7 @@ const isCreate = (node) => {
|
|
|
54
55
|
const path = memberPath(node.callee);
|
|
55
56
|
return path?.length === 2 && path[1] === 'create';
|
|
56
57
|
};
|
|
58
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
57
59
|
const needsFilter = (node) => node.type === 'LogicalExpression' ||
|
|
58
60
|
node.type === 'ConditionalExpression' ||
|
|
59
61
|
node.type === 'SpreadElement';
|
|
@@ -72,9 +74,14 @@ exports.releaseStyles = {
|
|
|
72
74
|
themeImport: 'Remove the createTheme import after inlining its CSS variables.',
|
|
73
75
|
animation: '`keyframes` or `viewTransition` was written globally.',
|
|
74
76
|
animationImport: 'Remove the animation import after replacing it with its generated name.',
|
|
77
|
+
staticImport: 'Remove the createStatic import after inlining its values into the CSS.',
|
|
78
|
+
alias: 'Read this style from the CSS Module the whole file was exported to.',
|
|
75
79
|
core: 'Remove the Plumeria import after exporting the styles.',
|
|
76
80
|
prop: 'Use className after exporting this style to CSS Modules.',
|
|
77
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.',
|
|
78
85
|
functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
|
|
79
86
|
unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
|
|
80
87
|
},
|
|
@@ -85,6 +92,9 @@ exports.releaseStyles = {
|
|
|
85
92
|
modules: { type: 'object' },
|
|
86
93
|
themes: { type: 'object' },
|
|
87
94
|
animations: { type: 'object' },
|
|
95
|
+
statics: { type: 'object' },
|
|
96
|
+
values: { type: 'object' },
|
|
97
|
+
complete: { type: 'boolean' },
|
|
88
98
|
styleProp: { type: 'string' },
|
|
89
99
|
active: { type: 'boolean' },
|
|
90
100
|
},
|
|
@@ -94,43 +104,165 @@ exports.releaseStyles = {
|
|
|
94
104
|
},
|
|
95
105
|
create(context) {
|
|
96
106
|
const raw = (context.options[0] ?? {});
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
107
|
+
const declared = raw.modules?.[context.filename];
|
|
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] ?? []);
|
|
100
117
|
const styleProp = raw.styleProp ?? 'classStyle';
|
|
101
118
|
const sourceCode = context.sourceCode;
|
|
102
|
-
|
|
103
|
-
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
|
+
};
|
|
187
|
+
const specifierFor = (module) => {
|
|
188
|
+
if (!module.target)
|
|
189
|
+
return module.source;
|
|
190
|
+
const relative = path
|
|
191
|
+
.relative(path.dirname(context.filename), module.target)
|
|
192
|
+
.split(path.sep)
|
|
193
|
+
.join('/');
|
|
194
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
195
|
+
};
|
|
196
|
+
const importOf = (specifiers, source) => {
|
|
197
|
+
const defaultImport = specifiers.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
|
|
198
|
+
const namedImports = specifiers.filter((specifier) => specifier.type === 'ImportSpecifier');
|
|
199
|
+
const clauses = [];
|
|
200
|
+
if (defaultImport)
|
|
201
|
+
clauses.push(sourceCode.getText(defaultImport));
|
|
202
|
+
if (namedImports.length > 0) {
|
|
203
|
+
clauses.push(`{ ${namedImports
|
|
204
|
+
.map((specifier) => sourceCode.getText(specifier))
|
|
205
|
+
.join(', ')} }`);
|
|
206
|
+
}
|
|
207
|
+
return `import ${clauses.join(', ')} from ${sourceCode.getText(source)};`;
|
|
208
|
+
};
|
|
209
|
+
const importedFrom = (specifier, table) => {
|
|
210
|
+
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
211
|
+
return resolved ? table?.[resolved] : undefined;
|
|
212
|
+
};
|
|
213
|
+
const byTarget = new Map(Object.values(raw.modules ?? {}).flatMap((module) => module.target ? [[module.target, module]] : []));
|
|
104
214
|
const importedReleasedModule = (specifier) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
return source ? raw.modules?.[source] : undefined;
|
|
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;
|
|
111
220
|
};
|
|
112
|
-
const importedThemeBindings = (specifier) =>
|
|
113
|
-
|
|
221
|
+
const importedThemeBindings = (specifier) => importedFrom(specifier, raw.themes);
|
|
222
|
+
const importedAnimationBindings = (specifier) => importedFrom(specifier, raw.animations);
|
|
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;
|
|
230
|
+
const releasedRange = () => {
|
|
231
|
+
if (!entry)
|
|
114
232
|
return undefined;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
233
|
+
for (const statement of sourceCode.ast.body) {
|
|
234
|
+
const declaration = statement.type === 'ExportNamedDeclaration'
|
|
235
|
+
? statement.declaration
|
|
236
|
+
: statement;
|
|
237
|
+
if (declaration?.type !== 'VariableDeclaration')
|
|
238
|
+
continue;
|
|
239
|
+
for (const candidate of declaration.declarations) {
|
|
240
|
+
if (candidate.id.type === 'Identifier' &&
|
|
241
|
+
candidate.id.name === entry.binding &&
|
|
242
|
+
isCreate(candidate.init))
|
|
243
|
+
return statement.range;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
119
247
|
};
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
248
|
+
const onlyUsedByReleasedStyles = (local) => {
|
|
249
|
+
const range = releasedRange();
|
|
250
|
+
if (!range)
|
|
251
|
+
return false;
|
|
252
|
+
const scope = sourceCode.getScope(sourceCode.ast);
|
|
253
|
+
const moduleScope = scope.type === 'global' ? (scope.childScopes[0] ?? scope) : scope;
|
|
254
|
+
const variable = moduleScope.variables.find((candidate) => candidate.name === local);
|
|
255
|
+
if (!variable || variable.references.length === 0)
|
|
256
|
+
return false;
|
|
257
|
+
return variable.references.every((reference) => reference.identifier.range[0] >= range[0] &&
|
|
258
|
+
reference.identifier.range[1] <= range[1]);
|
|
127
259
|
};
|
|
128
|
-
const removeImportedBindings = (node, bindings, messageId) => {
|
|
260
|
+
const removeImportedBindings = (node, bindings, messageId, dead) => {
|
|
129
261
|
const removed = node.specifiers.filter((specifier) => {
|
|
130
262
|
if (specifier.type !== 'ImportSpecifier')
|
|
131
263
|
return false;
|
|
132
264
|
const name = specifier.imported.name ?? specifier.imported.value;
|
|
133
|
-
return bindings.includes(name);
|
|
265
|
+
return bindings.includes(name) && (!dead || dead(specifier));
|
|
134
266
|
});
|
|
135
267
|
if (removed.length === 0)
|
|
136
268
|
return false;
|
|
@@ -138,32 +270,205 @@ exports.releaseStyles = {
|
|
|
138
270
|
context.report({
|
|
139
271
|
node,
|
|
140
272
|
messageId,
|
|
141
|
-
fix: (fixer) =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const defaultImport = retained.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
|
|
145
|
-
const namedImports = retained.filter((specifier) => specifier.type === 'ImportSpecifier');
|
|
146
|
-
const clauses = [];
|
|
147
|
-
if (defaultImport)
|
|
148
|
-
clauses.push(sourceCode.getText(defaultImport));
|
|
149
|
-
if (namedImports.length > 0) {
|
|
150
|
-
clauses.push(`{ ${namedImports
|
|
151
|
-
.map((specifier) => sourceCode.getText(specifier))
|
|
152
|
-
.join(', ')} }`);
|
|
153
|
-
}
|
|
154
|
-
return fixer.replaceText(node, `import ${clauses.join(', ')} from ${sourceCode.getText(node.source)};`);
|
|
155
|
-
},
|
|
273
|
+
fix: (fixer) => retained.length === 0
|
|
274
|
+
? removeStatement(fixer, node)
|
|
275
|
+
: fixer.replaceText(node, importOf(retained, node.source)),
|
|
156
276
|
});
|
|
157
277
|
return true;
|
|
158
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
|
+
};
|
|
159
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
|
+
},
|
|
160
464
|
ImportDeclaration(node) {
|
|
161
|
-
if (
|
|
465
|
+
if (!coreNeeded &&
|
|
466
|
+
(entry || themeBindings.length > 0 || animationBindings.length > 0) &&
|
|
162
467
|
node.source.value === '@plumeria/core') {
|
|
163
468
|
context.report({
|
|
164
469
|
node,
|
|
165
470
|
messageId: 'core',
|
|
166
|
-
fix: (fixer) => fixer
|
|
471
|
+
fix: (fixer) => removeStatement(fixer, node),
|
|
167
472
|
});
|
|
168
473
|
return;
|
|
169
474
|
}
|
|
@@ -177,16 +482,28 @@ exports.releaseStyles = {
|
|
|
177
482
|
if (removeImportedBindings(node, importedThemes, 'themeImport'))
|
|
178
483
|
return;
|
|
179
484
|
}
|
|
485
|
+
const importedStatics = importedStaticBindings(String(node.source.value));
|
|
486
|
+
if (importedStatics) {
|
|
487
|
+
if (removeImportedBindings(node, importedStatics, 'staticImport', (specifier) => onlyUsedByReleasedStyles(specifier.local.name)))
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
180
490
|
const importedModule = importedReleasedModule(String(node.source.value));
|
|
181
491
|
if (importedModule) {
|
|
182
|
-
active = true;
|
|
183
492
|
const importedBinding = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
184
493
|
(specifier.imported.name ?? specifier.imported.value) ===
|
|
185
494
|
importedModule.binding);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
495
|
+
const local = importedBinding?.local.name ?? importedModule.binding;
|
|
496
|
+
if (importedModule.definitionOnly && importedBinding) {
|
|
497
|
+
const retained = node.specifiers.filter((specifier) => specifier !== importedBinding);
|
|
498
|
+
const released = `import ${local} from '${specifierFor(importedModule)}';`;
|
|
499
|
+
context.report({
|
|
500
|
+
node,
|
|
501
|
+
messageId: 'definition',
|
|
502
|
+
fix: (fixer) => fixer.replaceText(node, retained.length === 0
|
|
503
|
+
? released
|
|
504
|
+
: `${released}\n${importOf(retained, node.source)}`),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
190
507
|
}
|
|
191
508
|
},
|
|
192
509
|
VariableDeclaration(node) {
|
|
@@ -212,14 +529,24 @@ exports.releaseStyles = {
|
|
|
212
529
|
: path[1] === 'keyframes' || path[1] === 'viewTransition'
|
|
213
530
|
? 'animation'
|
|
214
531
|
: 'static',
|
|
215
|
-
fix: (fixer) => fixer
|
|
532
|
+
fix: (fixer) => removeStatement(fixer, target),
|
|
216
533
|
});
|
|
217
534
|
return;
|
|
218
535
|
}
|
|
219
536
|
if (!entry)
|
|
220
537
|
return;
|
|
221
|
-
if (declaration.
|
|
222
|
-
|
|
538
|
+
if (!isCreate(declaration.init))
|
|
539
|
+
return;
|
|
540
|
+
if (entry.aliases?.[declaration.id.name]) {
|
|
541
|
+
const aliased = node.parent?.type === 'ExportNamedDeclaration' ? node.parent : node;
|
|
542
|
+
context.report({
|
|
543
|
+
node: aliased,
|
|
544
|
+
messageId: 'definition',
|
|
545
|
+
fix: (fixer) => removeStatement(fixer, aliased),
|
|
546
|
+
});
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (declaration.id.name !== entry.binding)
|
|
223
550
|
return;
|
|
224
551
|
const exported = node.parent?.type === 'ExportNamedDeclaration';
|
|
225
552
|
const target = exported ? node.parent : node;
|
|
@@ -227,10 +554,115 @@ exports.releaseStyles = {
|
|
|
227
554
|
const replacement = exported
|
|
228
555
|
? `${imported}\nexport { ${entry.binding} };`
|
|
229
556
|
: imported;
|
|
557
|
+
const anchor = importAnchor();
|
|
230
558
|
context.report({
|
|
231
559
|
node: target,
|
|
232
560
|
messageId: 'definition',
|
|
233
|
-
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'),
|
|
600
|
+
});
|
|
601
|
+
},
|
|
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
|
+
}
|
|
624
|
+
if (!entry?.aliases || node.object.type !== 'Identifier')
|
|
625
|
+
return;
|
|
626
|
+
const alias = entry.aliases[node.object.name];
|
|
627
|
+
if (!alias)
|
|
628
|
+
return;
|
|
629
|
+
const key = node.computed
|
|
630
|
+
? node.property.type === 'Literal'
|
|
631
|
+
? String(node.property.value)
|
|
632
|
+
: undefined
|
|
633
|
+
: node.property.name;
|
|
634
|
+
const className = key ? alias[key] : undefined;
|
|
635
|
+
if (!className)
|
|
636
|
+
return;
|
|
637
|
+
const access = IDENTIFIER.test(className)
|
|
638
|
+
? `.${className}`
|
|
639
|
+
: `['${className}']`;
|
|
640
|
+
context.report({
|
|
641
|
+
node,
|
|
642
|
+
messageId: 'alias',
|
|
643
|
+
fix: (fixer) => fixer.replaceText(node, `${entry.binding}${access}`),
|
|
644
|
+
});
|
|
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),
|
|
234
666
|
});
|
|
235
667
|
},
|
|
236
668
|
JSXAttribute(node) {
|
|
@@ -241,29 +673,49 @@ exports.releaseStyles = {
|
|
|
241
673
|
const expression = node.value?.type === 'JSXExpressionContainer'
|
|
242
674
|
? node.value.expression
|
|
243
675
|
: undefined;
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
});
|
|
707
|
+
};
|
|
708
|
+
const mergedClass = (elements) => {
|
|
709
|
+
if (elements.length < 2)
|
|
710
|
+
return undefined;
|
|
711
|
+
const resolved = elements.map(styleKey);
|
|
712
|
+
if (resolved.some((part) => !part))
|
|
251
713
|
return undefined;
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
? String(value.callee.property.value)
|
|
255
|
-
: undefined
|
|
256
|
-
: value.callee.property.name;
|
|
257
|
-
const func = key ? moduleInfo.functions?.[key] : undefined;
|
|
258
|
-
if (!key || !func || func.variables.length !== value.arguments.length)
|
|
714
|
+
const module = resolved[0].module;
|
|
715
|
+
if (resolved.some((part) => part.module !== module))
|
|
259
716
|
return undefined;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
declarations: func.variables.map((variable, index) => {
|
|
263
|
-
const argument = sourceCode.getText(value.arguments[index]);
|
|
264
|
-
return `'${variable}': ${argument}`;
|
|
265
|
-
}),
|
|
266
|
-
};
|
|
717
|
+
const name = module.merges?.[resolved.map((part) => part.key).join('|')];
|
|
718
|
+
return name ? accessOf(module, name) : undefined;
|
|
267
719
|
};
|
|
268
720
|
const replaceWithFunctionStyle = (className, declarations) => {
|
|
269
721
|
const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
|
|
@@ -299,9 +751,7 @@ exports.releaseStyles = {
|
|
|
299
751
|
return;
|
|
300
752
|
}
|
|
301
753
|
if (expression?.type === 'CallExpression' &&
|
|
302
|
-
expression.callee
|
|
303
|
-
expression.callee.object.type === 'Identifier' &&
|
|
304
|
-
expression.callee.object.name === moduleInfo?.binding) {
|
|
754
|
+
moduleOf(expression.callee)) {
|
|
305
755
|
context.report({
|
|
306
756
|
node,
|
|
307
757
|
messageId: 'unsupportedFunctionStyle',
|
|
@@ -310,34 +760,70 @@ exports.releaseStyles = {
|
|
|
310
760
|
}
|
|
311
761
|
if (expression?.type === 'ArrayExpression') {
|
|
312
762
|
const elements = expression.elements.filter(Boolean);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!resolved)
|
|
318
|
-
return sourceCode.getText(element);
|
|
319
|
-
declarations.push(...resolved.declarations);
|
|
320
|
-
return resolved.className;
|
|
321
|
-
});
|
|
322
|
-
const replacement = elements.some(needsFilter)
|
|
323
|
-
? `[${texts.join(', ')}].filter(Boolean).join(' ')`
|
|
324
|
-
: `\`${texts.map((text) => `\${${text}}`).join(' ')}\``;
|
|
325
|
-
if (declarations.length > 0) {
|
|
326
|
-
replaceWithFunctionStyle(replacement, declarations);
|
|
763
|
+
const merged = mergedClass(elements);
|
|
764
|
+
if (merged) {
|
|
765
|
+
if (carried) {
|
|
766
|
+
takeProp(merged);
|
|
327
767
|
return;
|
|
328
768
|
}
|
|
329
769
|
context.report({
|
|
330
770
|
node: expression,
|
|
331
771
|
messageId: 'composition',
|
|
332
|
-
fix: (fixer) => fixer.replaceText(expression,
|
|
772
|
+
fix: (fixer) => fixer.replaceText(expression, merged),
|
|
333
773
|
});
|
|
774
|
+
takeProp();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (elements.length === 0) {
|
|
778
|
+
takeProp();
|
|
779
|
+
return;
|
|
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;
|
|
334
815
|
}
|
|
816
|
+
context.report({
|
|
817
|
+
node: expression,
|
|
818
|
+
messageId: 'composition',
|
|
819
|
+
fix: (fixer) => fixer.replaceText(expression, replacement),
|
|
820
|
+
});
|
|
821
|
+
takeProp();
|
|
822
|
+
return;
|
|
335
823
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
fix: (fixer) => fixer.replaceText(node.name, 'className'),
|
|
340
|
-
});
|
|
824
|
+
if (!complete && expression && resolveText(expression) === undefined)
|
|
825
|
+
return;
|
|
826
|
+
takeProp();
|
|
341
827
|
},
|
|
342
828
|
};
|
|
343
829
|
},
|