@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/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 };