@csszyx/compiler 0.7.0 → 0.8.0
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 +27 -2
- package/dist/color-var.cjs +3 -28
- package/dist/{color-var.js → color-var.mjs} +2 -6
- package/dist/index.cjs +2073 -3202
- package/dist/index.d.cts +12118 -111
- package/dist/index.d.mts +13484 -0
- package/dist/index.mjs +2706 -0
- package/dist/shared/compiler.BIUVmI0H.mjs +2271 -0
- package/dist/{index.js → shared/compiler.C6jT0mcT.cjs} +75 -1609
- package/dist/transform-core.cjs +17 -0
- package/dist/transform-core.d.cts +86 -0
- package/dist/transform-core.d.mts +86 -0
- package/dist/transform-core.mjs +1 -0
- package/package.json +41 -19
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/index.d.ts +0 -1477
- /package/dist/{color-var.d.ts → color-var.d.mts} +0 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2706 @@
|
|
|
1
|
+
import { init, version, transform_sz } from '@csszyx/core';
|
|
2
|
+
import { t as transform, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, s as stripInvalidColorStrings } from './shared/compiler.BIUVmI0H.mjs';
|
|
3
|
+
export { B as BOOLEAN_SHORTHANDS, d as PROPERTY_CATEGORY_MAP, S as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.BIUVmI0H.mjs';
|
|
4
|
+
import * as t from '@babel/types';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import * as babel from '@babel/core';
|
|
7
|
+
import MagicString from 'magic-string';
|
|
8
|
+
import { parseSync } from 'oxc-parser';
|
|
9
|
+
|
|
10
|
+
const AST_BUDGET = 5e4;
|
|
11
|
+
class ASTBudgetExceededError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Source filename if known (set by callers via the `filename` argument
|
|
14
|
+
* to {@link transformSourceCode}). May be `undefined` when invoked
|
|
15
|
+
* with raw source strings outside a build context (e.g. tests).
|
|
16
|
+
*/
|
|
17
|
+
filename;
|
|
18
|
+
/**
|
|
19
|
+
* Node count at the point traversal was aborted. Always strictly
|
|
20
|
+
* greater than {@link budget}.
|
|
21
|
+
*/
|
|
22
|
+
nodeCount;
|
|
23
|
+
/**
|
|
24
|
+
* Effective budget that was exceeded. Equals the third constructor
|
|
25
|
+
* argument, the caller's `options.astBudget`, or {@link AST_BUDGET}
|
|
26
|
+
* when no override was supplied.
|
|
27
|
+
*/
|
|
28
|
+
budget;
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param filename Source filename if known, otherwise `undefined`.
|
|
32
|
+
* @param nodeCount Node count at the point traversal was aborted.
|
|
33
|
+
* @param budget Effective budget that was exceeded. Defaults to
|
|
34
|
+
* {@link AST_BUDGET} for backwards compatibility with callers that
|
|
35
|
+
* don't pass an override.
|
|
36
|
+
*/
|
|
37
|
+
constructor(filename, nodeCount, budget = AST_BUDGET) {
|
|
38
|
+
const where = filename ?? "<anonymous>";
|
|
39
|
+
super(
|
|
40
|
+
`[csszyx] AST budget exceeded: ${where} has more than ${budget} nodes (traversal aborted at ${nodeCount}). Files this large are almost always machine-generated and should be excluded from sz transformation. Either exclude the file from the plugin (Vite: \`csszyx({ exclude: [/large-data\\.ts$/] })\`), or raise the limit globally with \`csszyx({ build: { astBudgetLimit: 100_000 } })\`.`
|
|
41
|
+
);
|
|
42
|
+
this.name = "ASTBudgetExceededError";
|
|
43
|
+
this.filename = filename;
|
|
44
|
+
this.nodeCount = nodeCount;
|
|
45
|
+
this.budget = budget;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function generateInlineRecoveryToken(filename, line, column, elementType) {
|
|
50
|
+
const input = `${filename}:${line}:${column}:${elementType}`;
|
|
51
|
+
return createHash("sha256").update(input).digest("hex").substring(0, 12);
|
|
52
|
+
}
|
|
53
|
+
function isValidInlineRecoveryMode(value) {
|
|
54
|
+
return value === "csr" || value === "dev-only";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function transformSourceCode(source, filename, options) {
|
|
58
|
+
const astBudget = options?.astBudget ?? AST_BUDGET;
|
|
59
|
+
let usesRuntime = false;
|
|
60
|
+
let usesMerge = false;
|
|
61
|
+
let usesColorVar = false;
|
|
62
|
+
let transformed = false;
|
|
63
|
+
const collectedClasses = /* @__PURE__ */ new Set();
|
|
64
|
+
const rawClassNames = /* @__PURE__ */ new Set();
|
|
65
|
+
const diagnostics = [];
|
|
66
|
+
const recoveryTokens = /* @__PURE__ */ new Map();
|
|
67
|
+
if (!source.includes("sz")) {
|
|
68
|
+
return {
|
|
69
|
+
code: source,
|
|
70
|
+
transformed: false,
|
|
71
|
+
usesRuntime: false,
|
|
72
|
+
usesMerge: false,
|
|
73
|
+
usesColorVar: false,
|
|
74
|
+
classes: collectedClasses,
|
|
75
|
+
rawClassNames,
|
|
76
|
+
diagnostics,
|
|
77
|
+
recoveryTokens
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const result = babel.transformSync(source, {
|
|
82
|
+
filename: filename ?? "file.tsx",
|
|
83
|
+
// Enable TS/JSX parsing
|
|
84
|
+
ast: true,
|
|
85
|
+
code: true,
|
|
86
|
+
configFile: false,
|
|
87
|
+
babelrc: false,
|
|
88
|
+
parserOpts: {
|
|
89
|
+
plugins: ["typescript", "jsx"]
|
|
90
|
+
},
|
|
91
|
+
plugins: [
|
|
92
|
+
() => ({
|
|
93
|
+
// Budget guard runs in `pre` (before the visitor pass)
|
|
94
|
+
// so it short-circuits pathologically large files
|
|
95
|
+
// before any sz transform work begins, and doesn't
|
|
96
|
+
// interfere with the JSXAttribute handler below.
|
|
97
|
+
pre(file) {
|
|
98
|
+
let nodeCount = 0;
|
|
99
|
+
babel.traverse(file.ast, {
|
|
100
|
+
enter() {
|
|
101
|
+
nodeCount++;
|
|
102
|
+
if (nodeCount > astBudget) {
|
|
103
|
+
throw new ASTBudgetExceededError(
|
|
104
|
+
filename,
|
|
105
|
+
nodeCount,
|
|
106
|
+
astBudget
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
visitor: {
|
|
113
|
+
JSXAttribute(path) {
|
|
114
|
+
const attrName = t.isJSXIdentifier(path.node.name) ? path.node.name.name : "";
|
|
115
|
+
if (attrName === "className" || attrName === "class") {
|
|
116
|
+
const val = path.node.value;
|
|
117
|
+
if (t.isStringLiteral(val)) {
|
|
118
|
+
for (const c of val.value.split(/\s+/)) {
|
|
119
|
+
if (c) {
|
|
120
|
+
rawClassNames.add(c);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (attrName === "szRecover") {
|
|
127
|
+
const recoverValue = path.node.value;
|
|
128
|
+
if (!t.isStringLiteral(recoverValue)) {
|
|
129
|
+
diagnostics.push(
|
|
130
|
+
`[csszyx] szRecover at ${filename ?? "<anonymous>"}: only string-literal values ("csr" | "dev-only") are supported. Dynamic values disable token emission for this element.`
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!isValidInlineRecoveryMode(recoverValue.value)) {
|
|
135
|
+
diagnostics.push(
|
|
136
|
+
`[csszyx] szRecover at ${filename ?? "<anonymous>"}: unknown mode "${recoverValue.value}" \u2014 expected "csr" or "dev-only". Token emission skipped.`
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const opening = path.parentPath;
|
|
141
|
+
if (!opening?.isJSXOpeningElement()) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const alreadyTagged = opening.node.attributes.some(
|
|
145
|
+
(attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "data-sz-recovery-token"
|
|
146
|
+
);
|
|
147
|
+
if (alreadyTagged) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const loc = path.node.loc;
|
|
151
|
+
const elementType = t.isJSXIdentifier(opening.node.name) ? opening.node.name.name : t.isJSXMemberExpression(opening.node.name) ? "<member>" : "<unknown>";
|
|
152
|
+
const line = loc?.start.line ?? 0;
|
|
153
|
+
const column = loc?.start.column ?? 0;
|
|
154
|
+
const file = filename ?? "file.tsx";
|
|
155
|
+
const token = generateInlineRecoveryToken(
|
|
156
|
+
file,
|
|
157
|
+
line,
|
|
158
|
+
column,
|
|
159
|
+
elementType
|
|
160
|
+
);
|
|
161
|
+
opening.node.attributes.push(
|
|
162
|
+
t.jsxAttribute(
|
|
163
|
+
t.jsxIdentifier("data-sz-recovery-token"),
|
|
164
|
+
t.stringLiteral(token)
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
recoveryTokens.set(token, {
|
|
168
|
+
mode: recoverValue.value,
|
|
169
|
+
component: elementType,
|
|
170
|
+
path: `${file}:${line}:${column}`
|
|
171
|
+
});
|
|
172
|
+
transformed = true;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (attrName !== "sz") {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const value = path.node.value;
|
|
179
|
+
let existingClassNameNode = null;
|
|
180
|
+
let existingClassExpr = null;
|
|
181
|
+
let existingStyleNode = null;
|
|
182
|
+
let existingStyleExpr = null;
|
|
183
|
+
if (path.parentPath?.isJSXOpeningElement()) {
|
|
184
|
+
for (const attr of path.parentPath.node.attributes) {
|
|
185
|
+
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
|
|
186
|
+
const aName = attr.name;
|
|
187
|
+
if (aName.name === "className" || aName.name === "class") {
|
|
188
|
+
existingClassNameNode = attr;
|
|
189
|
+
const aVal = attr.value;
|
|
190
|
+
if (t.isStringLiteral(aVal)) {
|
|
191
|
+
existingClassExpr = aVal;
|
|
192
|
+
} else if (t.isJSXExpressionContainer(aVal)) {
|
|
193
|
+
if (t.isExpression(aVal.expression)) {
|
|
194
|
+
existingClassExpr = aVal.expression;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else if (aName.name === "style") {
|
|
198
|
+
existingStyleNode = attr;
|
|
199
|
+
const aVal = attr.value;
|
|
200
|
+
if (t.isJSXExpressionContainer(aVal)) {
|
|
201
|
+
if (t.isExpression(aVal.expression)) {
|
|
202
|
+
existingStyleExpr = aVal.expression;
|
|
203
|
+
}
|
|
204
|
+
} else if (t.isStringLiteral(aVal)) {
|
|
205
|
+
existingStyleExpr = aVal;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const createMergedClassNameValue = (szExpr) => {
|
|
212
|
+
if (!existingClassExpr) {
|
|
213
|
+
return t.isStringLiteral(szExpr) ? szExpr : t.jsxExpressionContainer(szExpr);
|
|
214
|
+
}
|
|
215
|
+
if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
|
|
216
|
+
path.parentPath.node.attributes = path.parentPath.node.attributes.filter(
|
|
217
|
+
(a) => a !== existingClassNameNode
|
|
218
|
+
);
|
|
219
|
+
existingClassNameNode = null;
|
|
220
|
+
}
|
|
221
|
+
if (t.isStringLiteral(existingClassExpr) && t.isStringLiteral(szExpr)) {
|
|
222
|
+
const merged = `${existingClassExpr.value} ${szExpr.value}`.trim();
|
|
223
|
+
return t.stringLiteral(merged);
|
|
224
|
+
}
|
|
225
|
+
usesRuntime = true;
|
|
226
|
+
usesMerge = true;
|
|
227
|
+
return t.jsxExpressionContainer(
|
|
228
|
+
t.callExpression(t.identifier("_szMerge"), [
|
|
229
|
+
existingClassExpr,
|
|
230
|
+
szExpr
|
|
231
|
+
])
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
const mergeAndInjectStyle = (newStyleProps) => {
|
|
235
|
+
if (newStyleProps.length === 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!path.parentPath?.isJSXOpeningElement()) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (existingStyleNode && existingStyleExpr) {
|
|
242
|
+
path.parentPath.node.attributes = path.parentPath.node.attributes.filter(
|
|
243
|
+
(a) => a !== existingStyleNode
|
|
244
|
+
);
|
|
245
|
+
existingStyleNode = null;
|
|
246
|
+
if (t.isObjectExpression(existingStyleExpr)) {
|
|
247
|
+
existingStyleExpr.properties.push(...newStyleProps);
|
|
248
|
+
path.parentPath.node.attributes.push(
|
|
249
|
+
t.jsxAttribute(
|
|
250
|
+
t.jsxIdentifier("style"),
|
|
251
|
+
t.jsxExpressionContainer(existingStyleExpr)
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
} else if (t.isStringLiteral(existingStyleExpr)) {
|
|
255
|
+
const parsedOldProps = parseStyleStringToObjectExpr(
|
|
256
|
+
existingStyleExpr.value
|
|
257
|
+
).properties;
|
|
258
|
+
path.parentPath.node.attributes.push(
|
|
259
|
+
t.jsxAttribute(
|
|
260
|
+
t.jsxIdentifier("style"),
|
|
261
|
+
t.jsxExpressionContainer(
|
|
262
|
+
t.objectExpression([
|
|
263
|
+
...parsedOldProps,
|
|
264
|
+
...newStyleProps
|
|
265
|
+
])
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
} else {
|
|
270
|
+
const mergedStyle = t.objectExpression([
|
|
271
|
+
t.spreadElement(existingStyleExpr),
|
|
272
|
+
...newStyleProps
|
|
273
|
+
]);
|
|
274
|
+
path.parentPath.node.attributes.push(
|
|
275
|
+
t.jsxAttribute(
|
|
276
|
+
t.jsxIdentifier("style"),
|
|
277
|
+
t.jsxExpressionContainer(mergedStyle)
|
|
278
|
+
)
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
path.parentPath.node.attributes.push(
|
|
283
|
+
t.jsxAttribute(
|
|
284
|
+
t.jsxIdentifier("style"),
|
|
285
|
+
t.jsxExpressionContainer(
|
|
286
|
+
t.objectExpression(newStyleProps)
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
existingStyleExpr = t.objectExpression(newStyleProps);
|
|
291
|
+
existingStyleNode = path.parentPath.node.attributes[path.parentPath.node.attributes.length - 1];
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
if (t.isStringLiteral(value)) {
|
|
295
|
+
path.node.name.name = "className";
|
|
296
|
+
for (const c of value.value.split(/\s+/)) {
|
|
297
|
+
if (c) {
|
|
298
|
+
collectedClasses.add(c);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
path.node.value = createMergedClassNameValue(value);
|
|
302
|
+
transformed = true;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (t.isJSXExpressionContainer(value)) {
|
|
306
|
+
const expression = value.expression;
|
|
307
|
+
if (t.isObjectExpression(expression)) {
|
|
308
|
+
const getBinding = (name) => path.scope.getBinding(name);
|
|
309
|
+
const flatExpression = resolveObjectSpreads(expression, getBinding) ?? expression;
|
|
310
|
+
const staticObject = evaluateStaticObject(flatExpression);
|
|
311
|
+
if (staticObject !== null) {
|
|
312
|
+
const { className, attributes } = transform(staticObject);
|
|
313
|
+
for (const c of className.split(/\s+/)) {
|
|
314
|
+
if (c) {
|
|
315
|
+
collectedClasses.add(c);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
path.node.name.name = "className";
|
|
319
|
+
path.node.value = createMergedClassNameValue(
|
|
320
|
+
t.stringLiteral(className)
|
|
321
|
+
);
|
|
322
|
+
Object.entries(attributes).forEach(([key, val]) => {
|
|
323
|
+
if (path.parentPath?.isJSXOpeningElement()) {
|
|
324
|
+
if (key === "style") {
|
|
325
|
+
const newProps = parseStyleStringToObjectExpr(
|
|
326
|
+
val
|
|
327
|
+
).properties;
|
|
328
|
+
mergeAndInjectStyle(
|
|
329
|
+
newProps
|
|
330
|
+
);
|
|
331
|
+
} else {
|
|
332
|
+
path.parentPath.node.attributes.push(
|
|
333
|
+
t.jsxAttribute(
|
|
334
|
+
t.jsxIdentifier(key),
|
|
335
|
+
t.stringLiteral(val)
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
transformed = true;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const hoisted = tryHoistConditionalSpread(
|
|
345
|
+
expression,
|
|
346
|
+
getBinding
|
|
347
|
+
);
|
|
348
|
+
if (hoisted !== null) {
|
|
349
|
+
path.node.name.name = "className";
|
|
350
|
+
path.node.value = createMergedClassNameValue(hoisted);
|
|
351
|
+
collectFromExpr(hoisted, collectedClasses);
|
|
352
|
+
transformed = true;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const partial = evaluatePartialObject$1(flatExpression);
|
|
356
|
+
if (partial !== null && !partial.hasSpread && (partial.dynamicProps.size > 0 || partial.conditionalClasses.length > 0)) {
|
|
357
|
+
const staticClasses = [];
|
|
358
|
+
if (Object.keys(partial.staticProps).length > 0) {
|
|
359
|
+
const { className: sc } = transform(
|
|
360
|
+
partial.staticProps
|
|
361
|
+
);
|
|
362
|
+
if (sc) {
|
|
363
|
+
staticClasses.push(sc);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const cssVarClasses = [];
|
|
367
|
+
const styleProps = [];
|
|
368
|
+
for (const [, info] of partial.dynamicProps) {
|
|
369
|
+
if (!info.skipClass) {
|
|
370
|
+
cssVarClasses.push(buildCSSVarClassName$1(info));
|
|
371
|
+
}
|
|
372
|
+
styleProps.push(
|
|
373
|
+
t.objectProperty(
|
|
374
|
+
t.stringLiteral(info.varName),
|
|
375
|
+
generateStyleValueExpression(info)
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const baseClasses = [
|
|
380
|
+
...staticClasses,
|
|
381
|
+
...partial.rawClasses,
|
|
382
|
+
...cssVarClasses
|
|
383
|
+
].join(" ");
|
|
384
|
+
for (const c of baseClasses.split(/\s+/)) {
|
|
385
|
+
if (c) {
|
|
386
|
+
collectedClasses.add(c);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const cc of partial.conditionalClasses) {
|
|
390
|
+
for (const c of cc.consequent.split(/\s+/)) {
|
|
391
|
+
if (c) {
|
|
392
|
+
collectedClasses.add(c);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const c of cc.alternate.split(/\s+/)) {
|
|
396
|
+
if (c) {
|
|
397
|
+
collectedClasses.add(c);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const classExpr = partial.conditionalClasses.length > 0 ? buildConditionalClassExpr(
|
|
402
|
+
baseClasses,
|
|
403
|
+
partial.conditionalClasses
|
|
404
|
+
) : t.stringLiteral(baseClasses);
|
|
405
|
+
path.node.name.name = "className";
|
|
406
|
+
path.node.value = createMergedClassNameValue(classExpr);
|
|
407
|
+
mergeAndInjectStyle(styleProps);
|
|
408
|
+
if (partial.usesColorVar) {
|
|
409
|
+
usesColorVar = true;
|
|
410
|
+
}
|
|
411
|
+
transformed = true;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (t.isIdentifier(expression) && !t.isJSXEmptyExpression(expression)) {
|
|
416
|
+
const binding = path.scope.getBinding(expression.name);
|
|
417
|
+
if (binding?.path.isVariableDeclarator()) {
|
|
418
|
+
const init = binding.path.node.init;
|
|
419
|
+
if (init) {
|
|
420
|
+
const gbIdent = (name) => path.scope.getBinding(name);
|
|
421
|
+
const resolved = tryStaticTransformNode(init, gbIdent);
|
|
422
|
+
if (resolved !== null) {
|
|
423
|
+
path.node.name.name = "className";
|
|
424
|
+
if (t.isStringLiteral(resolved)) {
|
|
425
|
+
path.node.value = createMergedClassNameValue(resolved);
|
|
426
|
+
for (const c of resolved.value.split(/\s+/)) {
|
|
427
|
+
if (c) {
|
|
428
|
+
collectedClasses.add(c);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
path.node.value = createMergedClassNameValue(resolved);
|
|
433
|
+
collectFromExpr(resolved, collectedClasses);
|
|
434
|
+
}
|
|
435
|
+
transformed = true;
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (t.isConditionalExpression(expression)) {
|
|
442
|
+
const gbCond = (name) => path.scope.getBinding(name);
|
|
443
|
+
const resolved = tryStaticTransformNode(expression, gbCond);
|
|
444
|
+
if (resolved !== null) {
|
|
445
|
+
path.node.name.name = "className";
|
|
446
|
+
if (t.isStringLiteral(resolved)) {
|
|
447
|
+
path.node.value = createMergedClassNameValue(resolved);
|
|
448
|
+
for (const c of resolved.value.split(/\s+/)) {
|
|
449
|
+
if (c) {
|
|
450
|
+
collectedClasses.add(c);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
path.node.value = createMergedClassNameValue(resolved);
|
|
455
|
+
collectFromExpr(resolved, collectedClasses);
|
|
456
|
+
}
|
|
457
|
+
transformed = true;
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (t.isArrayExpression(expression)) {
|
|
462
|
+
const parts = [];
|
|
463
|
+
let hasRuntime = false;
|
|
464
|
+
const getBindingForArray = (name) => path.scope.getBinding(name);
|
|
465
|
+
for (const element of expression.elements) {
|
|
466
|
+
if (element === null) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (t.isBooleanLiteral(element) && !element.value) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (t.isNullLiteral(element)) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (t.isIdentifier(element) && element.name === "undefined") {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (t.isLogicalExpression(element) && element.operator === "&&") {
|
|
479
|
+
const resolved2 = tryStaticTransformNode(
|
|
480
|
+
element.right,
|
|
481
|
+
getBindingForArray
|
|
482
|
+
);
|
|
483
|
+
if (resolved2 !== null && t.isStringLiteral(resolved2)) {
|
|
484
|
+
if (resolved2.value) {
|
|
485
|
+
parts.push(
|
|
486
|
+
t.logicalExpression(
|
|
487
|
+
"&&",
|
|
488
|
+
element.left,
|
|
489
|
+
resolved2
|
|
490
|
+
)
|
|
491
|
+
);
|
|
492
|
+
for (const c of resolved2.value.split(/\s+/)) {
|
|
493
|
+
if (c) {
|
|
494
|
+
collectedClasses.add(c);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
hasRuntime = true;
|
|
498
|
+
}
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
parts.push(element);
|
|
502
|
+
hasRuntime = true;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const resolved = tryStaticTransformNode(
|
|
506
|
+
element,
|
|
507
|
+
getBindingForArray
|
|
508
|
+
);
|
|
509
|
+
if (resolved !== null) {
|
|
510
|
+
if (t.isStringLiteral(resolved)) {
|
|
511
|
+
if (resolved.value) {
|
|
512
|
+
parts.push(resolved);
|
|
513
|
+
for (const c of resolved.value.split(/\s+/)) {
|
|
514
|
+
if (c) {
|
|
515
|
+
collectedClasses.add(c);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
parts.push(resolved);
|
|
521
|
+
collectFromExpr(resolved, collectedClasses);
|
|
522
|
+
hasRuntime = true;
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
525
|
+
parts.push(element);
|
|
526
|
+
hasRuntime = true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
path.node.name.name = "className";
|
|
530
|
+
if (parts.length === 0) {
|
|
531
|
+
path.node.value = createMergedClassNameValue(
|
|
532
|
+
t.stringLiteral("")
|
|
533
|
+
);
|
|
534
|
+
} else if (!hasRuntime) {
|
|
535
|
+
const merged = parts.map((p) => p.value).filter(Boolean).join(" ");
|
|
536
|
+
path.node.value = createMergedClassNameValue(
|
|
537
|
+
t.stringLiteral(merged)
|
|
538
|
+
);
|
|
539
|
+
} else {
|
|
540
|
+
if (existingClassExpr) {
|
|
541
|
+
parts.unshift(existingClassExpr);
|
|
542
|
+
if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
|
|
543
|
+
path.parentPath.node.attributes = path.parentPath.node.attributes.filter(
|
|
544
|
+
(a) => a !== existingClassNameNode
|
|
545
|
+
);
|
|
546
|
+
existingClassNameNode = null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const szCall2 = t.callExpression(
|
|
550
|
+
t.identifier("_szMerge"),
|
|
551
|
+
parts
|
|
552
|
+
);
|
|
553
|
+
path.node.value = t.jsxExpressionContainer(szCall2);
|
|
554
|
+
usesMerge = true;
|
|
555
|
+
usesRuntime = true;
|
|
556
|
+
}
|
|
557
|
+
transformed = true;
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const loc = expression.loc;
|
|
561
|
+
const lineCol = loc ? `${loc.start.line}:${loc.start.column + 1}` : "?";
|
|
562
|
+
let reason, suggestion;
|
|
563
|
+
if (t.isCallExpression(expression)) {
|
|
564
|
+
const callee = expression.callee;
|
|
565
|
+
const name = t.isIdentifier(callee) ? callee.name : t.isMemberExpression(callee) && t.isIdentifier(callee.property) ? callee.property.name : "?";
|
|
566
|
+
reason = `function call \`${name}()\` result is unknown at build time`;
|
|
567
|
+
suggestion = "If it returns static variants \u2192 convert to szv(). If it depends on runtime data \u2192 use dynamic().";
|
|
568
|
+
} else if (t.isIdentifier(expression)) {
|
|
569
|
+
reason = `identifier \`${expression.name}\` could not be resolved to a static value`;
|
|
570
|
+
suggestion = "Make sure it's a module-level or function-body const with a literal object value. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
|
|
571
|
+
} else if (t.isMemberExpression(expression)) {
|
|
572
|
+
reason = "member expression is not statically resolvable";
|
|
573
|
+
suggestion = "Extract the value to a module-level const. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
|
|
574
|
+
} else {
|
|
575
|
+
reason = `expression of type \`${expression.type}\` is not statically analyzable`;
|
|
576
|
+
suggestion = "Use a literal sz object or a module-level const. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
|
|
577
|
+
}
|
|
578
|
+
diagnostics.push(
|
|
579
|
+
`sz fallback at ${lineCol}: ${reason}.
|
|
580
|
+
Suggestion: ${suggestion}`
|
|
581
|
+
);
|
|
582
|
+
path.node.name.name = "className";
|
|
583
|
+
const szCall = t.callExpression(t.identifier("_sz"), [
|
|
584
|
+
expression
|
|
585
|
+
]);
|
|
586
|
+
path.node.value = createMergedClassNameValue(szCall);
|
|
587
|
+
usesRuntime = true;
|
|
588
|
+
transformed = true;
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
// ── szv catalog extraction ────────────────────────────────────────
|
|
592
|
+
// When the compiler sees `const X = szv({...})` with a static config,
|
|
593
|
+
// it emits a no-op catalog array so Tailwind JIT can scan all variant
|
|
594
|
+
// class strings — even when szv is called at runtime with dynamic args.
|
|
595
|
+
VariableDeclarator(path) {
|
|
596
|
+
const init = path.node.init;
|
|
597
|
+
if (!t.isCallExpression(init)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (!t.isIdentifier(init.callee) || init.callee.name !== "szv") {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (init.arguments.length === 0) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (!t.isIdentifier(path.node.id)) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const configArg = init.arguments[0];
|
|
610
|
+
if (!t.isObjectExpression(configArg)) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const config = evaluateStaticObject(configArg);
|
|
614
|
+
if (!config) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const base = config.base ?? {};
|
|
618
|
+
const variants = config.variants ?? {};
|
|
619
|
+
const classStrings = [];
|
|
620
|
+
const baseResult = transform(base);
|
|
621
|
+
const baseCls = typeof baseResult === "string" ? baseResult : baseResult.className;
|
|
622
|
+
if (baseCls) {
|
|
623
|
+
classStrings.push(baseCls);
|
|
624
|
+
}
|
|
625
|
+
for (const variantValues of Object.values(variants)) {
|
|
626
|
+
for (const variantObj of Object.values(variantValues)) {
|
|
627
|
+
if (!variantObj || typeof variantObj !== "object") {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const merged = {
|
|
631
|
+
...base,
|
|
632
|
+
...variantObj
|
|
633
|
+
};
|
|
634
|
+
const result2 = transform(merged);
|
|
635
|
+
const cls = typeof result2 === "string" ? result2 : result2.className;
|
|
636
|
+
if (cls) {
|
|
637
|
+
classStrings.push(cls);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (classStrings.length === 0) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
for (const combined of classStrings) {
|
|
645
|
+
for (const c of combined.split(/\s+/)) {
|
|
646
|
+
if (c) {
|
|
647
|
+
collectedClasses.add(c);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const catalogDecl = t.variableDeclaration("const", [
|
|
652
|
+
t.variableDeclarator(
|
|
653
|
+
t.identifier(`_szv_catalog_${path.node.id.name}`),
|
|
654
|
+
t.arrayExpression(classStrings.map((s) => t.stringLiteral(s)))
|
|
655
|
+
)
|
|
656
|
+
]);
|
|
657
|
+
const parentPath = path.parentPath;
|
|
658
|
+
if (parentPath && t.isVariableDeclaration(parentPath.node)) {
|
|
659
|
+
parentPath.insertAfter(
|
|
660
|
+
catalogDecl
|
|
661
|
+
);
|
|
662
|
+
transformed = true;
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
// ── dynamic() literal extraction ──────────────────────────────────
|
|
666
|
+
// Detects `dynamic({...})` and `dynamic(CONST_IDENTIFIER)` calls
|
|
667
|
+
// with statically-analyzable arguments and adds the resulting
|
|
668
|
+
// class tokens to collectedClasses so prescanAndWriteClasses()
|
|
669
|
+
// includes them in csszyx-classes.html for Tailwind to scan.
|
|
670
|
+
// This means dynamic() with static/const args works in Astro SSR
|
|
671
|
+
// without needing client:* directives.
|
|
672
|
+
CallExpression(path) {
|
|
673
|
+
const callee = path.node.callee;
|
|
674
|
+
if (!t.isIdentifier(callee) || callee.name !== "dynamic") {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (path.node.arguments.length === 0) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const arg = path.node.arguments[0];
|
|
681
|
+
if (t.isObjectExpression(arg)) {
|
|
682
|
+
const staticObj = evaluateStaticObject(arg);
|
|
683
|
+
if (!staticObj) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const { className } = transform(staticObj);
|
|
687
|
+
for (const c of className.split(/\s+/)) {
|
|
688
|
+
if (c) {
|
|
689
|
+
collectedClasses.add(c);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
let argExpr = arg;
|
|
695
|
+
while (t.isTSAsExpression(argExpr) || t.isTSSatisfiesExpression(argExpr)) {
|
|
696
|
+
argExpr = argExpr.expression;
|
|
697
|
+
}
|
|
698
|
+
if (t.isIdentifier(argExpr)) {
|
|
699
|
+
const binding = path.scope.getBinding(argExpr.name);
|
|
700
|
+
if (!binding) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const declarator = binding.path.node;
|
|
704
|
+
if (!t.isVariableDeclarator(declarator) || !declarator.init) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
let initExpr = declarator.init;
|
|
708
|
+
while (t.isTSAsExpression(initExpr) || t.isTSSatisfiesExpression(initExpr)) {
|
|
709
|
+
initExpr = initExpr.expression;
|
|
710
|
+
}
|
|
711
|
+
if (!t.isObjectExpression(initExpr)) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const staticObj = evaluateStaticObject(initExpr);
|
|
715
|
+
if (!staticObj) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const { className } = transform(staticObj);
|
|
719
|
+
for (const c of className.split(/\s+/)) {
|
|
720
|
+
if (c) {
|
|
721
|
+
collectedClasses.add(c);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
})
|
|
728
|
+
]
|
|
729
|
+
});
|
|
730
|
+
return {
|
|
731
|
+
code: result?.code || source,
|
|
732
|
+
transformed,
|
|
733
|
+
usesRuntime,
|
|
734
|
+
usesMerge,
|
|
735
|
+
usesColorVar,
|
|
736
|
+
classes: collectedClasses,
|
|
737
|
+
rawClassNames,
|
|
738
|
+
diagnostics,
|
|
739
|
+
recoveryTokens
|
|
740
|
+
};
|
|
741
|
+
} catch (e) {
|
|
742
|
+
if (e instanceof ASTBudgetExceededError) {
|
|
743
|
+
throw e;
|
|
744
|
+
}
|
|
745
|
+
console.warn("[csszyx] AST transform failed, falling back to original code:", e);
|
|
746
|
+
return {
|
|
747
|
+
code: source,
|
|
748
|
+
transformed: false,
|
|
749
|
+
usesRuntime: false,
|
|
750
|
+
usesMerge: false,
|
|
751
|
+
usesColorVar: false,
|
|
752
|
+
classes: collectedClasses,
|
|
753
|
+
rawClassNames,
|
|
754
|
+
diagnostics,
|
|
755
|
+
recoveryTokens
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function parseStyleStringToObjectExpr(styleStr) {
|
|
760
|
+
const props = styleStr.split(";").map((s) => s.trim()).filter(Boolean);
|
|
761
|
+
const objProps = [];
|
|
762
|
+
for (const prop of props) {
|
|
763
|
+
const idx = prop.indexOf(":");
|
|
764
|
+
if (idx > -1) {
|
|
765
|
+
const k = prop.slice(0, idx).trim();
|
|
766
|
+
const v = prop.slice(idx + 1).trim();
|
|
767
|
+
let keyNode;
|
|
768
|
+
if (k.startsWith("--")) {
|
|
769
|
+
keyNode = t.stringLiteral(k);
|
|
770
|
+
} else {
|
|
771
|
+
const camel = k.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
772
|
+
keyNode = t.identifier(camel);
|
|
773
|
+
}
|
|
774
|
+
objProps.push(t.objectProperty(keyNode, t.stringLiteral(v)));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return t.objectExpression(objProps);
|
|
778
|
+
}
|
|
779
|
+
function tryStaticTransformNode(node, getBinding) {
|
|
780
|
+
if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) {
|
|
781
|
+
return tryStaticTransformNode(node.expression, getBinding);
|
|
782
|
+
}
|
|
783
|
+
if (t.isObjectExpression(node)) {
|
|
784
|
+
const resolved = getBinding ? resolveObjectSpreads(node, getBinding) ?? node : node;
|
|
785
|
+
const staticObj = evaluateStaticObject(resolved);
|
|
786
|
+
if (staticObj !== null) {
|
|
787
|
+
const { className } = transform(staticObj);
|
|
788
|
+
return t.stringLiteral(className);
|
|
789
|
+
}
|
|
790
|
+
if (getBinding) {
|
|
791
|
+
const hoisted = tryHoistConditionalSpread(node, getBinding);
|
|
792
|
+
if (hoisted !== null) {
|
|
793
|
+
return hoisted;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
if (t.isStringLiteral(node)) {
|
|
799
|
+
return node;
|
|
800
|
+
}
|
|
801
|
+
if (t.isIdentifier(node) && getBinding) {
|
|
802
|
+
const binding = getBinding(node.name);
|
|
803
|
+
if (binding?.path.isVariableDeclarator()) {
|
|
804
|
+
const init = binding.path.node.init;
|
|
805
|
+
if (init) {
|
|
806
|
+
return tryStaticTransformNode(init, getBinding);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
if (t.isConditionalExpression(node)) {
|
|
812
|
+
const consequent = tryStaticTransformNode(node.consequent, getBinding);
|
|
813
|
+
const alternate = tryStaticTransformNode(node.alternate, getBinding);
|
|
814
|
+
if (consequent !== null && alternate !== null) {
|
|
815
|
+
return t.conditionalExpression(node.test, consequent, alternate);
|
|
816
|
+
}
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
function tryHoistConditionalSpread(node, getBinding) {
|
|
822
|
+
let conditionalSpreadIdx = -1;
|
|
823
|
+
let conditionalExpr = null;
|
|
824
|
+
for (let i = 0; i < node.properties.length; i++) {
|
|
825
|
+
const prop = node.properties[i];
|
|
826
|
+
if (!t.isSpreadElement(prop)) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (t.isConditionalExpression(prop.argument)) {
|
|
830
|
+
if (conditionalSpreadIdx !== -1) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
conditionalSpreadIdx = i;
|
|
834
|
+
conditionalExpr = prop.argument;
|
|
835
|
+
} else {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (conditionalSpreadIdx === -1 || conditionalExpr === null) {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
const otherProps = node.properties.filter((_, i) => i !== conditionalSpreadIdx);
|
|
843
|
+
const mkObj = (branch) => t.objectExpression([t.spreadElement(branch), ...otherProps]);
|
|
844
|
+
const resolvedA = tryStaticTransformNode(mkObj(conditionalExpr.consequent), getBinding);
|
|
845
|
+
const resolvedB = tryStaticTransformNode(mkObj(conditionalExpr.alternate), getBinding);
|
|
846
|
+
if (!resolvedA || !resolvedB) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
if (!t.isStringLiteral(resolvedA) || !t.isStringLiteral(resolvedB)) {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
return t.conditionalExpression(conditionalExpr.test, resolvedA, resolvedB);
|
|
853
|
+
}
|
|
854
|
+
function evaluateStaticObject(node) {
|
|
855
|
+
const result = {};
|
|
856
|
+
for (const prop of node.properties) {
|
|
857
|
+
if (!t.isObjectProperty(prop)) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
if (prop.computed) {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
let key;
|
|
864
|
+
if (t.isIdentifier(prop.key)) {
|
|
865
|
+
key = prop.key.name;
|
|
866
|
+
} else if (t.isStringLiteral(prop.key)) {
|
|
867
|
+
key = prop.key.value;
|
|
868
|
+
} else if (t.isNumericLiteral(prop.key)) {
|
|
869
|
+
key = String(prop.key.value);
|
|
870
|
+
} else {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
const value = prop.value;
|
|
874
|
+
if (t.isStringLiteral(value)) {
|
|
875
|
+
result[key] = value.value;
|
|
876
|
+
} else if (t.isNumericLiteral(value)) {
|
|
877
|
+
result[key] = value.value;
|
|
878
|
+
} else if (t.isBooleanLiteral(value)) {
|
|
879
|
+
result[key] = value.value;
|
|
880
|
+
} else if (t.isUnaryExpression(value) && value.operator === "-" && t.isNumericLiteral(value.argument)) {
|
|
881
|
+
result[key] = -value.argument.value;
|
|
882
|
+
} else if (t.isObjectExpression(value)) {
|
|
883
|
+
const nested = evaluateStaticObject(value);
|
|
884
|
+
if (nested === null) {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
result[key] = nested;
|
|
888
|
+
} else {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return result;
|
|
893
|
+
}
|
|
894
|
+
function resolveObjectSpreads(node, getBinding) {
|
|
895
|
+
const newProps = [];
|
|
896
|
+
for (const prop of node.properties) {
|
|
897
|
+
if (!t.isSpreadElement(prop)) {
|
|
898
|
+
if (t.isObjectProperty(prop) && t.isObjectExpression(prop.value)) {
|
|
899
|
+
const resolvedValue = resolveObjectSpreads(prop.value, getBinding);
|
|
900
|
+
if (resolvedValue === null) {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
newProps.push(
|
|
904
|
+
t.objectProperty(prop.key, resolvedValue, prop.computed, prop.shorthand)
|
|
905
|
+
);
|
|
906
|
+
} else {
|
|
907
|
+
newProps.push(prop);
|
|
908
|
+
}
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
const arg = prop.argument;
|
|
912
|
+
if (!t.isIdentifier(arg)) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
const binding = getBinding(arg.name);
|
|
916
|
+
if (!binding?.path.isVariableDeclarator()) {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
let init = binding.path.node.init;
|
|
920
|
+
if (t.isTSAsExpression(init) || t.isTSSatisfiesExpression(init)) {
|
|
921
|
+
init = init.expression;
|
|
922
|
+
}
|
|
923
|
+
if (!t.isObjectExpression(init)) {
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
const inner = resolveObjectSpreads(init, getBinding);
|
|
927
|
+
if (inner === null) {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
newProps.push(...inner.properties);
|
|
931
|
+
}
|
|
932
|
+
return t.objectExpression(newProps);
|
|
933
|
+
}
|
|
934
|
+
function extractStaticLiteralValue$1(node) {
|
|
935
|
+
if (t.isStringLiteral(node)) {
|
|
936
|
+
return node.value;
|
|
937
|
+
}
|
|
938
|
+
if (t.isNumericLiteral(node)) {
|
|
939
|
+
return node.value;
|
|
940
|
+
}
|
|
941
|
+
if (t.isBooleanLiteral(node)) {
|
|
942
|
+
return node.value;
|
|
943
|
+
}
|
|
944
|
+
if (t.isUnaryExpression(node) && node.operator === "-" && t.isNumericLiteral(node.argument)) {
|
|
945
|
+
return -node.argument.value;
|
|
946
|
+
}
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
function buildConditionalClassExpr(baseClasses, conditionalClasses) {
|
|
950
|
+
if (conditionalClasses.length === 0) {
|
|
951
|
+
return t.stringLiteral(baseClasses);
|
|
952
|
+
}
|
|
953
|
+
const makeCondExpr = (cc) => t.conditionalExpression(
|
|
954
|
+
cc.test,
|
|
955
|
+
t.stringLiteral(cc.consequent),
|
|
956
|
+
t.stringLiteral(cc.alternate)
|
|
957
|
+
);
|
|
958
|
+
if (conditionalClasses.length === 1 && !baseClasses) {
|
|
959
|
+
return makeCondExpr(conditionalClasses[0]);
|
|
960
|
+
}
|
|
961
|
+
const quasis = [];
|
|
962
|
+
const exprs = [];
|
|
963
|
+
for (let i = 0; i < conditionalClasses.length; i++) {
|
|
964
|
+
const prefix = i === 0 ? baseClasses ? `${baseClasses} ` : "" : " ";
|
|
965
|
+
quasis.push(t.templateElement({ raw: prefix, cooked: prefix }, false));
|
|
966
|
+
exprs.push(makeCondExpr(conditionalClasses[i]));
|
|
967
|
+
}
|
|
968
|
+
quasis.push(t.templateElement({ raw: "", cooked: "" }, true));
|
|
969
|
+
return t.templateLiteral(quasis, exprs);
|
|
970
|
+
}
|
|
971
|
+
function evaluatePartialObject$1(node, variantChain = "") {
|
|
972
|
+
const staticProps = {};
|
|
973
|
+
const dynamicProps = /* @__PURE__ */ new Map();
|
|
974
|
+
const rawClasses = [];
|
|
975
|
+
const conditionalClasses = [];
|
|
976
|
+
let usesColorVar = false;
|
|
977
|
+
for (const prop of node.properties) {
|
|
978
|
+
if (t.isSpreadElement(prop)) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
if (!t.isObjectProperty(prop)) {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
if (prop.computed) {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
let key;
|
|
988
|
+
if (t.isIdentifier(prop.key)) {
|
|
989
|
+
key = prop.key.name;
|
|
990
|
+
} else if (t.isStringLiteral(prop.key)) {
|
|
991
|
+
key = prop.key.value;
|
|
992
|
+
} else if (t.isNumericLiteral(prop.key)) {
|
|
993
|
+
key = String(prop.key.value);
|
|
994
|
+
} else {
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
const value = prop.value;
|
|
998
|
+
if (t.isStringLiteral(value)) {
|
|
999
|
+
staticProps[key] = value.value;
|
|
1000
|
+
} else if (t.isNumericLiteral(value)) {
|
|
1001
|
+
staticProps[key] = value.value;
|
|
1002
|
+
} else if (t.isBooleanLiteral(value)) {
|
|
1003
|
+
staticProps[key] = value.value;
|
|
1004
|
+
} else if (t.isUnaryExpression(value) && value.operator === "-" && t.isNumericLiteral(value.argument)) {
|
|
1005
|
+
staticProps[key] = -value.argument.value;
|
|
1006
|
+
} else if (t.isObjectExpression(value)) {
|
|
1007
|
+
const nested = evaluateStaticObject(value);
|
|
1008
|
+
if (nested !== null) {
|
|
1009
|
+
staticProps[key] = nested;
|
|
1010
|
+
} else {
|
|
1011
|
+
const colorObjProps = /* @__PURE__ */ new Map();
|
|
1012
|
+
for (const p of value.properties) {
|
|
1013
|
+
if (t.isObjectProperty(p) && !p.computed && t.isIdentifier(p.key)) {
|
|
1014
|
+
colorObjProps.set(p.key.name, p);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (colorObjProps.has("color") && COLOR_PROPERTIES.has(key)) {
|
|
1018
|
+
const colorProp = colorObjProps.get("color");
|
|
1019
|
+
if (!colorProp) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const opProp = colorObjProps.get("op");
|
|
1023
|
+
const twPrefix = PROPERTY_MAP[key] || key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1024
|
+
let colorStr = null;
|
|
1025
|
+
if (t.isStringLiteral(colorProp.value)) {
|
|
1026
|
+
colorStr = colorProp.value.value;
|
|
1027
|
+
}
|
|
1028
|
+
if (colorStr && opProp) {
|
|
1029
|
+
const opVarName = getCSSVariableName(
|
|
1030
|
+
`${key}-op`,
|
|
1031
|
+
variantChain || void 0
|
|
1032
|
+
);
|
|
1033
|
+
const uniqueKey = variantChain ? `${variantChain}-${key}-op` : `${key}-op`;
|
|
1034
|
+
if (t.isStringLiteral(opProp.value) || t.isNumericLiteral(opProp.value)) {
|
|
1035
|
+
const opVal = t.isStringLiteral(opProp.value) ? opProp.value.value : opProp.value.value;
|
|
1036
|
+
staticProps[key] = { color: colorStr, op: opVal };
|
|
1037
|
+
} else if (t.isExpression(opProp.value)) {
|
|
1038
|
+
const variantPfx = variantChain ? `${variantChain}:` : "";
|
|
1039
|
+
rawClasses.push(`${variantPfx}${twPrefix}-${colorStr}/(${opVarName})`);
|
|
1040
|
+
dynamicProps.set(uniqueKey, {
|
|
1041
|
+
expression: opProp.value,
|
|
1042
|
+
category: PropertyCategory.UNITLESS,
|
|
1043
|
+
varName: opVarName,
|
|
1044
|
+
twPrefix: `${twPrefix}-op`,
|
|
1045
|
+
variantChain: variantChain || "",
|
|
1046
|
+
skipClass: true
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
} else if (!colorStr && opProp) {
|
|
1050
|
+
const varName = getCSSVariableName(key, variantChain || void 0);
|
|
1051
|
+
const uniqueKey = variantChain ? `${variantChain}-${key}` : key;
|
|
1052
|
+
usesColorVar = true;
|
|
1053
|
+
dynamicProps.set(uniqueKey, {
|
|
1054
|
+
expression: t.isExpression(colorProp.value) ? colorProp.value : t.stringLiteral(""),
|
|
1055
|
+
category: PropertyCategory.COLOR,
|
|
1056
|
+
varName,
|
|
1057
|
+
twPrefix,
|
|
1058
|
+
variantChain: variantChain || ""
|
|
1059
|
+
});
|
|
1060
|
+
} else if (colorStr && !opProp) {
|
|
1061
|
+
staticProps[key] = colorStr;
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
const isVariant = KNOWN_VARIANTS.has(key) || KNOWN_VARIANTS.has(getVariantPrefix(key));
|
|
1065
|
+
if (isVariant) {
|
|
1066
|
+
const variantKey = variantChain ? `${variantChain}-${key}` : key;
|
|
1067
|
+
const nestedResult = evaluatePartialObject$1(value, variantKey);
|
|
1068
|
+
if (nestedResult === null) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
if (Object.keys(nestedResult.staticProps).length > 0) {
|
|
1072
|
+
staticProps[key] = nestedResult.staticProps;
|
|
1073
|
+
}
|
|
1074
|
+
for (const [k, v] of nestedResult.dynamicProps) {
|
|
1075
|
+
dynamicProps.set(k, v);
|
|
1076
|
+
}
|
|
1077
|
+
rawClasses.push(...nestedResult.rawClasses);
|
|
1078
|
+
conditionalClasses.push(...nestedResult.conditionalClasses);
|
|
1079
|
+
if (nestedResult.usesColorVar) {
|
|
1080
|
+
usesColorVar = true;
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
} else if (t.isConditionalExpression(value)) {
|
|
1088
|
+
const consVal = extractStaticLiteralValue$1(value.consequent);
|
|
1089
|
+
const altVal = extractStaticLiteralValue$1(value.alternate);
|
|
1090
|
+
if (consVal !== null && altVal !== null) {
|
|
1091
|
+
const { className: classA } = transform({ [key]: consVal });
|
|
1092
|
+
const { className: classB } = transform({ [key]: altVal });
|
|
1093
|
+
const vPfx = variantChain ? `${getVariantPrefix(variantChain)}:` : "";
|
|
1094
|
+
const prefixed = (cls) => vPfx ? cls.split(/\s+/).filter(Boolean).map((c) => vPfx + c).join(" ") : cls;
|
|
1095
|
+
conditionalClasses.push({
|
|
1096
|
+
test: value.test,
|
|
1097
|
+
consequent: prefixed(classA),
|
|
1098
|
+
alternate: prefixed(classB)
|
|
1099
|
+
});
|
|
1100
|
+
} else {
|
|
1101
|
+
const twPrefix = PROPERTY_MAP[key] || key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1102
|
+
const category = getPropertyCategory(key);
|
|
1103
|
+
const varName = getCSSVariableName(key, variantChain || void 0);
|
|
1104
|
+
const uniqueKey = variantChain ? `${variantChain}-${key}` : key;
|
|
1105
|
+
if (COLOR_PROPERTIES.has(key)) {
|
|
1106
|
+
usesColorVar = true;
|
|
1107
|
+
}
|
|
1108
|
+
dynamicProps.set(uniqueKey, {
|
|
1109
|
+
expression: value,
|
|
1110
|
+
category,
|
|
1111
|
+
varName,
|
|
1112
|
+
twPrefix,
|
|
1113
|
+
variantChain: variantChain || ""
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
} else if (t.isExpression(value)) {
|
|
1117
|
+
const twPrefix = PROPERTY_MAP[key] || key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1118
|
+
const category = getPropertyCategory(key);
|
|
1119
|
+
const varName = getCSSVariableName(key, variantChain || void 0);
|
|
1120
|
+
const uniqueKey = variantChain ? `${variantChain}-${key}` : key;
|
|
1121
|
+
if (COLOR_PROPERTIES.has(key)) {
|
|
1122
|
+
usesColorVar = true;
|
|
1123
|
+
}
|
|
1124
|
+
dynamicProps.set(uniqueKey, {
|
|
1125
|
+
expression: value,
|
|
1126
|
+
category,
|
|
1127
|
+
varName,
|
|
1128
|
+
twPrefix,
|
|
1129
|
+
variantChain: variantChain || ""
|
|
1130
|
+
});
|
|
1131
|
+
} else {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
staticProps,
|
|
1137
|
+
dynamicProps,
|
|
1138
|
+
rawClasses,
|
|
1139
|
+
conditionalClasses,
|
|
1140
|
+
hasSpread: false,
|
|
1141
|
+
usesColorVar
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
function generateStyleValueExpression(info) {
|
|
1145
|
+
const { expression, category } = info;
|
|
1146
|
+
switch (category) {
|
|
1147
|
+
case PropertyCategory.SPACING:
|
|
1148
|
+
return t.templateLiteral(
|
|
1149
|
+
[
|
|
1150
|
+
t.templateElement({ raw: "calc(", cooked: "calc(" }, false),
|
|
1151
|
+
t.templateElement(
|
|
1152
|
+
{ raw: " * var(--spacing))", cooked: " * var(--spacing))" },
|
|
1153
|
+
true
|
|
1154
|
+
)
|
|
1155
|
+
],
|
|
1156
|
+
[expression]
|
|
1157
|
+
);
|
|
1158
|
+
case PropertyCategory.COLOR:
|
|
1159
|
+
return t.callExpression(t.identifier("__szColorVar"), [expression]);
|
|
1160
|
+
case PropertyCategory.ANGLE:
|
|
1161
|
+
return t.templateLiteral(
|
|
1162
|
+
[
|
|
1163
|
+
t.templateElement({ raw: "", cooked: "" }, false),
|
|
1164
|
+
t.templateElement({ raw: "deg", cooked: "deg" }, true)
|
|
1165
|
+
],
|
|
1166
|
+
[expression]
|
|
1167
|
+
);
|
|
1168
|
+
case PropertyCategory.DURATION:
|
|
1169
|
+
return t.templateLiteral(
|
|
1170
|
+
[
|
|
1171
|
+
t.templateElement({ raw: "", cooked: "" }, false),
|
|
1172
|
+
t.templateElement({ raw: "ms", cooked: "ms" }, true)
|
|
1173
|
+
],
|
|
1174
|
+
[expression]
|
|
1175
|
+
);
|
|
1176
|
+
default:
|
|
1177
|
+
return t.templateLiteral(
|
|
1178
|
+
[
|
|
1179
|
+
t.templateElement({ raw: "", cooked: "" }, false),
|
|
1180
|
+
t.templateElement({ raw: "", cooked: "" }, true)
|
|
1181
|
+
],
|
|
1182
|
+
[expression]
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function collectFromExpr(node, classes) {
|
|
1187
|
+
if (t.isStringLiteral(node)) {
|
|
1188
|
+
for (const c of node.value.split(/\s+/)) {
|
|
1189
|
+
if (c) {
|
|
1190
|
+
classes.add(c);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
} else if (t.isConditionalExpression(node)) {
|
|
1194
|
+
collectFromExpr(node.consequent, classes);
|
|
1195
|
+
collectFromExpr(node.alternate, classes);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function buildCSSVarClassName$1(info) {
|
|
1199
|
+
const { twPrefix, varName, variantChain } = info;
|
|
1200
|
+
const variantPrefix = variantChain ? `${getVariantPrefix(variantChain)}:` : "";
|
|
1201
|
+
return `${variantPrefix}${twPrefix}-(${varName})`;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
class CsszyxCompiler {
|
|
1205
|
+
static instance;
|
|
1206
|
+
wasmLoaded = false;
|
|
1207
|
+
/**
|
|
1208
|
+
* Private constructor to enforce singleton pattern.
|
|
1209
|
+
*/
|
|
1210
|
+
constructor() {
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Gets the singleton instance of the compiler.
|
|
1214
|
+
*
|
|
1215
|
+
* @returns {CsszyxCompiler} The compiler instance.
|
|
1216
|
+
*/
|
|
1217
|
+
static getInstance() {
|
|
1218
|
+
if (!CsszyxCompiler.instance) {
|
|
1219
|
+
CsszyxCompiler.instance = new CsszyxCompiler();
|
|
1220
|
+
}
|
|
1221
|
+
return CsszyxCompiler.instance;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Initializes the WASM core.
|
|
1225
|
+
*
|
|
1226
|
+
* @returns {Promise<void>} Resolves when WASM is ready.
|
|
1227
|
+
*/
|
|
1228
|
+
async init() {
|
|
1229
|
+
if (this.wasmLoaded) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
init();
|
|
1234
|
+
this.wasmLoaded = true;
|
|
1235
|
+
console.info(`[csszyx] WASM Core initialized (v${version()})`);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
console.warn(
|
|
1238
|
+
"[csszyx] Failed to initialize WASM core, falling back to JavaScript transformer",
|
|
1239
|
+
error
|
|
1240
|
+
);
|
|
1241
|
+
this.wasmLoaded = false;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Transforms an sz object into Tailwind classes.
|
|
1246
|
+
*
|
|
1247
|
+
* @param {SzObject} sz - The object to transform.
|
|
1248
|
+
* @returns {string} The transformed class string.
|
|
1249
|
+
*/
|
|
1250
|
+
transform(sz) {
|
|
1251
|
+
if (this.wasmLoaded) {
|
|
1252
|
+
const cleaned = stripInvalidColorStrings(sz);
|
|
1253
|
+
try {
|
|
1254
|
+
return transform_sz(cleaned);
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
console.warn("[csszyx] WASM transformation failed, using JS fallback", error);
|
|
1257
|
+
return transform(sz).className;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return transform(sz).className;
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Checks if the WASM core is currently active.
|
|
1264
|
+
*
|
|
1265
|
+
* @returns {boolean} True if WASM is loaded.
|
|
1266
|
+
*/
|
|
1267
|
+
isWasmActive() {
|
|
1268
|
+
return this.wasmLoaded;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Generates a recovery token using WASM or JS fallback.
|
|
1272
|
+
*
|
|
1273
|
+
* @param {object} metadata - Token metadata
|
|
1274
|
+
* @param metadata.component - Component name
|
|
1275
|
+
* @param metadata.filePath - File path source
|
|
1276
|
+
* @param metadata.line - Line number
|
|
1277
|
+
* @param metadata.column - Column number
|
|
1278
|
+
* @param metadata.mode - Build mode (dev/prod)
|
|
1279
|
+
* @param metadata.buildId - Unique build identifier
|
|
1280
|
+
* @returns {string} The generated token
|
|
1281
|
+
*/
|
|
1282
|
+
generateRecoveryToken(metadata) {
|
|
1283
|
+
if (this.wasmLoaded) {
|
|
1284
|
+
try {
|
|
1285
|
+
const { generate_token } = require("@csszyx/core");
|
|
1286
|
+
return generate_token(
|
|
1287
|
+
metadata.component,
|
|
1288
|
+
metadata.filePath,
|
|
1289
|
+
metadata.line,
|
|
1290
|
+
metadata.column,
|
|
1291
|
+
metadata.mode,
|
|
1292
|
+
metadata.buildId
|
|
1293
|
+
);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
console.warn("[csszyx] WASM token generation failed", error);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const str = `${metadata.component}:${metadata.filePath}:${metadata.line}:${metadata.column}:${metadata.mode}:${metadata.buildId}`;
|
|
1299
|
+
let hash = 0;
|
|
1300
|
+
for (let i = 0; i < str.length; i++) {
|
|
1301
|
+
hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
|
|
1302
|
+
}
|
|
1303
|
+
return Math.abs(hash).toString(16).padStart(12, "0").slice(0, 12);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function findLCA(nodeA, nodeB, parentMap) {
|
|
1308
|
+
const ancestorsA = /* @__PURE__ */ new Set();
|
|
1309
|
+
let current = nodeA;
|
|
1310
|
+
while (current) {
|
|
1311
|
+
ancestorsA.add(current);
|
|
1312
|
+
current = parentMap.get(current);
|
|
1313
|
+
}
|
|
1314
|
+
current = nodeB;
|
|
1315
|
+
while (current) {
|
|
1316
|
+
if (ancestorsA.has(current) && t.isJSXOpeningElement(current) && current !== nodeA && current !== nodeB) {
|
|
1317
|
+
return current;
|
|
1318
|
+
}
|
|
1319
|
+
current = parentMap.get(current);
|
|
1320
|
+
}
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
function isFragment(node) {
|
|
1324
|
+
if (t.isJSXIdentifier(node.name)) {
|
|
1325
|
+
return node.name.name === "Fragment";
|
|
1326
|
+
}
|
|
1327
|
+
if (t.isJSXMemberExpression(node.name)) {
|
|
1328
|
+
return t.isJSXIdentifier(node.name.property) && node.name.property.name === "Fragment";
|
|
1329
|
+
}
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
function removeStyleVar(element, varName) {
|
|
1333
|
+
for (const attr of element.attributes) {
|
|
1334
|
+
if (!t.isJSXAttribute(attr)) {
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
if (!t.isJSXIdentifier(attr.name) || attr.name.name !== "style") {
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (!t.isJSXExpressionContainer(attr.value)) {
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
const styleObj = attr.value.expression;
|
|
1344
|
+
if (!t.isObjectExpression(styleObj)) {
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
styleObj.properties = styleObj.properties.filter((prop) => {
|
|
1348
|
+
if (!t.isObjectProperty(prop)) {
|
|
1349
|
+
return true;
|
|
1350
|
+
}
|
|
1351
|
+
if (t.isStringLiteral(prop.key)) {
|
|
1352
|
+
return prop.key.value !== varName;
|
|
1353
|
+
}
|
|
1354
|
+
return true;
|
|
1355
|
+
});
|
|
1356
|
+
if (styleObj.properties.length === 0) {
|
|
1357
|
+
const idx = element.attributes.indexOf(attr);
|
|
1358
|
+
if (idx !== -1) {
|
|
1359
|
+
element.attributes.splice(idx, 1);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
function addStyleVar(element, varName, valueExpr) {
|
|
1366
|
+
for (const attr of element.attributes) {
|
|
1367
|
+
if (!t.isJSXAttribute(attr)) {
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
if (!t.isJSXIdentifier(attr.name) || attr.name.name !== "style") {
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (!t.isJSXExpressionContainer(attr.value)) {
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
const styleObj = attr.value.expression;
|
|
1377
|
+
if (!t.isObjectExpression(styleObj)) {
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
const existing = styleObj.properties.find(
|
|
1381
|
+
(prop) => t.isObjectProperty(prop) && t.isStringLiteral(prop.key) && prop.key.value === varName
|
|
1382
|
+
);
|
|
1383
|
+
if (!existing) {
|
|
1384
|
+
styleObj.properties.push(t.objectProperty(t.stringLiteral(varName), valueExpr));
|
|
1385
|
+
}
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
element.attributes.push(
|
|
1389
|
+
t.jsxAttribute(
|
|
1390
|
+
t.jsxIdentifier("style"),
|
|
1391
|
+
t.jsxExpressionContainer(
|
|
1392
|
+
t.objectExpression([t.objectProperty(t.stringLiteral(varName), valueExpr)])
|
|
1393
|
+
)
|
|
1394
|
+
)
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
function hoistCSSVariables(usages, parentMap) {
|
|
1398
|
+
if (usages.length < 2) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1402
|
+
for (const usage of usages) {
|
|
1403
|
+
if (usage.serializedValue === null) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const groupKey = `${usage.varName}::${usage.serializedValue}`;
|
|
1407
|
+
const group = groups.get(groupKey) || [];
|
|
1408
|
+
group.push(usage);
|
|
1409
|
+
groups.set(groupKey, group);
|
|
1410
|
+
}
|
|
1411
|
+
for (const [, group] of groups) {
|
|
1412
|
+
if (group.length < 2) {
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
let lca = group[0].element;
|
|
1416
|
+
for (let i = 1; i < group.length; i++) {
|
|
1417
|
+
const newLca = findLCA(lca, group[i].element, parentMap);
|
|
1418
|
+
if (newLca === null || isFragment(newLca)) {
|
|
1419
|
+
lca = null;
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
lca = newLca;
|
|
1423
|
+
}
|
|
1424
|
+
if (!lca) {
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
addStyleVar(lca, group[0].varName, group[0].valueExpr);
|
|
1428
|
+
for (const usage of group) {
|
|
1429
|
+
removeStyleVar(usage.element, usage.varName);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
function buildParentMap(ast) {
|
|
1434
|
+
const map = /* @__PURE__ */ new Map();
|
|
1435
|
+
function traverse(node, parent) {
|
|
1436
|
+
if (parent) {
|
|
1437
|
+
map.set(node, parent);
|
|
1438
|
+
}
|
|
1439
|
+
for (const key of Object.keys(node)) {
|
|
1440
|
+
const value = node[key];
|
|
1441
|
+
if (value && typeof value === "object") {
|
|
1442
|
+
if (Array.isArray(value)) {
|
|
1443
|
+
for (const item of value) {
|
|
1444
|
+
if (item && typeof item === "object" && "type" in item) {
|
|
1445
|
+
traverse(item, node);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
} else if ("type" in value) {
|
|
1449
|
+
traverse(value, node);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
traverse(ast);
|
|
1455
|
+
return map;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
class ManifestBuilder {
|
|
1459
|
+
tokens = /* @__PURE__ */ new Map();
|
|
1460
|
+
buildId;
|
|
1461
|
+
/**
|
|
1462
|
+
* Creates a new manifest builder.
|
|
1463
|
+
*
|
|
1464
|
+
* @param {string} buildId - Build ID (git hash or timestamp)
|
|
1465
|
+
*
|
|
1466
|
+
* @example
|
|
1467
|
+
* ```typescript
|
|
1468
|
+
* const builder = new ManifestBuilder('abc123');
|
|
1469
|
+
* ```
|
|
1470
|
+
*/
|
|
1471
|
+
constructor(buildId) {
|
|
1472
|
+
this.buildId = buildId;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Adds a token to the manifest.
|
|
1476
|
+
*
|
|
1477
|
+
* @param {string} token - The recovery token
|
|
1478
|
+
* @param {TokenMetadata} metadata - Token metadata
|
|
1479
|
+
*
|
|
1480
|
+
* @example
|
|
1481
|
+
* ```typescript
|
|
1482
|
+
* builder.addToken('a94f1c2e8b3d', {
|
|
1483
|
+
* mode: 'csr',
|
|
1484
|
+
* component: 'Button',
|
|
1485
|
+
* filePath: '/app/Button.tsx',
|
|
1486
|
+
* line: 10,
|
|
1487
|
+
* column: 5,
|
|
1488
|
+
* buildId: 'abc123'
|
|
1489
|
+
* });
|
|
1490
|
+
* ```
|
|
1491
|
+
*/
|
|
1492
|
+
addToken(token, metadata) {
|
|
1493
|
+
const relativePath = this.toRelativePath(metadata.filePath);
|
|
1494
|
+
this.tokens.set(token, {
|
|
1495
|
+
mode: metadata.mode,
|
|
1496
|
+
component: metadata.component,
|
|
1497
|
+
path: relativePath
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Converts absolute path to relative path.
|
|
1502
|
+
*
|
|
1503
|
+
* @param {string} absolutePath - Absolute file path
|
|
1504
|
+
* @returns {string} Relative path
|
|
1505
|
+
*/
|
|
1506
|
+
toRelativePath(absolutePath) {
|
|
1507
|
+
if (absolutePath.startsWith("/")) {
|
|
1508
|
+
const parts = absolutePath.split("/");
|
|
1509
|
+
const rootIndex = parts.findIndex(
|
|
1510
|
+
(p) => p === "src" || p === "app" || p === "components"
|
|
1511
|
+
);
|
|
1512
|
+
if (rootIndex > 0) {
|
|
1513
|
+
return parts.slice(rootIndex).join("/");
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return absolutePath;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Computes checksum of the tokens object.
|
|
1520
|
+
*
|
|
1521
|
+
* @param {Record<string, TokenData>} tokens - Tokens object
|
|
1522
|
+
* @returns {string} SHA-256 checksum
|
|
1523
|
+
*/
|
|
1524
|
+
computeChecksum(tokens) {
|
|
1525
|
+
const sortedKeys = Object.keys(tokens).sort();
|
|
1526
|
+
const sortedTokens = {};
|
|
1527
|
+
for (const key of sortedKeys) {
|
|
1528
|
+
sortedTokens[key] = tokens[key];
|
|
1529
|
+
}
|
|
1530
|
+
const content = JSON.stringify(sortedTokens);
|
|
1531
|
+
return createHash("sha256").update(content).digest("hex");
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Builds the final recovery manifest.
|
|
1535
|
+
*
|
|
1536
|
+
* @returns {RecoveryManifest} The complete recovery manifest
|
|
1537
|
+
*
|
|
1538
|
+
* @example
|
|
1539
|
+
* ```typescript
|
|
1540
|
+
* const manifest = builder.build();
|
|
1541
|
+
* // Returns: { buildId: 'abc123', checksum: '...', tokens: {...} }
|
|
1542
|
+
* ```
|
|
1543
|
+
*/
|
|
1544
|
+
build() {
|
|
1545
|
+
const tokensObject = {};
|
|
1546
|
+
for (const [token, data] of this.tokens.entries()) {
|
|
1547
|
+
tokensObject[token] = data;
|
|
1548
|
+
}
|
|
1549
|
+
const checksum = this.computeChecksum(tokensObject);
|
|
1550
|
+
return {
|
|
1551
|
+
buildId: this.buildId,
|
|
1552
|
+
checksum,
|
|
1553
|
+
tokens: tokensObject
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Gets the number of tokens in the manifest.
|
|
1558
|
+
*
|
|
1559
|
+
* @returns {number} Token count
|
|
1560
|
+
*/
|
|
1561
|
+
size() {
|
|
1562
|
+
return this.tokens.size;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Checks if a token exists in the manifest.
|
|
1566
|
+
*
|
|
1567
|
+
* @param {string} token - Token to check
|
|
1568
|
+
* @returns {boolean} True if token exists
|
|
1569
|
+
*/
|
|
1570
|
+
hasToken(token) {
|
|
1571
|
+
return this.tokens.has(token);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Clears all tokens from the manifest.
|
|
1575
|
+
*/
|
|
1576
|
+
clear() {
|
|
1577
|
+
this.tokens.clear();
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function serializeManifest(manifest, pretty = false) {
|
|
1581
|
+
return JSON.stringify(manifest, null, pretty ? 2 : 0);
|
|
1582
|
+
}
|
|
1583
|
+
function parseManifest(json) {
|
|
1584
|
+
const parsed = JSON.parse(json);
|
|
1585
|
+
if (!parsed.buildId || !parsed.checksum || !parsed.tokens) {
|
|
1586
|
+
throw new Error("Invalid recovery manifest format");
|
|
1587
|
+
}
|
|
1588
|
+
return parsed;
|
|
1589
|
+
}
|
|
1590
|
+
function validateManifest(manifest) {
|
|
1591
|
+
if (!manifest || typeof manifest !== "object") {
|
|
1592
|
+
return { valid: false, error: "Manifest must be an object" };
|
|
1593
|
+
}
|
|
1594
|
+
const m = manifest;
|
|
1595
|
+
if (typeof m.buildId !== "string") {
|
|
1596
|
+
return { valid: false, error: "buildId must be a string" };
|
|
1597
|
+
}
|
|
1598
|
+
if (typeof m.checksum !== "string") {
|
|
1599
|
+
return { valid: false, error: "checksum must be a string" };
|
|
1600
|
+
}
|
|
1601
|
+
if (!m.tokens || typeof m.tokens !== "object") {
|
|
1602
|
+
return { valid: false, error: "tokens must be an object" };
|
|
1603
|
+
}
|
|
1604
|
+
return { valid: true };
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
function isValidRecoveryMode(value) {
|
|
1608
|
+
return value === "csr" || value === "dev-only";
|
|
1609
|
+
}
|
|
1610
|
+
function generateRecoveryToken(metadata) {
|
|
1611
|
+
return CsszyxCompiler.getInstance().generateRecoveryToken(metadata);
|
|
1612
|
+
}
|
|
1613
|
+
function createRecoveryToken(metadata, buildId) {
|
|
1614
|
+
const fullMetadata = {
|
|
1615
|
+
...metadata,
|
|
1616
|
+
buildId
|
|
1617
|
+
};
|
|
1618
|
+
const token = generateRecoveryToken(fullMetadata);
|
|
1619
|
+
return {
|
|
1620
|
+
token,
|
|
1621
|
+
metadata: fullMetadata
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
function validateSzRecover(value, componentName) {
|
|
1625
|
+
if (!isValidRecoveryMode(value)) {
|
|
1626
|
+
return {
|
|
1627
|
+
valid: false,
|
|
1628
|
+
error: `szRecover in ${componentName} must be static literal "csr" or "dev-only", got: ${JSON.stringify(value)}`
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
return { valid: true };
|
|
1632
|
+
}
|
|
1633
|
+
function injectRecoveryToken(attributes, token) {
|
|
1634
|
+
return {
|
|
1635
|
+
...attributes,
|
|
1636
|
+
"data-sz-recovery-token": token
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
class OxcNotImplementedError extends Error {
|
|
1641
|
+
/**
|
|
1642
|
+
* @param slice The Phase D slice expected to implement this path.
|
|
1643
|
+
* @param detail What the caller asked for that is not yet wired.
|
|
1644
|
+
*/
|
|
1645
|
+
constructor(slice, detail) {
|
|
1646
|
+
super(`transformOxc: ${slice} not implemented yet \u2014 ${detail}`);
|
|
1647
|
+
this.name = "OxcNotImplementedError";
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
function transformOxc(source, filename, options) {
|
|
1651
|
+
const classes = /* @__PURE__ */ new Set();
|
|
1652
|
+
const rawClassNames = /* @__PURE__ */ new Set();
|
|
1653
|
+
const diagnostics = [];
|
|
1654
|
+
const recoveryTokens = /* @__PURE__ */ new Map();
|
|
1655
|
+
if (!source.includes("sz")) {
|
|
1656
|
+
return {
|
|
1657
|
+
code: source,
|
|
1658
|
+
transformed: false,
|
|
1659
|
+
usesRuntime: false,
|
|
1660
|
+
usesMerge: false,
|
|
1661
|
+
usesColorVar: false,
|
|
1662
|
+
classes,
|
|
1663
|
+
rawClassNames,
|
|
1664
|
+
diagnostics,
|
|
1665
|
+
recoveryTokens
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
const effectiveFilename = filename ?? "file.tsx";
|
|
1669
|
+
const astBudget = options?.astBudget ?? AST_BUDGET;
|
|
1670
|
+
const parsed = parseSync(effectiveFilename, source);
|
|
1671
|
+
if (parsed.errors.length > 0) {
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
`oxc-parser errors in ${effectiveFilename}: ` + parsed.errors.map((e) => e.message).join("; ")
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
assertAstBudget(parsed.program, effectiveFilename, astBudget);
|
|
1677
|
+
const edits = new MagicString(source);
|
|
1678
|
+
const objectBindings = collectObjectBindings(parsed.program);
|
|
1679
|
+
const conditionalBindings = collectConditionalBindings(parsed.program);
|
|
1680
|
+
let transformed = false;
|
|
1681
|
+
let usesRuntime = false;
|
|
1682
|
+
let usesMerge = false;
|
|
1683
|
+
let usesColorVar = false;
|
|
1684
|
+
walk(parsed.program, (node) => {
|
|
1685
|
+
if (node.type === "CallExpression") {
|
|
1686
|
+
collectDynamicCallClasses(
|
|
1687
|
+
node,
|
|
1688
|
+
effectiveFilename,
|
|
1689
|
+
objectBindings,
|
|
1690
|
+
classes
|
|
1691
|
+
);
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (node.type !== "JSXOpeningElement") {
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
const openingNode = node;
|
|
1698
|
+
const attrs = openingNode.attributes ?? [];
|
|
1699
|
+
const szAttrs = [];
|
|
1700
|
+
let classNameAttr = null;
|
|
1701
|
+
let styleAttr = null;
|
|
1702
|
+
let szRecoverAttr = null;
|
|
1703
|
+
let alreadyTagged = false;
|
|
1704
|
+
let lastAttr = null;
|
|
1705
|
+
for (const attrRaw of attrs) {
|
|
1706
|
+
if (attrRaw.type !== "JSXAttribute") {
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
const attr = attrRaw;
|
|
1710
|
+
lastAttr = attr;
|
|
1711
|
+
const name = attr.name?.name;
|
|
1712
|
+
if (name === "sz") {
|
|
1713
|
+
szAttrs.push(attr);
|
|
1714
|
+
} else if (name === "className" || name === "class") {
|
|
1715
|
+
classNameAttr = attr;
|
|
1716
|
+
} else if (name === "style") {
|
|
1717
|
+
styleAttr = attr;
|
|
1718
|
+
} else if (name === "szRecover") {
|
|
1719
|
+
szRecoverAttr = attr;
|
|
1720
|
+
} else if (name === "data-sz-recovery-token") {
|
|
1721
|
+
alreadyTagged = true;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
if (szRecoverAttr && !alreadyTagged) {
|
|
1725
|
+
const recoverValue = stringLiteralValue(szRecoverAttr.value);
|
|
1726
|
+
if (recoverValue === null) {
|
|
1727
|
+
diagnostics.push(
|
|
1728
|
+
`[csszyx] szRecover at ${effectiveFilename}: only string-literal values ("csr" | "dev-only") are supported. Dynamic values disable token emission for this element.`
|
|
1729
|
+
);
|
|
1730
|
+
} else if (!isValidInlineRecoveryMode(recoverValue)) {
|
|
1731
|
+
diagnostics.push(
|
|
1732
|
+
`[csszyx] szRecover at ${effectiveFilename}: unknown mode "${recoverValue}" \u2014 expected "csr" or "dev-only". Token emission skipped.`
|
|
1733
|
+
);
|
|
1734
|
+
} else {
|
|
1735
|
+
const elementType = extractElementName(openingNode.name);
|
|
1736
|
+
const { line, column } = offsetToLineColumn(source, szRecoverAttr.start);
|
|
1737
|
+
const token = generateInlineRecoveryToken(
|
|
1738
|
+
effectiveFilename,
|
|
1739
|
+
line,
|
|
1740
|
+
column,
|
|
1741
|
+
elementType
|
|
1742
|
+
);
|
|
1743
|
+
if (lastAttr) {
|
|
1744
|
+
edits.appendRight(lastAttr.end, ` data-sz-recovery-token="${token}"`);
|
|
1745
|
+
}
|
|
1746
|
+
recoveryTokens.set(token, {
|
|
1747
|
+
mode: recoverValue,
|
|
1748
|
+
component: elementType,
|
|
1749
|
+
path: `${effectiveFilename}:${line}:${column}`
|
|
1750
|
+
});
|
|
1751
|
+
transformed = true;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
if (classNameAttr) {
|
|
1755
|
+
const rawValue = stringLiteralValue(classNameAttr.value);
|
|
1756
|
+
if (rawValue !== null) {
|
|
1757
|
+
for (const c of rawValue.split(/\s+/)) {
|
|
1758
|
+
if (c) {
|
|
1759
|
+
rawClassNames.add(c);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
if (szAttrs.length === 0) {
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
const szDerived = [];
|
|
1768
|
+
let runtimeFallbackExpr = null;
|
|
1769
|
+
let runtimeFallbackAttr = null;
|
|
1770
|
+
for (const szAttr of szAttrs) {
|
|
1771
|
+
const value = szAttr.value;
|
|
1772
|
+
if (!value) {
|
|
1773
|
+
throw new OxcNotImplementedError(
|
|
1774
|
+
"D3",
|
|
1775
|
+
`sz attribute without value at ${effectiveFilename}:${szAttr.start}`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
const stringValue = stringLiteralValue(value);
|
|
1779
|
+
if (stringValue !== null) {
|
|
1780
|
+
for (const c of stringValue.split(/\s+/)) {
|
|
1781
|
+
if (c) {
|
|
1782
|
+
szDerived.push(c);
|
|
1783
|
+
classes.add(c);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
if (value.type !== "JSXExpressionContainer") {
|
|
1789
|
+
throw new OxcNotImplementedError(
|
|
1790
|
+
"D3",
|
|
1791
|
+
`unsupported sz attribute value ${value.type} at ${effectiveFilename}:${szAttr.start}`
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
const expression = value.expression;
|
|
1795
|
+
if (expression.type === "ConditionalExpression") {
|
|
1796
|
+
const conditionalClassExpr = buildStaticConditionalClassExpression(
|
|
1797
|
+
expression,
|
|
1798
|
+
effectiveFilename,
|
|
1799
|
+
objectBindings,
|
|
1800
|
+
source,
|
|
1801
|
+
classes
|
|
1802
|
+
);
|
|
1803
|
+
if (conditionalClassExpr) {
|
|
1804
|
+
if (classNameAttr || szAttrs.length > 1) {
|
|
1805
|
+
runtimeFallbackExpr = expression;
|
|
1806
|
+
runtimeFallbackAttr = szAttr;
|
|
1807
|
+
break;
|
|
1808
|
+
}
|
|
1809
|
+
edits.overwrite(
|
|
1810
|
+
szAttr.start,
|
|
1811
|
+
szAttr.end,
|
|
1812
|
+
`className={${conditionalClassExpr}}`
|
|
1813
|
+
);
|
|
1814
|
+
transformed = true;
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (expression.type === "Identifier") {
|
|
1819
|
+
const identifierName = String(expression.name);
|
|
1820
|
+
const bound = objectBindings.get(identifierName);
|
|
1821
|
+
if (bound) {
|
|
1822
|
+
const result2 = transform(
|
|
1823
|
+
astObjectToSzObject(bound, effectiveFilename, objectBindings)
|
|
1824
|
+
);
|
|
1825
|
+
for (const c of result2.className.split(/\s+/)) {
|
|
1826
|
+
if (c) {
|
|
1827
|
+
szDerived.push(c);
|
|
1828
|
+
classes.add(c);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
const conditional = conditionalBindings.get(identifierName);
|
|
1834
|
+
if (conditional) {
|
|
1835
|
+
const conditionalClassExpr = buildStaticConditionalClassExpression(
|
|
1836
|
+
conditional,
|
|
1837
|
+
effectiveFilename,
|
|
1838
|
+
objectBindings,
|
|
1839
|
+
source,
|
|
1840
|
+
classes
|
|
1841
|
+
);
|
|
1842
|
+
if (conditionalClassExpr) {
|
|
1843
|
+
if (classNameAttr || szAttrs.length > 1) {
|
|
1844
|
+
runtimeFallbackExpr = expression;
|
|
1845
|
+
runtimeFallbackAttr = szAttr;
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
edits.overwrite(
|
|
1849
|
+
szAttr.start,
|
|
1850
|
+
szAttr.end,
|
|
1851
|
+
`className={${conditionalClassExpr}}`
|
|
1852
|
+
);
|
|
1853
|
+
transformed = true;
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
if (expression.type === "ArrayExpression") {
|
|
1859
|
+
const arrayClasses = astArrayToStaticClasses(
|
|
1860
|
+
expression,
|
|
1861
|
+
effectiveFilename,
|
|
1862
|
+
objectBindings
|
|
1863
|
+
);
|
|
1864
|
+
if (arrayClasses === null) {
|
|
1865
|
+
collectArrayCandidateClasses(
|
|
1866
|
+
expression,
|
|
1867
|
+
effectiveFilename,
|
|
1868
|
+
objectBindings,
|
|
1869
|
+
classes
|
|
1870
|
+
);
|
|
1871
|
+
runtimeFallbackExpr = expression;
|
|
1872
|
+
runtimeFallbackAttr = szAttr;
|
|
1873
|
+
break;
|
|
1874
|
+
}
|
|
1875
|
+
for (const c of arrayClasses) {
|
|
1876
|
+
szDerived.push(c);
|
|
1877
|
+
classes.add(c);
|
|
1878
|
+
}
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
if (expression.type !== "ObjectExpression") {
|
|
1882
|
+
runtimeFallbackExpr = expression;
|
|
1883
|
+
runtimeFallbackAttr = szAttr;
|
|
1884
|
+
break;
|
|
1885
|
+
}
|
|
1886
|
+
let szObj;
|
|
1887
|
+
try {
|
|
1888
|
+
szObj = astObjectToSzObject(
|
|
1889
|
+
expression,
|
|
1890
|
+
effectiveFilename,
|
|
1891
|
+
objectBindings
|
|
1892
|
+
);
|
|
1893
|
+
} catch (err) {
|
|
1894
|
+
if (err instanceof OxcNotImplementedError) {
|
|
1895
|
+
const conditionalSpreadClassExpr = buildConditionalSpreadClassExpression(
|
|
1896
|
+
expression,
|
|
1897
|
+
effectiveFilename,
|
|
1898
|
+
objectBindings,
|
|
1899
|
+
source,
|
|
1900
|
+
classes
|
|
1901
|
+
);
|
|
1902
|
+
if (conditionalSpreadClassExpr) {
|
|
1903
|
+
if (classNameAttr || szAttrs.length > 1) {
|
|
1904
|
+
runtimeFallbackExpr = expression;
|
|
1905
|
+
runtimeFallbackAttr = szAttr;
|
|
1906
|
+
break;
|
|
1907
|
+
}
|
|
1908
|
+
edits.overwrite(
|
|
1909
|
+
szAttr.start,
|
|
1910
|
+
szAttr.end,
|
|
1911
|
+
`className={${conditionalSpreadClassExpr}}`
|
|
1912
|
+
);
|
|
1913
|
+
transformed = true;
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
const partial = buildPartialObjectTransform(
|
|
1917
|
+
expression,
|
|
1918
|
+
effectiveFilename,
|
|
1919
|
+
objectBindings,
|
|
1920
|
+
source
|
|
1921
|
+
);
|
|
1922
|
+
if (partial && szAttrs.length === 1) {
|
|
1923
|
+
if (classNameAttr?.value?.type === "JSXExpressionContainer") {
|
|
1924
|
+
const classExpression = classNameAttr.value.expression;
|
|
1925
|
+
const classExpressionSource = source.slice(
|
|
1926
|
+
classExpression.start,
|
|
1927
|
+
classExpression.end
|
|
1928
|
+
);
|
|
1929
|
+
edits.overwrite(
|
|
1930
|
+
classNameAttr.start,
|
|
1931
|
+
classNameAttr.end,
|
|
1932
|
+
`className={_szMerge(${classExpressionSource}, ${JSON.stringify(partial.className)})}`
|
|
1933
|
+
);
|
|
1934
|
+
edits.remove(whitespaceStart(source, szAttr.start), szAttr.end);
|
|
1935
|
+
applyStyleProps(edits, source, styleAttr, lastAttr, partial.styleProps);
|
|
1936
|
+
for (const c of partial.className.split(/\s+/)) {
|
|
1937
|
+
if (c) {
|
|
1938
|
+
classes.add(c);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
usesRuntime = true;
|
|
1942
|
+
usesMerge = true;
|
|
1943
|
+
usesColorVar ||= partial.usesColorVar;
|
|
1944
|
+
transformed = true;
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
if (classNameAttr && stringLiteralValue(classNameAttr.value) !== null) {
|
|
1948
|
+
const existing = stringLiteralValue(classNameAttr.value);
|
|
1949
|
+
const merged = [existing, partial.className].filter(Boolean).join(" ");
|
|
1950
|
+
edits.overwrite(
|
|
1951
|
+
classNameAttr.start,
|
|
1952
|
+
classNameAttr.end,
|
|
1953
|
+
`className="${merged}"`
|
|
1954
|
+
);
|
|
1955
|
+
edits.remove(whitespaceStart(source, szAttr.start), szAttr.end);
|
|
1956
|
+
} else {
|
|
1957
|
+
edits.overwrite(szAttr.start, szAttr.end, partial.classNameAttr);
|
|
1958
|
+
}
|
|
1959
|
+
applyStyleProps(edits, source, styleAttr, lastAttr, partial.styleProps);
|
|
1960
|
+
for (const c of partial.className.split(/\s+/)) {
|
|
1961
|
+
if (c) {
|
|
1962
|
+
classes.add(c);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
usesColorVar ||= partial.usesColorVar;
|
|
1966
|
+
transformed = true;
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
runtimeFallbackExpr = expression;
|
|
1970
|
+
runtimeFallbackAttr = szAttr;
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
throw err;
|
|
1974
|
+
}
|
|
1975
|
+
const result = transform(szObj);
|
|
1976
|
+
for (const c of result.className.split(/\s+/)) {
|
|
1977
|
+
if (c) {
|
|
1978
|
+
szDerived.push(c);
|
|
1979
|
+
classes.add(c);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
if (runtimeFallbackExpr && runtimeFallbackAttr) {
|
|
1984
|
+
if (classNameAttr) {
|
|
1985
|
+
throw new OxcNotImplementedError(
|
|
1986
|
+
"D2.5+",
|
|
1987
|
+
`runtime sz fallback combined with existing className at ${effectiveFilename}:${runtimeFallbackAttr.start}`
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
const exprSource = source.slice(runtimeFallbackExpr.start, runtimeFallbackExpr.end);
|
|
1991
|
+
if (runtimeFallbackExpr.type !== "ArrayExpression") {
|
|
1992
|
+
diagnostics.push(buildRuntimeFallbackDiagnostic(runtimeFallbackExpr, source));
|
|
1993
|
+
}
|
|
1994
|
+
edits.overwrite(
|
|
1995
|
+
runtimeFallbackAttr.start,
|
|
1996
|
+
runtimeFallbackAttr.end,
|
|
1997
|
+
`className={_sz(${exprSource})}`
|
|
1998
|
+
);
|
|
1999
|
+
for (const szAttr of szAttrs) {
|
|
2000
|
+
if (szAttr === runtimeFallbackAttr) continue;
|
|
2001
|
+
const deleteStart = whitespaceStart(source, szAttr.start);
|
|
2002
|
+
edits.remove(deleteStart, szAttr.end);
|
|
2003
|
+
}
|
|
2004
|
+
usesRuntime = true;
|
|
2005
|
+
transformed = true;
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const existingRaw = classNameAttr ? stringLiteralValue(classNameAttr.value) : null;
|
|
2009
|
+
const mergedClasses = [
|
|
2010
|
+
...existingRaw ? existingRaw.split(/\s+/).filter(Boolean) : [],
|
|
2011
|
+
...szDerived
|
|
2012
|
+
];
|
|
2013
|
+
const mergedAttr = `className="${mergedClasses.join(" ")}"`;
|
|
2014
|
+
if (classNameAttr) {
|
|
2015
|
+
edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
|
|
2016
|
+
for (const szAttr of szAttrs) {
|
|
2017
|
+
const deleteStart = whitespaceStart(source, szAttr.start);
|
|
2018
|
+
edits.remove(deleteStart, szAttr.end);
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
const [firstSz, ...rest] = szAttrs;
|
|
2022
|
+
if (!firstSz) {
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
edits.overwrite(firstSz.start, firstSz.end, mergedAttr);
|
|
2026
|
+
for (const szAttr of rest) {
|
|
2027
|
+
const deleteStart = whitespaceStart(source, szAttr.start);
|
|
2028
|
+
edits.remove(deleteStart, szAttr.end);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
transformed = true;
|
|
2032
|
+
});
|
|
2033
|
+
return {
|
|
2034
|
+
code: transformed ? edits.toString() : source,
|
|
2035
|
+
transformed,
|
|
2036
|
+
usesRuntime,
|
|
2037
|
+
usesMerge,
|
|
2038
|
+
usesColorVar,
|
|
2039
|
+
classes,
|
|
2040
|
+
rawClassNames,
|
|
2041
|
+
diagnostics,
|
|
2042
|
+
recoveryTokens
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
function stringLiteralValue(value) {
|
|
2046
|
+
if (!value) {
|
|
2047
|
+
return null;
|
|
2048
|
+
}
|
|
2049
|
+
if (value.type === "Literal") {
|
|
2050
|
+
const v = value.value;
|
|
2051
|
+
return typeof v === "string" ? v : null;
|
|
2052
|
+
}
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
2055
|
+
function whitespaceStart(source, attrStart) {
|
|
2056
|
+
let idx = attrStart;
|
|
2057
|
+
while (idx > 0 && /\s/.test(source.charAt(idx - 1))) {
|
|
2058
|
+
idx--;
|
|
2059
|
+
}
|
|
2060
|
+
return idx;
|
|
2061
|
+
}
|
|
2062
|
+
function offsetToLineColumn(source, offset) {
|
|
2063
|
+
let line = 1;
|
|
2064
|
+
let column = 0;
|
|
2065
|
+
const limit = Math.min(offset, source.length);
|
|
2066
|
+
for (let i = 0; i < limit; i++) {
|
|
2067
|
+
if (source.charCodeAt(i) === 10) {
|
|
2068
|
+
line++;
|
|
2069
|
+
column = 0;
|
|
2070
|
+
} else {
|
|
2071
|
+
column++;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return { line, column };
|
|
2075
|
+
}
|
|
2076
|
+
function buildRuntimeFallbackDiagnostic(expression, source) {
|
|
2077
|
+
const { line, column } = offsetToLineColumn(source, expression.start);
|
|
2078
|
+
const lineCol = `${line}:${column + 1}`;
|
|
2079
|
+
let reason;
|
|
2080
|
+
let suggestion;
|
|
2081
|
+
if (expression.type === "CallExpression") {
|
|
2082
|
+
const callee = expression.callee;
|
|
2083
|
+
const name = callee.type === "Identifier" ? callee.name : callee.type === "MemberExpression" && (callee.property?.type ?? "") === "Identifier" ? String(
|
|
2084
|
+
callee.property.name
|
|
2085
|
+
) : "?";
|
|
2086
|
+
reason = `function call \`${name}()\` result is unknown at build time`;
|
|
2087
|
+
suggestion = "If it returns static variants \u2192 convert to szv(). If it depends on runtime data \u2192 use dynamic().";
|
|
2088
|
+
} else if (expression.type === "Identifier") {
|
|
2089
|
+
reason = `identifier \`${expression.name}\` could not be resolved to a static value`;
|
|
2090
|
+
suggestion = "Make sure it's a module-level or function-body const with a literal object value. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
|
|
2091
|
+
} else if (expression.type === "MemberExpression") {
|
|
2092
|
+
reason = "member expression is not statically resolvable";
|
|
2093
|
+
suggestion = "Extract the value to a module-level const. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
|
|
2094
|
+
} else {
|
|
2095
|
+
reason = `expression of type \`${expression.type}\` is not statically analyzable`;
|
|
2096
|
+
suggestion = "Use a literal sz object or a module-level const. For variant-based styling \u2192 szv(). For true runtime values \u2192 dynamic().";
|
|
2097
|
+
}
|
|
2098
|
+
return `sz fallback at ${lineCol}: ${reason}.
|
|
2099
|
+
Suggestion: ${suggestion}`;
|
|
2100
|
+
}
|
|
2101
|
+
function extractElementName(nameNode) {
|
|
2102
|
+
if (nameNode.type === "JSXIdentifier") {
|
|
2103
|
+
return String(nameNode.name);
|
|
2104
|
+
}
|
|
2105
|
+
if (nameNode.type === "JSXMemberExpression") {
|
|
2106
|
+
return "<member>";
|
|
2107
|
+
}
|
|
2108
|
+
return "<unknown>";
|
|
2109
|
+
}
|
|
2110
|
+
function astObjectToSzObject(node, filename, bindings) {
|
|
2111
|
+
const result = {};
|
|
2112
|
+
for (const propRaw of node.properties) {
|
|
2113
|
+
if (propRaw.type === "SpreadElement") {
|
|
2114
|
+
const spread = propRaw;
|
|
2115
|
+
if (spread.argument.type === "Identifier") {
|
|
2116
|
+
const bound = bindings.get(String(spread.argument.name));
|
|
2117
|
+
if (bound) {
|
|
2118
|
+
Object.assign(result, astObjectToSzObject(bound, filename, bindings));
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
throw new OxcNotImplementedError(
|
|
2123
|
+
"D5",
|
|
2124
|
+
`unsupported object spread in sz object at ${filename}:${propRaw.start}`
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
if (propRaw.type !== "Property") {
|
|
2128
|
+
throw new OxcNotImplementedError(
|
|
2129
|
+
"D5",
|
|
2130
|
+
`non-Property in sz object (e.g. SpreadElement) at ${filename}:${propRaw.start}`
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
const prop = propRaw;
|
|
2134
|
+
if (prop.computed) {
|
|
2135
|
+
throw new OxcNotImplementedError(
|
|
2136
|
+
"D2.1",
|
|
2137
|
+
`computed key in sz object at ${filename}:${prop.key.start}`
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
const key = extractKeyName(prop.key);
|
|
2141
|
+
if (key === null) {
|
|
2142
|
+
throw new OxcNotImplementedError(
|
|
2143
|
+
"D2.1",
|
|
2144
|
+
`unsupported key shape ${prop.key.type} at ${filename}:${prop.key.start}`
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
result[key] = astValueToSzValue(prop.value, filename, bindings);
|
|
2148
|
+
}
|
|
2149
|
+
return result;
|
|
2150
|
+
}
|
|
2151
|
+
function astArrayToStaticClasses(node, filename, bindings) {
|
|
2152
|
+
const out = [];
|
|
2153
|
+
for (const element of node.elements) {
|
|
2154
|
+
if (!element || isFalsyLiteral(element)) {
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
let objectNode = null;
|
|
2158
|
+
if (element.type === "ObjectExpression") {
|
|
2159
|
+
objectNode = element;
|
|
2160
|
+
} else if (element.type === "Identifier") {
|
|
2161
|
+
objectNode = bindings.get(String(element.name)) ?? null;
|
|
2162
|
+
}
|
|
2163
|
+
if (!objectNode) {
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
let result;
|
|
2167
|
+
try {
|
|
2168
|
+
result = transform(astObjectToSzObject(objectNode, filename, bindings));
|
|
2169
|
+
} catch (err) {
|
|
2170
|
+
if (err instanceof OxcNotImplementedError) {
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
throw err;
|
|
2174
|
+
}
|
|
2175
|
+
for (const c of result.className.split(/\s+/)) {
|
|
2176
|
+
if (c) {
|
|
2177
|
+
out.push(c);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return out;
|
|
2182
|
+
}
|
|
2183
|
+
function collectArrayCandidateClasses(node, filename, bindings, classes) {
|
|
2184
|
+
for (const element of node.elements) {
|
|
2185
|
+
if (!element || isFalsyLiteral(element)) {
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
const candidate = element.type === "LogicalExpression" && element.operator === "&&" ? element.right : element;
|
|
2189
|
+
collectStaticObjectCandidateClasses(candidate, filename, bindings, classes);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
function collectStaticObjectCandidateClasses(node, filename, bindings, classes) {
|
|
2193
|
+
const objectNode = resolveObjectExpression(node, bindings);
|
|
2194
|
+
if (!objectNode) {
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
let result;
|
|
2198
|
+
try {
|
|
2199
|
+
result = transform(astObjectToSzObject(objectNode, filename, bindings));
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
if (err instanceof OxcNotImplementedError) {
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
throw err;
|
|
2205
|
+
}
|
|
2206
|
+
for (const cls of result.className.split(/\s+/)) {
|
|
2207
|
+
if (cls) {
|
|
2208
|
+
classes.add(cls);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
function isFalsyLiteral(node) {
|
|
2213
|
+
if (node.type !== "Literal") {
|
|
2214
|
+
return false;
|
|
2215
|
+
}
|
|
2216
|
+
const value = node.value;
|
|
2217
|
+
return value === false || value === null;
|
|
2218
|
+
}
|
|
2219
|
+
function assertAstBudget(root, filename, astBudget) {
|
|
2220
|
+
let nodeCount = 0;
|
|
2221
|
+
walk(root, () => {
|
|
2222
|
+
nodeCount++;
|
|
2223
|
+
if (nodeCount > astBudget) {
|
|
2224
|
+
throw new ASTBudgetExceededError(filename, nodeCount, astBudget);
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
function collectDynamicCallClasses(node, filename, bindings, classes) {
|
|
2229
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "dynamic") {
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const [firstArg] = node.arguments;
|
|
2233
|
+
if (!firstArg) {
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
const objectNode = resolveObjectExpression(firstArg, bindings);
|
|
2237
|
+
if (!objectNode) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
let result;
|
|
2241
|
+
try {
|
|
2242
|
+
result = transform(astObjectToSzObject(objectNode, filename, bindings));
|
|
2243
|
+
} catch (err) {
|
|
2244
|
+
if (err instanceof OxcNotImplementedError) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
throw err;
|
|
2248
|
+
}
|
|
2249
|
+
for (const cls of result.className.split(/\s+/)) {
|
|
2250
|
+
if (cls) {
|
|
2251
|
+
classes.add(cls);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
function resolveObjectExpression(node, bindings) {
|
|
2256
|
+
const unwrapped = unwrapExpression(node);
|
|
2257
|
+
if (unwrapped.type === "ObjectExpression") {
|
|
2258
|
+
return unwrapped;
|
|
2259
|
+
}
|
|
2260
|
+
if (unwrapped.type === "Identifier") {
|
|
2261
|
+
return bindings.get(String(unwrapped.name)) ?? null;
|
|
2262
|
+
}
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes) {
|
|
2266
|
+
let conditionalSpread = null;
|
|
2267
|
+
const otherProps = [];
|
|
2268
|
+
for (const prop of node.properties) {
|
|
2269
|
+
if (prop.type !== "SpreadElement") {
|
|
2270
|
+
otherProps.push(prop);
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
const spread = prop;
|
|
2274
|
+
const spreadArgument = unwrapExpression(spread.argument);
|
|
2275
|
+
if (spreadArgument.type !== "ConditionalExpression" || conditionalSpread) {
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2278
|
+
conditionalSpread = spreadArgument;
|
|
2279
|
+
}
|
|
2280
|
+
if (!conditionalSpread) {
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
const consequent = compileConditionalSpreadBranch(
|
|
2284
|
+
conditionalSpread.consequent,
|
|
2285
|
+
otherProps,
|
|
2286
|
+
node,
|
|
2287
|
+
filename,
|
|
2288
|
+
bindings
|
|
2289
|
+
);
|
|
2290
|
+
const alternate = compileConditionalSpreadBranch(
|
|
2291
|
+
conditionalSpread.alternate,
|
|
2292
|
+
otherProps,
|
|
2293
|
+
node,
|
|
2294
|
+
filename,
|
|
2295
|
+
bindings
|
|
2296
|
+
);
|
|
2297
|
+
if (consequent === null || alternate === null) {
|
|
2298
|
+
return null;
|
|
2299
|
+
}
|
|
2300
|
+
for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
|
|
2301
|
+
if (cls) {
|
|
2302
|
+
classes.add(cls);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
const testSource = source.slice(conditionalSpread.test.start, conditionalSpread.test.end);
|
|
2306
|
+
return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
|
|
2307
|
+
}
|
|
2308
|
+
function compileConditionalSpreadBranch(branch, otherProps, sourceNode, filename, bindings) {
|
|
2309
|
+
const branchObject = resolveObjectExpression(branch, bindings);
|
|
2310
|
+
if (!branchObject) {
|
|
2311
|
+
return null;
|
|
2312
|
+
}
|
|
2313
|
+
try {
|
|
2314
|
+
const branchValue = astObjectToSzObject(branchObject, filename, bindings);
|
|
2315
|
+
const overrides = astObjectToSzObject(
|
|
2316
|
+
{ ...sourceNode, properties: otherProps },
|
|
2317
|
+
filename,
|
|
2318
|
+
bindings
|
|
2319
|
+
);
|
|
2320
|
+
return transform({ ...branchValue, ...overrides }).className;
|
|
2321
|
+
} catch (err) {
|
|
2322
|
+
if (err instanceof OxcNotImplementedError) {
|
|
2323
|
+
return null;
|
|
2324
|
+
}
|
|
2325
|
+
throw err;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function buildPartialObjectTransform(node, filename, bindings, source) {
|
|
2329
|
+
const partial = evaluatePartialObject(node, filename, bindings, source);
|
|
2330
|
+
if (!partial || partial.dynamicProps.size === 0 && partial.conditionalClasses.length === 0) {
|
|
2331
|
+
return null;
|
|
2332
|
+
}
|
|
2333
|
+
if (partial.conditionalClasses.length > 0 && (partial.conditionalClasses.length !== 1 || partial.dynamicProps.size > 0 || Object.keys(partial.staticProps).length > 0)) {
|
|
2334
|
+
return null;
|
|
2335
|
+
}
|
|
2336
|
+
const classParts = [];
|
|
2337
|
+
if (Object.keys(partial.staticProps).length > 0) {
|
|
2338
|
+
const { className: className2 } = transform(partial.staticProps);
|
|
2339
|
+
if (className2) {
|
|
2340
|
+
classParts.push(className2);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
for (const [, info] of partial.dynamicProps) {
|
|
2344
|
+
classParts.push(buildCSSVarClassName(info));
|
|
2345
|
+
}
|
|
2346
|
+
for (const entry of partial.conditionalClasses) {
|
|
2347
|
+
classParts.push(entry.consequent, entry.alternate);
|
|
2348
|
+
}
|
|
2349
|
+
const className = classParts.filter(Boolean).join(" ");
|
|
2350
|
+
const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : `className="${className}"`;
|
|
2351
|
+
const styleProps = [...partial.dynamicProps.values()].map(
|
|
2352
|
+
(info) => `${JSON.stringify(info.varName)}: ${generateStyleValueSource(info, source)}`
|
|
2353
|
+
);
|
|
2354
|
+
return { className, classNameAttr, styleProps, usesColorVar: partial.usesColorVar };
|
|
2355
|
+
}
|
|
2356
|
+
function evaluatePartialObject(node, filename, bindings, source, variantChain = "") {
|
|
2357
|
+
const staticProps = {};
|
|
2358
|
+
const dynamicProps = /* @__PURE__ */ new Map();
|
|
2359
|
+
const conditionalClasses = [];
|
|
2360
|
+
let usesColorVar = false;
|
|
2361
|
+
for (const propRaw of node.properties) {
|
|
2362
|
+
if (propRaw.type === "SpreadElement") {
|
|
2363
|
+
const spread = propRaw;
|
|
2364
|
+
const objectNode = resolveObjectExpression(spread.argument, bindings);
|
|
2365
|
+
if (!objectNode) {
|
|
2366
|
+
return null;
|
|
2367
|
+
}
|
|
2368
|
+
try {
|
|
2369
|
+
Object.assign(staticProps, astObjectToSzObject(objectNode, filename, bindings));
|
|
2370
|
+
continue;
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
if (err instanceof OxcNotImplementedError) {
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
throw err;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
if (propRaw.type !== "Property") {
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
const prop = propRaw;
|
|
2382
|
+
if (prop.computed) {
|
|
2383
|
+
return null;
|
|
2384
|
+
}
|
|
2385
|
+
const key = extractKeyName(prop.key);
|
|
2386
|
+
if (key === null) {
|
|
2387
|
+
return null;
|
|
2388
|
+
}
|
|
2389
|
+
const value = unwrapExpression(prop.value);
|
|
2390
|
+
try {
|
|
2391
|
+
if (value.type === "ObjectExpression") {
|
|
2392
|
+
staticProps[key] = astObjectToSzObject(
|
|
2393
|
+
value,
|
|
2394
|
+
filename,
|
|
2395
|
+
bindings
|
|
2396
|
+
);
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
staticProps[key] = astValueToSzValue(value, filename, bindings);
|
|
2400
|
+
continue;
|
|
2401
|
+
} catch (err) {
|
|
2402
|
+
if (!(err instanceof OxcNotImplementedError)) {
|
|
2403
|
+
throw err;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (value.type === "ObjectExpression" && isKnownVariant(key)) {
|
|
2407
|
+
const nestedVariant = variantChain ? `${variantChain}-${key}` : key;
|
|
2408
|
+
const nested = evaluatePartialObject(
|
|
2409
|
+
value,
|
|
2410
|
+
filename,
|
|
2411
|
+
bindings,
|
|
2412
|
+
source,
|
|
2413
|
+
nestedVariant
|
|
2414
|
+
);
|
|
2415
|
+
if (!nested) {
|
|
2416
|
+
return null;
|
|
2417
|
+
}
|
|
2418
|
+
if (Object.keys(nested.staticProps).length > 0) {
|
|
2419
|
+
staticProps[key] = nested.staticProps;
|
|
2420
|
+
}
|
|
2421
|
+
for (const [nestedKey, nestedInfo] of nested.dynamicProps) {
|
|
2422
|
+
dynamicProps.set(nestedKey, nestedInfo);
|
|
2423
|
+
}
|
|
2424
|
+
conditionalClasses.push(...nested.conditionalClasses);
|
|
2425
|
+
usesColorVar ||= nested.usesColorVar;
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
if (value.type === "ConditionalExpression") {
|
|
2429
|
+
const conditional = value;
|
|
2430
|
+
const consequent = extractStaticLiteralValue(conditional.consequent);
|
|
2431
|
+
const alternate = extractStaticLiteralValue(conditional.alternate);
|
|
2432
|
+
if (consequent !== null && alternate !== null) {
|
|
2433
|
+
const { className: consequentClasses } = transform({ [key]: consequent });
|
|
2434
|
+
const { className: alternateClasses } = transform({ [key]: alternate });
|
|
2435
|
+
conditionalClasses.push({
|
|
2436
|
+
test: conditional.test,
|
|
2437
|
+
consequent: prefixVariantClasses(consequentClasses, variantChain),
|
|
2438
|
+
alternate: prefixVariantClasses(alternateClasses, variantChain)
|
|
2439
|
+
});
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (!isRuntimeExpression(value)) {
|
|
2444
|
+
return null;
|
|
2445
|
+
}
|
|
2446
|
+
const twPrefix = PROPERTY_MAP[key] || key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
2447
|
+
const category = getPropertyCategory(key);
|
|
2448
|
+
const varName = getCSSVariableName(key, variantChain || void 0);
|
|
2449
|
+
const uniqueKey = variantChain ? `${variantChain}-${key}` : key;
|
|
2450
|
+
if (COLOR_PROPERTIES.has(key)) {
|
|
2451
|
+
usesColorVar = true;
|
|
2452
|
+
}
|
|
2453
|
+
dynamicProps.set(uniqueKey, {
|
|
2454
|
+
expression: value,
|
|
2455
|
+
category,
|
|
2456
|
+
varName,
|
|
2457
|
+
twPrefix,
|
|
2458
|
+
variantChain
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
return { staticProps, dynamicProps, conditionalClasses, usesColorVar };
|
|
2462
|
+
}
|
|
2463
|
+
function applyStyleProps(edits, source, styleAttr, lastAttr, styleProps) {
|
|
2464
|
+
if (styleProps.length === 0) {
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
const propsSource = styleProps.join(", ");
|
|
2468
|
+
if (!styleAttr) {
|
|
2469
|
+
if (lastAttr) {
|
|
2470
|
+
edits.appendRight(lastAttr.end, ` style={{${propsSource}}}`);
|
|
2471
|
+
}
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
if (styleAttr.value?.type !== "JSXExpressionContainer") {
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
const expression = styleAttr.value.expression;
|
|
2478
|
+
const styleSource = source.slice(expression.start, expression.end);
|
|
2479
|
+
edits.overwrite(styleAttr.start, styleAttr.end, `style={{...${styleSource}, ${propsSource}}}`);
|
|
2480
|
+
}
|
|
2481
|
+
function generateStyleValueSource(info, source) {
|
|
2482
|
+
const expressionSource = source.slice(info.expression.start, info.expression.end);
|
|
2483
|
+
switch (info.category) {
|
|
2484
|
+
case PropertyCategory.SPACING:
|
|
2485
|
+
return `\`calc(\${${expressionSource}} * var(--spacing))\``;
|
|
2486
|
+
case PropertyCategory.COLOR:
|
|
2487
|
+
return `__szColorVar(${expressionSource})`;
|
|
2488
|
+
case PropertyCategory.ANGLE:
|
|
2489
|
+
return `\`\${${expressionSource}}deg\``;
|
|
2490
|
+
case PropertyCategory.DURATION:
|
|
2491
|
+
return `\`\${${expressionSource}}ms\``;
|
|
2492
|
+
default:
|
|
2493
|
+
return `\`\${${expressionSource}}\``;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
function buildCSSVarClassName(info) {
|
|
2497
|
+
const variantPrefix = info.variantChain ? `${getVariantPrefix(info.variantChain)}:` : "";
|
|
2498
|
+
return `${variantPrefix}${info.twPrefix}-(${info.varName})`;
|
|
2499
|
+
}
|
|
2500
|
+
function buildConditionalClassSource(classParts, conditionals, source) {
|
|
2501
|
+
if (conditionals.length === 1 && classParts.length === 2) {
|
|
2502
|
+
const [entry] = conditionals;
|
|
2503
|
+
return `${source.slice(entry.test.start, entry.test.end)} ? ${JSON.stringify(entry.consequent)} : ${JSON.stringify(entry.alternate)}`;
|
|
2504
|
+
}
|
|
2505
|
+
return JSON.stringify(classParts.filter(Boolean).join(" "));
|
|
2506
|
+
}
|
|
2507
|
+
function extractStaticLiteralValue(node) {
|
|
2508
|
+
const unwrapped = unwrapExpression(node);
|
|
2509
|
+
if (unwrapped.type !== "Literal") {
|
|
2510
|
+
return null;
|
|
2511
|
+
}
|
|
2512
|
+
const value = unwrapped.value;
|
|
2513
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : null;
|
|
2514
|
+
}
|
|
2515
|
+
function isKnownVariant(key) {
|
|
2516
|
+
return KNOWN_VARIANTS.has(key) || KNOWN_VARIANTS.has(getVariantPrefix(key));
|
|
2517
|
+
}
|
|
2518
|
+
function prefixVariantClasses(className, variantChain) {
|
|
2519
|
+
if (!variantChain) {
|
|
2520
|
+
return className;
|
|
2521
|
+
}
|
|
2522
|
+
const prefix = `${getVariantPrefix(variantChain)}:`;
|
|
2523
|
+
return className.split(/\s+/).filter(Boolean).map((cls) => `${prefix}${cls}`).join(" ");
|
|
2524
|
+
}
|
|
2525
|
+
function isRuntimeExpression(node) {
|
|
2526
|
+
return node.type === "Identifier" || node.type === "MemberExpression" || node.type === "CallExpression" || node.type === "ConditionalExpression" || node.type === "TemplateLiteral" || node.type === "BinaryExpression" || node.type === "LogicalExpression";
|
|
2527
|
+
}
|
|
2528
|
+
function buildStaticConditionalClassExpression(node, filename, bindings, source, classes) {
|
|
2529
|
+
const consequent = resolveStaticClassString(node.consequent, filename, bindings);
|
|
2530
|
+
const alternate = resolveStaticClassString(node.alternate, filename, bindings);
|
|
2531
|
+
if (consequent === null || alternate === null) {
|
|
2532
|
+
return null;
|
|
2533
|
+
}
|
|
2534
|
+
for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
|
|
2535
|
+
if (cls) {
|
|
2536
|
+
classes.add(cls);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
const testSource = source.slice(node.test.start, node.test.end);
|
|
2540
|
+
return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
|
|
2541
|
+
}
|
|
2542
|
+
function resolveStaticClassString(node, filename, bindings) {
|
|
2543
|
+
const unwrapped = unwrapExpression(node);
|
|
2544
|
+
let objectNode = null;
|
|
2545
|
+
if (unwrapped.type === "ObjectExpression") {
|
|
2546
|
+
objectNode = unwrapped;
|
|
2547
|
+
} else if (unwrapped.type === "Identifier") {
|
|
2548
|
+
objectNode = bindings.get(String(unwrapped.name)) ?? null;
|
|
2549
|
+
}
|
|
2550
|
+
if (!objectNode) {
|
|
2551
|
+
return null;
|
|
2552
|
+
}
|
|
2553
|
+
try {
|
|
2554
|
+
return transform(astObjectToSzObject(objectNode, filename, bindings)).className;
|
|
2555
|
+
} catch (err) {
|
|
2556
|
+
if (err instanceof OxcNotImplementedError) {
|
|
2557
|
+
return null;
|
|
2558
|
+
}
|
|
2559
|
+
throw err;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
function extractKeyName(key) {
|
|
2563
|
+
if (key.type === "Identifier") {
|
|
2564
|
+
return String(key.name);
|
|
2565
|
+
}
|
|
2566
|
+
if (key.type === "Literal") {
|
|
2567
|
+
const value = key.value;
|
|
2568
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2569
|
+
return String(value);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
return null;
|
|
2573
|
+
}
|
|
2574
|
+
function astValueToSzValue(node, filename, bindings) {
|
|
2575
|
+
if (node.type === "Literal") {
|
|
2576
|
+
const value = node.value;
|
|
2577
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2578
|
+
return value;
|
|
2579
|
+
}
|
|
2580
|
+
throw new OxcNotImplementedError(
|
|
2581
|
+
"D2.1",
|
|
2582
|
+
`unsupported literal value type at ${filename}:${node.start}`
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
if (node.type === "UnaryExpression") {
|
|
2586
|
+
const operator = node.operator;
|
|
2587
|
+
const argument = node.argument;
|
|
2588
|
+
if (operator === "-" && argument.type === "Literal") {
|
|
2589
|
+
const argValue = argument.value;
|
|
2590
|
+
if (typeof argValue === "number") {
|
|
2591
|
+
return -argValue;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
throw new OxcNotImplementedError(
|
|
2595
|
+
"D2.1",
|
|
2596
|
+
`unsupported unary expression at ${filename}:${node.start}`
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
if (node.type === "ObjectExpression") {
|
|
2600
|
+
return astObjectToSzObject(node, filename, bindings);
|
|
2601
|
+
}
|
|
2602
|
+
if (node.type === "Identifier" || node.type === "MemberExpression") {
|
|
2603
|
+
throw new OxcNotImplementedError(
|
|
2604
|
+
"D2.1",
|
|
2605
|
+
`identifier reference in sz object \u2014 scope resolution lands in a later slice (${filename}:${node.start})`
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
if (node.type === "ConditionalExpression" || node.type === "LogicalExpression") {
|
|
2609
|
+
throw new OxcNotImplementedError(
|
|
2610
|
+
"D2.5",
|
|
2611
|
+
`conditional/logical expression in sz object at ${filename}:${node.start}`
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
throw new OxcNotImplementedError(
|
|
2615
|
+
"D2.1",
|
|
2616
|
+
`unsupported value node type ${node.type} at ${filename}:${node.start}`
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
function collectObjectBindings(root) {
|
|
2620
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
2621
|
+
walk(root, (node) => {
|
|
2622
|
+
if (node.type !== "VariableDeclarator") {
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
const id = node.id;
|
|
2626
|
+
const init = node.init;
|
|
2627
|
+
if (!id || id.type !== "Identifier" || !init) {
|
|
2628
|
+
return;
|
|
2629
|
+
}
|
|
2630
|
+
const unwrapped = unwrapExpression(init);
|
|
2631
|
+
if (unwrapped.type === "ObjectExpression") {
|
|
2632
|
+
bindings.set(String(id.name), unwrapped);
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
return bindings;
|
|
2636
|
+
}
|
|
2637
|
+
function collectConditionalBindings(root) {
|
|
2638
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
2639
|
+
walk(root, (node) => {
|
|
2640
|
+
if (node.type !== "VariableDeclarator") {
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
const id = node.id;
|
|
2644
|
+
const init = node.init;
|
|
2645
|
+
if (!id || id.type !== "Identifier" || !init) {
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
const unwrapped = unwrapExpression(init);
|
|
2649
|
+
if (unwrapped.type === "ConditionalExpression") {
|
|
2650
|
+
bindings.set(
|
|
2651
|
+
String(id.name),
|
|
2652
|
+
unwrapped
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
return bindings;
|
|
2657
|
+
}
|
|
2658
|
+
function unwrapExpression(node) {
|
|
2659
|
+
let current = node;
|
|
2660
|
+
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression" || current.type === "ParenthesizedExpression") {
|
|
2661
|
+
const next = current.expression;
|
|
2662
|
+
if (!next) {
|
|
2663
|
+
break;
|
|
2664
|
+
}
|
|
2665
|
+
current = next;
|
|
2666
|
+
}
|
|
2667
|
+
return current;
|
|
2668
|
+
}
|
|
2669
|
+
function walk(node, visit) {
|
|
2670
|
+
if (!node || typeof node !== "object") {
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const typed = node;
|
|
2674
|
+
if (typeof typed.type !== "string") {
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
visit(typed);
|
|
2678
|
+
for (const key of Object.keys(typed)) {
|
|
2679
|
+
if (key === "loc" || key === "range" || key === "start" || key === "end" || key === "type") {
|
|
2680
|
+
continue;
|
|
2681
|
+
}
|
|
2682
|
+
const child = typed[key];
|
|
2683
|
+
if (Array.isArray(child)) {
|
|
2684
|
+
for (const item of child) {
|
|
2685
|
+
walk(item, visit);
|
|
2686
|
+
}
|
|
2687
|
+
} else if (child && typeof child === "object") {
|
|
2688
|
+
walk(child, visit);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
const VERSION = "0.0.0";
|
|
2694
|
+
const DEFAULT_COMPILER_OPTIONS = {
|
|
2695
|
+
buildId: Date.now().toString(),
|
|
2696
|
+
development: process.env.NODE_ENV !== "production",
|
|
2697
|
+
strictMode: false
|
|
2698
|
+
};
|
|
2699
|
+
function mergeOptions(options = {}) {
|
|
2700
|
+
return {
|
|
2701
|
+
...DEFAULT_COMPILER_OPTIONS,
|
|
2702
|
+
...options
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, KNOWN_VARIANTS, ManifestBuilder, OxcNotImplementedError, PROPERTY_MAP, PropertyCategory, VERSION, buildParentMap, createRecoveryToken, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isValidRecoveryMode, mergeOptions, parseManifest, serializeManifest, transform, transformOxc, transformSourceCode, validateManifest, validateSzRecover };
|