@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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Rule } from 'eslint';
|
|
2
2
|
export interface ModuleMap {
|
|
3
3
|
source: string;
|
|
4
|
+
target?: string;
|
|
4
5
|
names: Record<string, string>;
|
|
5
6
|
composes?: Record<string, string[]>;
|
|
7
|
+
functions?: Record<string, string[]>;
|
|
6
8
|
}
|
|
7
9
|
export interface AdoptStylesOptions {
|
|
8
10
|
modules: Record<string, ModuleMap>;
|
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.adoptStyles = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const resolve_1 = require("../resolve");
|
|
4
39
|
const CORE = '@plumeria/core';
|
|
5
40
|
const readOptions = (context) => {
|
|
6
41
|
const raw = (context.options[0] ?? {});
|
|
@@ -22,6 +57,9 @@ exports.adoptStyles = {
|
|
|
22
57
|
key: 'The generated key for "{{from}}" is "{{to}}".',
|
|
23
58
|
prop: 'Styles are carried by the "{{prop}}" prop.',
|
|
24
59
|
composes: '"{{key}}" composed other classes; pass them as an array.',
|
|
60
|
+
value: 'A style read as a value goes back through `css.use`.',
|
|
61
|
+
joined: 'A joined class list is written as an array.',
|
|
62
|
+
call: 'The custom properties are the arguments of a function style.',
|
|
25
63
|
},
|
|
26
64
|
schema: [
|
|
27
65
|
{
|
|
@@ -39,9 +77,144 @@ exports.adoptStyles = {
|
|
|
39
77
|
const bindings = new Map();
|
|
40
78
|
let hasCore = false;
|
|
41
79
|
let stylesTaken = false;
|
|
80
|
+
let coreLocal;
|
|
81
|
+
let readsValue = false;
|
|
82
|
+
let several = false;
|
|
83
|
+
const stylingPosition = (node) => {
|
|
84
|
+
let current = node;
|
|
85
|
+
while (current.parent &&
|
|
86
|
+
(current.parent.type === 'ArrayExpression' ||
|
|
87
|
+
current.parent.type === 'LogicalExpression' ||
|
|
88
|
+
current.parent.type === 'ConditionalExpression' ||
|
|
89
|
+
current.parent.type === 'JSXExpressionContainer'))
|
|
90
|
+
current = current.parent;
|
|
91
|
+
return (current.parent?.type === 'JSXAttribute' &&
|
|
92
|
+
(current.parent.name?.name === 'className' ||
|
|
93
|
+
current.parent.name?.name === styleProp));
|
|
94
|
+
};
|
|
95
|
+
const joinedElements = (node) => {
|
|
96
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length > 1) {
|
|
97
|
+
const spaced = node.quasis.every((quasi, index) => index === 0 || index === node.quasis.length - 1
|
|
98
|
+
? quasi.value.cooked === ''
|
|
99
|
+
: quasi.value.cooked === ' ');
|
|
100
|
+
return spaced ? node.expressions : undefined;
|
|
101
|
+
}
|
|
102
|
+
if (node?.type !== 'CallExpression' ||
|
|
103
|
+
node.callee.type !== 'MemberExpression' ||
|
|
104
|
+
node.callee.property?.name !== 'join' ||
|
|
105
|
+
node.arguments.length !== 1 ||
|
|
106
|
+
node.arguments[0].value !== ' ')
|
|
107
|
+
return undefined;
|
|
108
|
+
const joined = node.callee.object;
|
|
109
|
+
if (joined?.type === 'ArrayExpression')
|
|
110
|
+
return joined.elements.filter(Boolean);
|
|
111
|
+
return joined?.type === 'CallExpression' &&
|
|
112
|
+
joined.callee.type === 'MemberExpression' &&
|
|
113
|
+
joined.callee.property?.name === 'filter' &&
|
|
114
|
+
joined.callee.object?.type === 'ArrayExpression'
|
|
115
|
+
? joined.callee.object.elements.filter(Boolean)
|
|
116
|
+
: undefined;
|
|
117
|
+
};
|
|
118
|
+
const intrinsic = (element) => {
|
|
119
|
+
const name = element?.name;
|
|
120
|
+
return name?.type === 'JSXIdentifier' && /^[a-z]/.test(String(name.name));
|
|
121
|
+
};
|
|
122
|
+
const renamedText = (element) => {
|
|
123
|
+
const read = styleRead(element);
|
|
124
|
+
const binding = read?.object?.type === 'Identifier'
|
|
125
|
+
? bindings.get(read.object.name)
|
|
126
|
+
: undefined;
|
|
127
|
+
if (!binding)
|
|
128
|
+
return context.sourceCode.getText(element);
|
|
129
|
+
const written = read.computed && read.property.type === 'Literal'
|
|
130
|
+
? String(read.property.value)
|
|
131
|
+
: !read.computed && read.property.type === 'Identifier'
|
|
132
|
+
? read.property.name
|
|
133
|
+
: undefined;
|
|
134
|
+
if (written === undefined)
|
|
135
|
+
return context.sourceCode.getText(element);
|
|
136
|
+
const access = `${binding.local}.${binding.entry.names[written] ?? written}`;
|
|
137
|
+
return element.type === 'LogicalExpression'
|
|
138
|
+
? `${context.sourceCode.getText(element.left)} ${element.operator} ${access}`
|
|
139
|
+
: access;
|
|
140
|
+
};
|
|
141
|
+
const styleRead = (node) => node?.type === 'LogicalExpression'
|
|
142
|
+
? styleRead(node.right)
|
|
143
|
+
: node?.type === 'MemberExpression'
|
|
144
|
+
? node
|
|
145
|
+
: undefined;
|
|
146
|
+
const insideUse = (node) => {
|
|
147
|
+
let current = node;
|
|
148
|
+
while (current.parent &&
|
|
149
|
+
(current.parent.type === 'LogicalExpression' ||
|
|
150
|
+
current.parent.type === 'ConditionalExpression' ||
|
|
151
|
+
current.parent.type === 'ArrayExpression'))
|
|
152
|
+
current = current.parent;
|
|
153
|
+
return (current.parent?.type === 'CallExpression' &&
|
|
154
|
+
current.parent.callee?.type === 'MemberExpression' &&
|
|
155
|
+
current.parent.callee.property?.name === 'use');
|
|
156
|
+
};
|
|
42
157
|
return {
|
|
43
158
|
Program(program) {
|
|
44
159
|
hasCore = program.body.some((n) => n.type === 'ImportDeclaration' && n.source.value === CORE);
|
|
160
|
+
const locals = new Set();
|
|
161
|
+
let seen = 0;
|
|
162
|
+
for (const statement of program.body) {
|
|
163
|
+
if (statement.type !== 'ImportDeclaration')
|
|
164
|
+
continue;
|
|
165
|
+
if (statement.source.value === CORE) {
|
|
166
|
+
for (const specifier of statement.specifiers)
|
|
167
|
+
if (specifier.type === 'ImportNamespaceSpecifier' ||
|
|
168
|
+
(specifier.type === 'ImportSpecifier' &&
|
|
169
|
+
(specifier.imported.name ?? specifier.imported.value) ===
|
|
170
|
+
'css'))
|
|
171
|
+
coreLocal = specifier.local.name;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const target = (0, resolve_1.resolveSourcePath)(String(statement.source.value), context.filename);
|
|
175
|
+
if (!target || !modules[target])
|
|
176
|
+
continue;
|
|
177
|
+
for (const specifier of statement.specifiers)
|
|
178
|
+
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
179
|
+
locals.add(specifier.local.name);
|
|
180
|
+
seen += 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
several = seen > 1;
|
|
184
|
+
const visit = (node, styling) => {
|
|
185
|
+
if (!node || typeof node !== 'object')
|
|
186
|
+
return;
|
|
187
|
+
if (Array.isArray(node)) {
|
|
188
|
+
for (const child of node)
|
|
189
|
+
visit(child, styling);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (typeof node.type !== 'string')
|
|
193
|
+
return;
|
|
194
|
+
const inside = styling ||
|
|
195
|
+
(node.type === 'JSXAttribute' &&
|
|
196
|
+
(node.name?.name === 'className' ||
|
|
197
|
+
node.name?.name === styleProp));
|
|
198
|
+
if (!inside &&
|
|
199
|
+
node.type === 'MemberExpression' &&
|
|
200
|
+
locals.has(node.object?.name))
|
|
201
|
+
readsValue = true;
|
|
202
|
+
if (node.type === 'JSXAttribute' &&
|
|
203
|
+
node.name?.name === 'className' &&
|
|
204
|
+
node.parent?.name?.type === 'JSXIdentifier' &&
|
|
205
|
+
/^[A-Z]/.test(String(node.parent.name.name)) &&
|
|
206
|
+
node.value?.type === 'JSXExpressionContainer' &&
|
|
207
|
+
joinedElements(node.value.expression))
|
|
208
|
+
readsValue = true;
|
|
209
|
+
for (const key of Object.keys(node)) {
|
|
210
|
+
if (key === 'parent' || key === 'loc' || key === 'range')
|
|
211
|
+
continue;
|
|
212
|
+
visit(node[key], inside);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
visit(program.body, false);
|
|
216
|
+
if (readsValue && !coreLocal)
|
|
217
|
+
coreLocal = 'css';
|
|
45
218
|
stylesTaken = program.body.some((n) => {
|
|
46
219
|
if (n.type === 'VariableDeclaration') {
|
|
47
220
|
return n.declarations.some((d) => d.id?.name === 'styles');
|
|
@@ -56,28 +229,55 @@ exports.adoptStyles = {
|
|
|
56
229
|
},
|
|
57
230
|
ImportDeclaration(node) {
|
|
58
231
|
const specifier = String(node.source.value);
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
232
|
+
const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
|
|
233
|
+
const named = Object.entries(modules).filter(([key]) => key === specifier ||
|
|
234
|
+
path.basename(key) === path.basename(specifier));
|
|
235
|
+
const entry = (resolved ? modules[resolved] : undefined) ??
|
|
236
|
+
(named.length === 1 ? named[0][1] : undefined);
|
|
237
|
+
if (!entry) {
|
|
238
|
+
const adopted = resolved &&
|
|
239
|
+
Object.values(modules).find((candidate) => candidate.target === resolved);
|
|
240
|
+
if (!adopted)
|
|
241
|
+
return;
|
|
242
|
+
const already = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
243
|
+
(specifier.imported.name ?? specifier.imported.value) ===
|
|
244
|
+
'styles');
|
|
245
|
+
if (already)
|
|
246
|
+
bindings.set(already.local.name, {
|
|
247
|
+
entry: adopted,
|
|
248
|
+
local: already.local.name,
|
|
249
|
+
});
|
|
62
250
|
return;
|
|
251
|
+
}
|
|
252
|
+
const from = entry.target && path.isAbsolute(context.filename)
|
|
253
|
+
? (() => {
|
|
254
|
+
const relative = path
|
|
255
|
+
.relative(path.dirname(context.filename), entry.target)
|
|
256
|
+
.split(path.sep)
|
|
257
|
+
.join('/')
|
|
258
|
+
.replace(/\.ts$/, '');
|
|
259
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
260
|
+
})()
|
|
261
|
+
: entry.source;
|
|
63
262
|
const local = node.specifiers.find((s) => s.type === 'ImportDefaultSpecifier');
|
|
64
263
|
if (!local)
|
|
65
264
|
return;
|
|
66
265
|
const name = local.local.name;
|
|
67
|
-
const renamed = stylesTaken && name !== 'styles' ? name : 'styles';
|
|
266
|
+
const renamed = several || (stylesTaken && name !== 'styles') ? name : 'styles';
|
|
68
267
|
bindings.set(name, { entry, local: renamed });
|
|
69
268
|
const imported = renamed === 'styles'
|
|
70
|
-
? `import { styles } from '${
|
|
71
|
-
: `import { styles as ${renamed} } from '${
|
|
72
|
-
const
|
|
73
|
-
?
|
|
74
|
-
: `import '${CORE}'
|
|
269
|
+
? `import { styles } from '${from}';`
|
|
270
|
+
: `import { styles as ${renamed} } from '${from}';`;
|
|
271
|
+
const core = coreLocal
|
|
272
|
+
? `import * as ${coreLocal} from '${CORE}';`
|
|
273
|
+
: `import '${CORE}';`;
|
|
274
|
+
const replacement = hasCore ? imported : `${core}\n${imported}`;
|
|
75
275
|
if (!hasCore)
|
|
76
276
|
hasCore = true;
|
|
77
277
|
context.report({
|
|
78
278
|
node,
|
|
79
279
|
messageId: 'source',
|
|
80
|
-
data: { source:
|
|
280
|
+
data: { source: from },
|
|
81
281
|
fix: (fixer) => fixer.replaceText(node, replacement),
|
|
82
282
|
});
|
|
83
283
|
},
|
|
@@ -96,6 +296,14 @@ exports.adoptStyles = {
|
|
|
96
296
|
return;
|
|
97
297
|
const key = binding.entry.names[written] ?? written;
|
|
98
298
|
const local = binding.local;
|
|
299
|
+
if (coreLocal && !stylingPosition(node) && !insideUse(node)) {
|
|
300
|
+
context.report({
|
|
301
|
+
node,
|
|
302
|
+
messageId: 'value',
|
|
303
|
+
fix: (fixer) => fixer.replaceText(node, `${coreLocal}.use(${local}.${key})`),
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
99
307
|
if (key === written && local === node.object.name)
|
|
100
308
|
return;
|
|
101
309
|
context.report({
|
|
@@ -105,6 +313,82 @@ exports.adoptStyles = {
|
|
|
105
313
|
fix: (fixer) => fixer.replaceText(node, `${local}.${key}`),
|
|
106
314
|
});
|
|
107
315
|
},
|
|
316
|
+
[`JSXAttribute[name.name='${styleProp}']`](node) {
|
|
317
|
+
if (node.value?.type !== 'JSXExpressionContainer')
|
|
318
|
+
return;
|
|
319
|
+
const style = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
|
|
320
|
+
attribute.name?.name === 'style' &&
|
|
321
|
+
attribute.value?.type === 'JSXExpressionContainer' &&
|
|
322
|
+
attribute.value.expression.type === 'ObjectExpression');
|
|
323
|
+
if (!style)
|
|
324
|
+
return;
|
|
325
|
+
const properties = style.value.expression.properties;
|
|
326
|
+
const named = new Map();
|
|
327
|
+
for (const property of properties) {
|
|
328
|
+
if (property.type !== 'Property' || !property.computed)
|
|
329
|
+
continue;
|
|
330
|
+
const key = property.key.type === 'TSAsExpression'
|
|
331
|
+
? property.key.expression
|
|
332
|
+
: property.key;
|
|
333
|
+
if (key.type === 'Literal' && typeof key.value === 'string')
|
|
334
|
+
named.set(key.value, property);
|
|
335
|
+
}
|
|
336
|
+
if (named.size === 0)
|
|
337
|
+
return;
|
|
338
|
+
const consumed = new Set();
|
|
339
|
+
const call = (read) => {
|
|
340
|
+
const binding = read?.type === 'MemberExpression' &&
|
|
341
|
+
read.object.type === 'Identifier'
|
|
342
|
+
? bindings.get(read.object.name)
|
|
343
|
+
: undefined;
|
|
344
|
+
if (!binding)
|
|
345
|
+
return undefined;
|
|
346
|
+
const written = !read.computed && read.property.type === 'Identifier'
|
|
347
|
+
? read.property.name
|
|
348
|
+
: read.computed && read.property.type === 'Literal'
|
|
349
|
+
? String(read.property.value)
|
|
350
|
+
: undefined;
|
|
351
|
+
const key = written && (binding.entry.names[written] ?? written);
|
|
352
|
+
const variables = key ? binding.entry.functions?.[key] : undefined;
|
|
353
|
+
if (!variables || variables.some((v) => !named.has(v)))
|
|
354
|
+
return undefined;
|
|
355
|
+
for (const variable of variables)
|
|
356
|
+
consumed.add(named.get(variable));
|
|
357
|
+
return `${binding.local}.${key}(${variables
|
|
358
|
+
.map((variable) => context.sourceCode.getText(named.get(variable).value))
|
|
359
|
+
.join(', ')})`;
|
|
360
|
+
};
|
|
361
|
+
const expression = node.value.expression;
|
|
362
|
+
const elements = expression.type === 'ArrayExpression'
|
|
363
|
+
? expression.elements.filter(Boolean)
|
|
364
|
+
: [expression];
|
|
365
|
+
const texts = elements.map((element) => {
|
|
366
|
+
const called = call(styleRead(element));
|
|
367
|
+
if (!called)
|
|
368
|
+
return context.sourceCode.getText(element);
|
|
369
|
+
return element.type === 'LogicalExpression'
|
|
370
|
+
? `${context.sourceCode.getText(element.left)} ${element.operator} ${called}`
|
|
371
|
+
: called;
|
|
372
|
+
});
|
|
373
|
+
if (consumed.size === 0)
|
|
374
|
+
return;
|
|
375
|
+
const kept = properties.filter((property) => !consumed.has(property));
|
|
376
|
+
const replacement = expression.type === 'ArrayExpression'
|
|
377
|
+
? `[${texts.join(', ')}]`
|
|
378
|
+
: texts[0];
|
|
379
|
+
context.report({
|
|
380
|
+
node,
|
|
381
|
+
messageId: 'call',
|
|
382
|
+
fix: (fixer) => [
|
|
383
|
+
fixer.replaceText(expression, replacement),
|
|
384
|
+
kept.length === 0
|
|
385
|
+
? fixer.removeRange([style.range[0] - 1, style.range[1]])
|
|
386
|
+
: fixer.replaceText(style.value.expression, `{ ${kept
|
|
387
|
+
.map((property) => context.sourceCode.getText(property))
|
|
388
|
+
.join(', ')} }`),
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
},
|
|
108
392
|
JSXAttribute(node) {
|
|
109
393
|
if (node.name.type !== 'JSXIdentifier')
|
|
110
394
|
return;
|
|
@@ -114,6 +398,32 @@ exports.adoptStyles = {
|
|
|
114
398
|
if (!value || value.type !== 'JSXExpressionContainer')
|
|
115
399
|
return;
|
|
116
400
|
const expression = value.expression;
|
|
401
|
+
const joined = joinedElements(expression);
|
|
402
|
+
if (joined) {
|
|
403
|
+
if (!joined.some((element) => bindings.has(styleRead(element)?.object?.name)))
|
|
404
|
+
return;
|
|
405
|
+
const list = joined.map(renamedText).join(', ');
|
|
406
|
+
if (!intrinsic(node.parent) && coreLocal) {
|
|
407
|
+
context.report({
|
|
408
|
+
node: expression,
|
|
409
|
+
messageId: 'value',
|
|
410
|
+
fix: (fixer) => fixer.replaceText(expression, `${coreLocal}.use(${list})`),
|
|
411
|
+
});
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
context.report({
|
|
415
|
+
node: node.name,
|
|
416
|
+
messageId: 'prop',
|
|
417
|
+
data: { prop: styleProp },
|
|
418
|
+
fix: (fixer) => fixer.replaceText(node.name, styleProp),
|
|
419
|
+
});
|
|
420
|
+
context.report({
|
|
421
|
+
node: expression,
|
|
422
|
+
messageId: 'joined',
|
|
423
|
+
fix: (fixer) => fixer.replaceText(expression, `[${list}]`),
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
117
427
|
const root = expression.type === 'MemberExpression' &&
|
|
118
428
|
expression.object.type === 'Identifier'
|
|
119
429
|
? expression.object.name
|
|
@@ -9,9 +9,13 @@ export interface Converted {
|
|
|
9
9
|
code: string;
|
|
10
10
|
names: Record<string, string>;
|
|
11
11
|
composes: Record<string, string[]>;
|
|
12
|
+
functions: Record<string, string[]>;
|
|
12
13
|
reports: Report[];
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
interface Raw {
|
|
16
|
+
raw: string;
|
|
17
|
+
}
|
|
18
|
+
type Value = string | number | Raw;
|
|
15
19
|
export declare const toProperty: (name: string) => string;
|
|
16
20
|
export declare const toKey: (className: string) => string;
|
|
17
21
|
export declare const toValue: (raw: string) => Value;
|
|
@@ -41,6 +41,7 @@ const nest = (node, key) => {
|
|
|
41
41
|
node.nested.push([key, child]);
|
|
42
42
|
return child;
|
|
43
43
|
};
|
|
44
|
+
const isSpecificityBoost = (node) => node.type === 'pseudo' && /^:not\(#[\\_].*\)$/.test(node.toString());
|
|
44
45
|
const atRuleKeys = (rule) => {
|
|
45
46
|
const keys = [];
|
|
46
47
|
let parent = rule.parent;
|
|
@@ -57,7 +58,7 @@ const readSelector = (selector) => {
|
|
|
57
58
|
hint: 'Only a local class, or one local class under another, is converted.',
|
|
58
59
|
};
|
|
59
60
|
(0, postcss_selector_parser_1.default)((root) => {
|
|
60
|
-
const nodes = root.first.nodes.filter((n) => n.type !== 'string');
|
|
61
|
+
const nodes = root.first.nodes.filter((n) => n.type !== 'string' && !isSpecificityBoost(n));
|
|
61
62
|
const parts = [
|
|
62
63
|
{ pseudos: [] },
|
|
63
64
|
];
|
|
@@ -148,7 +149,16 @@ const serialise = (node, indent) => {
|
|
|
148
149
|
lines.push(`${indent}...${marker},`);
|
|
149
150
|
for (const [prop, value] of node.decls) {
|
|
150
151
|
const key = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(prop) ? prop : `'${prop}'`;
|
|
151
|
-
|
|
152
|
+
if (typeof value === 'object' && value.raw === prop) {
|
|
153
|
+
lines.push(`${indent}${prop},`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const written = typeof value === 'number'
|
|
157
|
+
? value
|
|
158
|
+
: typeof value === 'object'
|
|
159
|
+
? value.raw
|
|
160
|
+
: `'${value}'`;
|
|
161
|
+
lines.push(`${indent}${key}: ${written},`);
|
|
152
162
|
}
|
|
153
163
|
for (const [key, child] of node.nested) {
|
|
154
164
|
const written = key.startsWith('css.extended(') ? `[${key}]` : `'${key}'`;
|
|
@@ -224,12 +234,56 @@ function convertStylesheet(css) {
|
|
|
224
234
|
composes[target.key] = (composes[target.key] ?? []).concat(name.split(/\s+/).map(exports.toKey));
|
|
225
235
|
return;
|
|
226
236
|
}
|
|
227
|
-
|
|
237
|
+
const property = (0, exports.toProperty)(child.prop);
|
|
238
|
+
const held = node.decls.findIndex(([name]) => name === property);
|
|
239
|
+
if (held !== -1)
|
|
240
|
+
node.decls.splice(held, 1);
|
|
241
|
+
node.decls.push([property, (0, exports.toValue)(child.value)]);
|
|
228
242
|
});
|
|
229
243
|
}
|
|
230
244
|
});
|
|
245
|
+
const declared = new Set();
|
|
246
|
+
root.walkDecls((decl) => {
|
|
247
|
+
if (decl.prop.startsWith('--'))
|
|
248
|
+
declared.add(decl.prop);
|
|
249
|
+
});
|
|
250
|
+
const functions = {};
|
|
251
|
+
const VARIABLE = /^var\((--[A-Za-z0-9_-]+)\)$/;
|
|
252
|
+
const HASHED = /^x[0-9a-z]{7}$/;
|
|
253
|
+
const kebab = (name) => name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
254
|
+
const parameterise = (node, key, variables) => {
|
|
255
|
+
const owned = new RegExp(`^--[A-Za-z0-9_-]+-${kebab(key)}-([a-z0-9-]+)$`);
|
|
256
|
+
node.decls = node.decls.map(([prop, value]) => {
|
|
257
|
+
const match = typeof value === 'string' ? VARIABLE.exec(value.trim()) : null;
|
|
258
|
+
const variable = match?.[1];
|
|
259
|
+
if (!variable || declared.has(variable))
|
|
260
|
+
return [prop, value];
|
|
261
|
+
const parameter = owned.exec(variable)?.[1];
|
|
262
|
+
if (!parameter || HASHED.test(variable.slice(2).split('-')[0]))
|
|
263
|
+
return [prop, value];
|
|
264
|
+
if (!variables.includes(variable))
|
|
265
|
+
variables.push(variable);
|
|
266
|
+
return [prop, { raw: (0, exports.toKey)(parameter) }];
|
|
267
|
+
});
|
|
268
|
+
for (const [, child] of node.nested)
|
|
269
|
+
parameterise(child, key, variables);
|
|
270
|
+
};
|
|
271
|
+
for (const [key, node] of keys) {
|
|
272
|
+
const variables = [];
|
|
273
|
+
parameterise(node, key, variables);
|
|
274
|
+
if (variables.length > 0)
|
|
275
|
+
functions[key] = variables;
|
|
276
|
+
}
|
|
231
277
|
const body = [...keys.entries()]
|
|
232
|
-
.map(([key, node]) =>
|
|
278
|
+
.map(([key, node]) => {
|
|
279
|
+
const variables = functions[key];
|
|
280
|
+
if (!variables)
|
|
281
|
+
return ` ${key}: {\n${serialise(node, ' ')}\n },`;
|
|
282
|
+
const params = variables
|
|
283
|
+
.map((variable) => `${(0, exports.toKey)(variable.split('-').pop())}: string | number`)
|
|
284
|
+
.join(', ');
|
|
285
|
+
return ` ${key}: (${params}) => ({\n${serialise(node, ' ')}\n }),`;
|
|
286
|
+
})
|
|
233
287
|
.join('\n');
|
|
234
288
|
const code = `import * as css from '@plumeria/core';
|
|
235
289
|
|
|
@@ -237,5 +291,5 @@ export const styles = css.create({
|
|
|
237
291
|
${body}
|
|
238
292
|
});
|
|
239
293
|
`;
|
|
240
|
-
return { code, names, composes, reports };
|
|
294
|
+
return { code, names, composes, functions, reports };
|
|
241
295
|
}
|
|
@@ -14,9 +14,25 @@ export interface PlumeriaModule {
|
|
|
14
14
|
variables: string[];
|
|
15
15
|
}>;
|
|
16
16
|
aliases: Record<string, Record<string, string>>;
|
|
17
|
+
merges: Record<string, string>;
|
|
18
|
+
overrides: Record<string, Record<number, string>>;
|
|
17
19
|
definitionOnly: boolean;
|
|
18
20
|
reports: PlumeriaReport[];
|
|
19
21
|
}
|
|
22
|
+
export interface PlumeriaComposition {
|
|
23
|
+
parts: {
|
|
24
|
+
binding: string;
|
|
25
|
+
key: string;
|
|
26
|
+
}[];
|
|
27
|
+
slots: {
|
|
28
|
+
binding: string;
|
|
29
|
+
key: string;
|
|
30
|
+
}[][];
|
|
31
|
+
file: string;
|
|
32
|
+
line: number;
|
|
33
|
+
column: number;
|
|
34
|
+
}
|
|
35
|
+
export declare const compositionSignature: (classNames: string[]) => string;
|
|
20
36
|
export interface ThemeExtraction {
|
|
21
37
|
bindings: Record<string, Record<string, string>>;
|
|
22
38
|
globalCss: string;
|
|
@@ -42,4 +58,4 @@ export declare const __private: {
|
|
|
42
58
|
export declare function extractPlumeriaThemes(source: string): ThemeExtraction;
|
|
43
59
|
export declare function extractPlumeriaStatics(source: string, externalValues?: Record<string, unknown>): StaticExtraction;
|
|
44
60
|
export declare function extractPlumeriaAnimations(source: string, externalValues?: Record<string, unknown>): AnimationExtraction;
|
|
45
|
-
export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown
|
|
61
|
+
export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown>, compositions?: PlumeriaComposition[]): PlumeriaModule | null;
|