praxis-kit 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/_shared/diagnostics.d.ts +312 -0
  4. package/dist/_shared/diagnostics.js +360 -0
  5. package/dist/build-runtime-CJ_nQEaZ.js +5065 -0
  6. package/dist/codemod/index.d.ts +2 -0
  7. package/dist/codemod/index.js +176520 -0
  8. package/dist/contract/index.d.ts +677 -0
  9. package/dist/contract/index.js +341 -0
  10. package/dist/eslint/index.d.ts +90 -0
  11. package/dist/eslint/index.js +1047 -0
  12. package/dist/guards/index.d.ts +78 -0
  13. package/dist/guards/index.js +118 -0
  14. package/dist/html/index.d.ts +151 -0
  15. package/dist/html/index.js +1244 -0
  16. package/dist/index-BIBd_iPD.d.ts +951 -0
  17. package/dist/lit/index.d.ts +862 -0
  18. package/dist/lit/index.js +4893 -0
  19. package/dist/preact/index.d.ts +796 -0
  20. package/dist/preact/index.js +5043 -0
  21. package/dist/react/index.d.ts +28 -0
  22. package/dist/react/index.js +205 -0
  23. package/dist/react/legacy.d.ts +29 -0
  24. package/dist/react/legacy.js +80 -0
  25. package/dist/solid/index.d.ts +728 -0
  26. package/dist/solid/index.js +4821 -0
  27. package/dist/svelte/Polymorphic.svelte +190 -0
  28. package/dist/svelte/_polymorphic-runtime.d.ts +102 -0
  29. package/dist/svelte/_polymorphic-runtime.js +371 -0
  30. package/dist/svelte/index.d.ts +994 -0
  31. package/dist/svelte/index.js +4482 -0
  32. package/dist/tailwind/index.d.ts +197 -0
  33. package/dist/tailwind/index.js +767 -0
  34. package/dist/tailwind/safelist.css +20 -0
  35. package/dist/ts-plugin/index.cjs +166 -0
  36. package/dist/ts-plugin/index.d.cts +9 -0
  37. package/dist/utils/index.d.ts +19 -0
  38. package/dist/utils/index.js +21 -0
  39. package/dist/vite-plugin/index.d.ts +200 -0
  40. package/dist/vite-plugin/index.js +2106 -0
  41. package/dist/vue/index.d.ts +729 -0
  42. package/dist/vue/index.js +4945 -0
  43. package/dist/web/index.d.ts +832 -0
  44. package/dist/web/index.js +4868 -0
  45. package/package.json +258 -0
@@ -0,0 +1,2106 @@
1
+ import ts from "typescript";
2
+ import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
3
+ import { writeFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ //#region ../../lib/foundation/src/iterate.ts
6
+ function find(iterable, callback) {
7
+ for (const value of iterable) {
8
+ const result = callback(value);
9
+ if (result != null) return result;
10
+ }
11
+ return null;
12
+ }
13
+ function some(iterable, predicate) {
14
+ for (const value of iterable) if (predicate(value)) return true;
15
+ return false;
16
+ }
17
+ function every(iterable, predicate) {
18
+ let index = 0;
19
+ for (const value of iterable) if (!predicate(value, index++)) return false;
20
+ return true;
21
+ }
22
+ function* filter(iterable, predicate) {
23
+ let index = 0;
24
+ for (const value of iterable) if (predicate(value, index++)) yield value;
25
+ }
26
+ function* map(iterable, callback) {
27
+ let index = 0;
28
+ for (const value of iterable) yield callback(value, index++);
29
+ }
30
+ function forEach(iterable, callback) {
31
+ let index = 0;
32
+ for (const value of iterable) callback(value, index++);
33
+ }
34
+ function reduce(iterable, initial, callback) {
35
+ let accumulator = initial;
36
+ let index = 0;
37
+ for (const value of iterable) accumulator = callback(accumulator, value, index++);
38
+ return accumulator;
39
+ }
40
+ /**
41
+ * Transforms an iterable into a Record.
42
+ *
43
+ * The callback returns a `[key, value]` tuple for each element. Returning
44
+ * `null` aborts the collection and causes `collect()` to return `null`.
45
+ */
46
+ function collect(iterable, callback) {
47
+ const result = {};
48
+ let index = 0;
49
+ for (const value of iterable) {
50
+ const entry = callback(value, index++);
51
+ if (entry === null) return null;
52
+ result[entry[0]] = entry[1];
53
+ }
54
+ return result;
55
+ }
56
+ function findLast(value, callback) {
57
+ for (let index = value.length - 1; index >= 0; index--) {
58
+ const result = callback(value[index], index);
59
+ if (result != null) return result;
60
+ }
61
+ return null;
62
+ }
63
+ function* items(collection) {
64
+ for (let i = 0; i < collection.length; i++) {
65
+ const item = collection.item(i);
66
+ if (item !== null) yield item;
67
+ }
68
+ }
69
+ function nodeList(list) {
70
+ return { *[Symbol.iterator]() {
71
+ for (let i = 0; i < list.length; i++) {
72
+ const node = list.item(i);
73
+ if (node !== null) yield node;
74
+ }
75
+ } };
76
+ }
77
+ function mapEntries(m) {
78
+ return m.entries();
79
+ }
80
+ function set(s) {
81
+ return s.values();
82
+ }
83
+ function hasOwn(object, key) {
84
+ return Object.hasOwn(object, key);
85
+ }
86
+ function* entries(object) {
87
+ for (const key in object) {
88
+ if (!hasOwn(object, key)) continue;
89
+ yield [key, object[key]];
90
+ }
91
+ }
92
+ function* keys(object) {
93
+ for (const [key] of entries(object)) yield key;
94
+ }
95
+ function* values(object) {
96
+ for (const [, value] of entries(object)) yield value;
97
+ }
98
+ function mapValues(object, callback) {
99
+ const result = {};
100
+ for (const [key, value] of entries(object)) result[key] = callback(value, key);
101
+ return result;
102
+ }
103
+ function forEachEntry(object, callback) {
104
+ for (const [key, value] of entries(object)) callback(key, value);
105
+ }
106
+ function forEachKey(object, callback) {
107
+ for (const key of keys(object)) callback(key);
108
+ }
109
+ function forEachValue(object, callback) {
110
+ for (const value of values(object)) callback(value);
111
+ }
112
+ function forEachSet(s, callback) {
113
+ for (const value of s) callback(value);
114
+ }
115
+ const iterate = Object.freeze({
116
+ entries,
117
+ filter,
118
+ find,
119
+ findLast,
120
+ forEach,
121
+ forEachEntry,
122
+ forEachKey,
123
+ forEachSet,
124
+ forEachValue,
125
+ items,
126
+ keys,
127
+ map,
128
+ mapEntries,
129
+ mapValues,
130
+ nodeList,
131
+ reduce,
132
+ collect,
133
+ set,
134
+ some,
135
+ every,
136
+ values
137
+ });
138
+ //#endregion
139
+ //#region ../../lib/primitive/src/utils/lazy.ts
140
+ /**
141
+ * Lazily initializes a value. The factory is invoked exactly once on the first
142
+ * call, and every subsequent call returns the cached result, even if the
143
+ * result is `undefined`, `null`, `false`, `0`, or `''`.
144
+ */
145
+ function lazy(factory) {
146
+ let initialized = false;
147
+ let value;
148
+ return () => {
149
+ if (!initialized) {
150
+ value = factory();
151
+ initialized = true;
152
+ }
153
+ return value;
154
+ };
155
+ }
156
+ //#endregion
157
+ //#region ../../plugins/vite/src/ast.ts
158
+ /**
159
+ * Returns the initializer of a named property from an object literal.
160
+ *
161
+ * Only direct `PropertyAssignment` nodes are considered. Shorthand
162
+ * properties, spread assignments, getters, setters, and methods are ignored.
163
+ *
164
+ * @param obj Object literal to search.
165
+ * @param key Property name to locate.
166
+ * @returns The property's initializer expression, or `undefined` if the
167
+ * property is not present or is not a standard property assignment.
168
+ */
169
+ function getProperty(obj, key) {
170
+ return iterate.find(obj.properties, (prop) => {
171
+ if (!ts.isPropertyAssignment(prop)) return null;
172
+ const { name } = prop;
173
+ if ((ts.isIdentifier(name) || ts.isStringLiteral(name)) && name.text === key) return prop.initializer;
174
+ return null;
175
+ }) ?? void 0;
176
+ }
177
+ /**
178
+ * Narrows a node to an ObjectLiteralExpression.
179
+ *
180
+ * This is a convenience type guard for optional AST nodes.
181
+ *
182
+ * @param node Node to inspect.
183
+ * @returns The same node when it is an object literal; otherwise `undefined`.
184
+ */
185
+ function asObject(node) {
186
+ if (!node) return void 0;
187
+ return ts.isObjectLiteralExpression(node) ? node : void 0;
188
+ }
189
+ /**
190
+ * Narrows a node to an ArrayLiteralExpression.
191
+ *
192
+ * @param node Node to inspect.
193
+ * @returns The same node when it is an array literal; otherwise `undefined`.
194
+ */
195
+ function asArray(node) {
196
+ if (!node) return void 0;
197
+ return ts.isArrayLiteralExpression(node) ? node : void 0;
198
+ }
199
+ /**
200
+ * Extracts a non-negative integer from a numeric literal.
201
+ *
202
+ * Accepts only literal numeric values present in the AST.
203
+ * Expressions such as `1 + 2`, identifiers, and function calls
204
+ * are intentionally ignored.
205
+ *
206
+ * @remarks
207
+ * This helper does not evaluate constant expressions. It only
208
+ * reads literal values already present in the syntax tree.
209
+ *
210
+ * @param node Node to inspect.
211
+ * @returns A non-negative integer, or `undefined` when the node
212
+ * is not a numeric literal or represents a negative value.
213
+ */
214
+ function asNonNegativeInt(node) {
215
+ if (!node) return void 0;
216
+ if (ts.isNumericLiteral(node)) {
217
+ const n = Number(node.text);
218
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
219
+ }
220
+ }
221
+ /**
222
+ * Extracts a boolean literal from an AST node.
223
+ *
224
+ * Only the `true` and `false` keyword nodes are recognized.
225
+ *
226
+ * @param node Node to inspect.
227
+ * @returns `true`, `false`, or `undefined`.
228
+ */
229
+ function asBooleanLiteral(node) {
230
+ if (!node) return void 0;
231
+ switch (node.kind) {
232
+ case ts.SyntaxKind.TrueKeyword: return true;
233
+ case ts.SyntaxKind.FalseKeyword: return false;
234
+ default: return;
235
+ }
236
+ }
237
+ /**
238
+ * Determines whether a call expression invokes one of the known
239
+ * Praxis Kit factory functions.
240
+ *
241
+ * Supports both direct identifiers and property access expressions.
242
+ *
243
+ * Examples:
244
+ *
245
+ * ```ts
246
+ * defineComponent(...)
247
+ * factory.defineComponent(...)
248
+ * ```
249
+ *
250
+ * @param call Call expression to inspect.
251
+ * @param names Set of accepted factory names.
252
+ * @returns `true` when the call targets one of the supplied names.
253
+ */
254
+ function isFactoryCall(call, names) {
255
+ const { expression } = call;
256
+ if (ts.isIdentifier(expression)) return names.has(expression.text);
257
+ if (ts.isPropertyAccessExpression(expression)) return names.has(expression.name.text);
258
+ return false;
259
+ }
260
+ /**
261
+ * Returns the first argument when it is an object literal.
262
+ *
263
+ * Useful for parsing APIs that accept an options object as their
264
+ * first parameter.
265
+ *
266
+ * @param call Call expression to inspect.
267
+ * @returns The first argument if it is an object literal;
268
+ * otherwise `undefined`.
269
+ */
270
+ function firstObjectArg(call) {
271
+ const first = call.arguments[0];
272
+ return first && ts.isObjectLiteralExpression(first) ? first : void 0;
273
+ }
274
+ /**
275
+ * Performs a depth-first traversal of an AST subtree.
276
+ *
277
+ * The starting node is yielded first, followed by every
278
+ * descendant in lexical order.
279
+ *
280
+ * @remarks
281
+ * This is implemented as a generator, allowing consumers to
282
+ * iterate lazily using `for...of`.
283
+ *
284
+ * @param node Root node.
285
+ * @returns An iterator over every node in the subtree.
286
+ */
287
+ function* walk(node) {
288
+ yield node;
289
+ const children = [];
290
+ ts.forEachChild(node, (child) => {
291
+ children.push(child);
292
+ });
293
+ for (const child of children) yield* walk(child);
294
+ }
295
+ /**
296
+ * Traverses an AST subtree depth-first, invoking a visitor for
297
+ * every node encountered.
298
+ *
299
+ * This is a callback-oriented alternative to {@link walk}.
300
+ *
301
+ * @param node Root node.
302
+ * @param visitor Function invoked for every visited node.
303
+ */
304
+ function walkEach(node, visitor) {
305
+ for (const child of walk(node)) visitor(child);
306
+ }
307
+ const SCRIPT_KIND_BY_EXT = {
308
+ tsx: ts.ScriptKind.TSX,
309
+ jsx: ts.ScriptKind.JSX,
310
+ ts: ts.ScriptKind.TS,
311
+ mts: ts.ScriptKind.TS,
312
+ cts: ts.ScriptKind.TS,
313
+ js: ts.ScriptKind.JS,
314
+ mjs: ts.ScriptKind.JS,
315
+ cjs: ts.ScriptKind.JS
316
+ };
317
+ /**
318
+ * Parses source text into a TypeScript SourceFile.
319
+ *
320
+ * The `ScriptKind` is derived from the filename extension so each source is
321
+ * parsed with its real syntax semantics — a `.ts` file's `<T>expr` is a type
322
+ * assertion, not a broken JSX element. Unknown extensions fall back to TSX (the
323
+ * most permissive). Latest language version, parent pointers enabled.
324
+ *
325
+ * @param filename Virtual filename used for diagnostics and to pick the ScriptKind.
326
+ * @param code Source code to parse.
327
+ * @returns Parsed SourceFile.
328
+ */
329
+ function parseSource(filename, code) {
330
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
331
+ const scriptKind = SCRIPT_KIND_BY_EXT[ext] ?? ts.ScriptKind.TSX;
332
+ return ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true, scriptKind);
333
+ }
334
+ //#endregion
335
+ //#region ../../plugins/vite/src/class-extract.ts
336
+ /**
337
+ * Compile-time variant class precomputation.
338
+ *
339
+ * Extracts `styling.variants`, `styling.defaults`, and `styling.compounds` from
340
+ * factory call ASTs, enumerates all statically-known prop combinations, computes
341
+ * the variant class string for each, and injects the resulting map as
342
+ * `styling.precomputedClasses` directly into the source.
343
+ *
344
+ * At runtime, `VariantClassResolver` checks `precomputedClasses` before
345
+ * calling CVA — a plain object lookup replaces a CVA invocation + LRU cache
346
+ * write for every covered combination.
347
+ *
348
+ * **Skipped when any of the following are true:**
349
+ * - `styling.variants` is absent or contains non-literal values
350
+ * - `styling.compounds` contains non-literal conditions or class values
351
+ * - The total number of combinations exceeds MAX_COMBINATIONS
352
+ * - The styling object already has a `precomputedClasses` property
353
+ *
354
+ * @example
355
+ * Before:
356
+ * ```ts
357
+ * styling: {
358
+ * variants: { size: { sm: 'text-sm', lg: 'text-lg' } },
359
+ * }
360
+ * ```
361
+ * After:
362
+ * ```ts
363
+ * styling: {
364
+ * variants: { size: { sm: 'text-sm', lg: 'text-lg' } },
365
+ * precomputedClasses: {
366
+ * '__none__:': '',
367
+ * '__none__:size:s:sm': 'text-sm',
368
+ * '__none__:size:s:lg': 'text-lg',
369
+ * },
370
+ * }
371
+ * ```
372
+ */
373
+ const MAX_COMBINATIONS = 512;
374
+ /** Returns the text of a string literal node, or undefined. */
375
+ function asString(node) {
376
+ return node && ts.isStringLiteral(node) ? node.text : void 0;
377
+ }
378
+ /**
379
+ * Returns the text of a string literal, the texts of all elements of an array literal
380
+ * (every element must be a string literal), or undefined for any other shape.
381
+ */
382
+ function asStringOrStringArray(node) {
383
+ if (!node) return void 0;
384
+ if (ts.isStringLiteral(node)) return node.text;
385
+ if (ts.isArrayLiteralExpression(node)) {
386
+ const items = [];
387
+ if (!iterate.every(node.elements, (elem) => {
388
+ if (!ts.isStringLiteral(elem)) return false;
389
+ items.push(elem.text);
390
+ return true;
391
+ })) return void 0;
392
+ return items;
393
+ }
394
+ }
395
+ /** Returns the name text of a property assignment with an identifier or string literal key, or undefined. */
396
+ function propKey(prop) {
397
+ if (!ts.isPropertyAssignment(prop)) return void 0;
398
+ const n = prop.name;
399
+ return ts.isIdentifier(n) || ts.isStringLiteral(n) ? n.text : void 0;
400
+ }
401
+ /**
402
+ * Extracts `styling.variants` into a nested `VariantMap`.
403
+ * Returns null when any variant key or value entry is non-literal (bail — can't enumerate).
404
+ */
405
+ function extractVariantMap$1(stylingObj) {
406
+ const variantsObj = asObject(getProperty(stylingObj, "variants"));
407
+ if (!variantsObj) return null;
408
+ return iterate.collect(variantsObj.properties, (prop) => {
409
+ const key = propKey(prop);
410
+ if (!key || !ts.isPropertyAssignment(prop)) return null;
411
+ const valuesObj = asObject(prop.initializer);
412
+ if (!valuesObj) return null;
413
+ const values = iterate.collect(valuesObj.properties, (vp) => {
414
+ const vk = propKey(vp);
415
+ if (!vk || !ts.isPropertyAssignment(vp)) return null;
416
+ const value = asStringOrStringArray(vp.initializer);
417
+ if (value === void 0) return null;
418
+ return [vk, value];
419
+ });
420
+ if (!values) return null;
421
+ return [key, values];
422
+ });
423
+ }
424
+ /** Extracts `styling.defaults` as a `Record<variantKey, defaultValue>`. Returns `{}` when absent or non-literal. */
425
+ function extractDefaults(stylingObj) {
426
+ const defaultsObj = asObject(getProperty(stylingObj, "defaults"));
427
+ if (!defaultsObj) return {};
428
+ return iterate.collect(defaultsObj.properties, (prop) => {
429
+ const key = propKey(prop);
430
+ if (!key || !ts.isPropertyAssignment(prop)) return null;
431
+ const value = asString(prop.initializer);
432
+ if (value === void 0) return null;
433
+ return [key, value];
434
+ }) ?? {};
435
+ }
436
+ /**
437
+ * Extracts `styling.compounds` as a typed array.
438
+ * Returns null when any entry has a non-literal condition value or class — signals bail to the caller.
439
+ */
440
+ function extractCompounds(stylingObj) {
441
+ const compoundsArr = asArray(getProperty(stylingObj, "compounds"));
442
+ if (!compoundsArr) return [];
443
+ const result = [];
444
+ return iterate.every(compoundsArr.elements, (elem) => {
445
+ const obj = asObject(elem);
446
+ if (!obj) return false;
447
+ const cls = asStringOrStringArray(getProperty(obj, "class"));
448
+ if (cls === void 0) return false;
449
+ const conditions = {};
450
+ if (!iterate.every(obj.properties, (cp) => {
451
+ const key = propKey(cp);
452
+ if (!key || !ts.isPropertyAssignment(cp)) return false;
453
+ if (key === "class") return true;
454
+ const v = asStringOrStringArray(cp.initializer);
455
+ if (v === void 0) return false;
456
+ conditions[key] = v;
457
+ return true;
458
+ })) return false;
459
+ result.push({
460
+ conditions,
461
+ cls
462
+ });
463
+ return true;
464
+ }) ? result : null;
465
+ }
466
+ /**
467
+ * Enumerates every subset of variant props (each dimension either absent or set
468
+ * to one of its declared values). Returns null if total would exceed MAX_COMBINATIONS.
469
+ */
470
+ function enumerateCombinations(variantMap) {
471
+ const keys = Object.keys(variantMap);
472
+ if (keys.length === 0) return [{}];
473
+ let total = 1;
474
+ if (!iterate.every(keys, (key) => {
475
+ total *= Object.keys(variantMap[key]).length + 1;
476
+ return total <= MAX_COMBINATIONS;
477
+ })) return null;
478
+ function enumerateRecursive(remaining) {
479
+ if (remaining.length === 0) return [{}];
480
+ const first = remaining[0];
481
+ const restCombos = enumerateRecursive(remaining.slice(1));
482
+ const valueKeys = Object.keys(variantMap[first]);
483
+ const out = [];
484
+ iterate.forEach(restCombos, (combo) => {
485
+ out.push(combo);
486
+ iterate.forEach(valueKeys, (v) => {
487
+ out.push({
488
+ [first]: v,
489
+ ...combo
490
+ });
491
+ });
492
+ });
493
+ return out;
494
+ }
495
+ return enumerateRecursive(keys);
496
+ }
497
+ /**
498
+ * Builds the VariantClassResolver cache key for a given explicit prop combination.
499
+ * Absent variant dimensions are excluded from the key (defaults apply in compute).
500
+ */
501
+ function buildCacheKey(props) {
502
+ return `__none__:${Object.keys(props).sort().map((k) => `${k}:s:${props[k]}`).join("|")}`;
503
+ }
504
+ /** Computes the final class string for a given explicit prop combination against a resolved styling config. */
505
+ function computeClasses(config, props) {
506
+ const { variantMap, defaults, compounds } = config;
507
+ const effective = {
508
+ ...defaults,
509
+ ...props
510
+ };
511
+ const classes = [];
512
+ iterate.forEachEntry(variantMap, (key, values) => {
513
+ const v = effective[key];
514
+ if (v === void 0) return;
515
+ const cls = values[v];
516
+ if (cls === void 0) return;
517
+ if (Array.isArray(cls)) classes.push(...cls);
518
+ else classes.push(cls);
519
+ });
520
+ iterate.forEach(compounds, ({ conditions, cls }) => {
521
+ if (iterate.every(iterate.entries(conditions), ([key, cond]) => {
522
+ const value = effective[key];
523
+ return Array.isArray(cond) ? value !== void 0 && cond.includes(value) : value === cond;
524
+ })) {
525
+ if (Array.isArray(cls)) classes.push(...cls);
526
+ else classes.push(cls);
527
+ }
528
+ });
529
+ return classes.filter(Boolean).join(" ");
530
+ }
531
+ /**
532
+ * Builds a precomputed class map for the given styling object literal.
533
+ *
534
+ * Returns null when static extraction is not possible (non-literal values,
535
+ * no variants, or combination count exceeds MAX_COMBINATIONS).
536
+ */
537
+ function buildPrecomputedClasses(stylingObj) {
538
+ if (getProperty(stylingObj, "precomputedClasses") !== void 0) return null;
539
+ const variantMap = extractVariantMap$1(stylingObj);
540
+ if (!variantMap || Object.keys(variantMap).length === 0) return null;
541
+ const defaults = extractDefaults(stylingObj);
542
+ const compounds = extractCompounds(stylingObj);
543
+ if (!compounds) return null;
544
+ const combos = enumerateCombinations(variantMap);
545
+ if (!combos) return null;
546
+ const config = {
547
+ variantMap,
548
+ defaults,
549
+ compounds
550
+ };
551
+ return iterate.collect(combos, (combo) => [buildCacheKey(combo), computeClasses(config, combo)]);
552
+ }
553
+ /** Returns a TS transformer that injects a `precomputedClasses` property into each eligible factory call's `styling` object. */
554
+ function createClassExtractTransformer(factory, calleeNames, onInjected) {
555
+ return (context) => {
556
+ function visit(node) {
557
+ if (!ts.isCallExpression(node)) return ts.visitEachChild(node, visit, context);
558
+ if (!isFactoryCall(node, calleeNames)) return ts.visitEachChild(node, visit, context);
559
+ const arg = firstObjectArg(node);
560
+ if (!arg) return node;
561
+ const stylingObj = asObject(getProperty(arg, "styling"));
562
+ if (!stylingObj) return ts.visitEachChild(node, visit, context);
563
+ const map = buildPrecomputedClasses(stylingObj);
564
+ if (!map) return ts.visitEachChild(node, visit, context);
565
+ onInjected();
566
+ const mapProps = Array.from(iterate.map(iterate.entries(map), ([key, value]) => factory.createPropertyAssignment(factory.createStringLiteral(key), factory.createStringLiteral(value))));
567
+ const mapLiteral = factory.createObjectLiteralExpression(mapProps, true);
568
+ const precomputedProp = factory.createPropertyAssignment(factory.createIdentifier("precomputedClasses"), mapLiteral);
569
+ const newStylingObj = factory.createObjectLiteralExpression([...stylingObj.properties, precomputedProp], true);
570
+ const newArgProps = arg.properties.map((p) => {
571
+ if (ts.isPropertyAssignment(p) && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) && p.name.text === "styling") return factory.createPropertyAssignment(p.name, newStylingObj);
572
+ return p;
573
+ });
574
+ const newArg = factory.createObjectLiteralExpression(newArgProps, true);
575
+ return factory.createCallExpression(node.expression, node.typeArguments, [newArg, ...node.arguments.slice(1)]);
576
+ }
577
+ return (sourceFile) => ts.visitEachChild(sourceFile, visit, context);
578
+ };
579
+ }
580
+ /**
581
+ * Injects precomputed variant class maps into all factory calls in the given
582
+ * source file that have fully-static `styling.variants` configurations.
583
+ *
584
+ * Returns null when no factory calls with injectable variants are found.
585
+ */
586
+ function injectPrecomputedClasses(source, calleeNames) {
587
+ if (!iterate.some(walk(source), (node) => ts.isPropertyAssignment(node) && (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) && node.name.text === "variants")) return null;
588
+ let didInject = false;
589
+ const result = ts.transform(source, [createClassExtractTransformer(ts.factory, calleeNames, () => {
590
+ didInject = true;
591
+ })], { target: ts.ScriptTarget.Latest });
592
+ if (!didInject) {
593
+ result.dispose();
594
+ return null;
595
+ }
596
+ const output = ts.createPrinter({
597
+ newLine: ts.NewLineKind.LineFeed,
598
+ removeComments: false
599
+ }).printFile(result.transformed[0]);
600
+ result.dispose();
601
+ return output;
602
+ }
603
+ //#endregion
604
+ //#region ../../plugins/vite/src/collect.ts
605
+ /**
606
+ * Extracts a StaticBound from one element of the enforcement.children array,
607
+ * if and only if the element is an object literal with a statically-readable
608
+ * cardinality. Returns undefined for elements that use dynamic values.
609
+ */
610
+ function extractBound(element) {
611
+ const obj = asObject(element);
612
+ if (!obj) return void 0;
613
+ const cardObj = asObject(getProperty(obj, "cardinality"));
614
+ let cardinality;
615
+ if (cardObj) {
616
+ const minNode = getProperty(cardObj, "min");
617
+ const maxNode = getProperty(cardObj, "max");
618
+ const min = asNonNegativeInt(minNode) ?? 0;
619
+ const max = asNonNegativeInt(maxNode);
620
+ if (min === 0 && max === void 0) cardinality = { kind: "unbounded" };
621
+ else cardinality = {
622
+ kind: "bounded",
623
+ min,
624
+ max: max ?? Infinity
625
+ };
626
+ } else cardinality = { kind: "unbounded" };
627
+ const positionNode = getProperty(obj, "position");
628
+ let position = "any";
629
+ if (positionNode && ts.isStringLiteral(positionNode)) {
630
+ const p = positionNode.text;
631
+ if (p === "first" || p === "last" || p === "any") position = p;
632
+ }
633
+ return {
634
+ cardinality,
635
+ position
636
+ };
637
+ }
638
+ /** Returns the lower bound of a Cardinality as an integer (unbounded → 0). */
639
+ function cardinalityMin(c) {
640
+ return c.kind === "bounded" ? c.min : 0;
641
+ }
642
+ /** Returns the upper bound of a Cardinality (unbounded → Infinity). */
643
+ function cardinalityMax(c) {
644
+ return c.kind === "bounded" ? c.max : Infinity;
645
+ }
646
+ /**
647
+ * Walks a SourceFile and returns one ComponentConstraint for each
648
+ * `const X = createPolymorphicComponent({ enforcement: { children: [...] } })`
649
+ * where at least one children rule has a statically-extractable cardinality.
650
+ *
651
+ * Only handles simple `const X = factory(...)` declarations — named exports,
652
+ * default exports, and destructured patterns are not collected. Cross-file
653
+ * constraint collection (factory defined in module A, consumed in module B)
654
+ * is handled separately via the registry + import resolver path in the plugin.
655
+ * Dynamic cardinality values (computed variables, spreads) are silently skipped;
656
+ * extending to data-flow patterns is deferred.
657
+ */
658
+ function processVariableStatement(node, calleeNames, out) {
659
+ iterate.forEach(node.declarationList.declarations, (decl) => {
660
+ const { initializer, name } = decl;
661
+ if (!initializer || !ts.isCallExpression(initializer)) return;
662
+ if (!isFactoryCall(initializer, calleeNames)) return;
663
+ const arg = firstObjectArg(initializer);
664
+ if (!arg) return;
665
+ const tagNode = getProperty(arg, "tag");
666
+ const defaultTag = tagNode && ts.isStringLiteral(tagNode) ? tagNode.text : void 0;
667
+ const enfObj = asObject(getProperty(arg, "enforcement"));
668
+ const ariaArr = asArray(enfObj ? getProperty(enfObj, "aria") : void 0);
669
+ const hasAriaRules = ariaArr !== void 0 && ariaArr.elements.length > 0;
670
+ const exclusiveChildren = asBooleanLiteral(enfObj ? getProperty(enfObj, "exclusiveChildren") : void 0) ?? false;
671
+ const childrenArr = asArray(enfObj ? getProperty(enfObj, "children") : void 0);
672
+ const rules = [];
673
+ if (childrenArr) iterate.forEach(childrenArr.elements, (element) => {
674
+ const bound = extractBound(element);
675
+ if (bound) rules.push(bound);
676
+ });
677
+ if (rules.length === 0 && !hasAriaRules && !defaultTag && !exclusiveChildren) return;
678
+ let totalMin = 0;
679
+ let totalMax = 0;
680
+ iterate.forEach(rules, (rule) => {
681
+ totalMin += cardinalityMin(rule.cardinality);
682
+ const max = cardinalityMax(rule.cardinality);
683
+ totalMax = totalMax === Infinity || max === Infinity ? Infinity : totalMax + max;
684
+ });
685
+ const componentName = ts.isIdentifier(name) ? name.text : void 0;
686
+ if (!componentName) return;
687
+ out.push({
688
+ name: componentName,
689
+ rules,
690
+ totalMin,
691
+ totalMax,
692
+ ...defaultTag !== void 0 && { defaultTag },
693
+ hasAriaRules,
694
+ exclusiveChildren
695
+ });
696
+ });
697
+ }
698
+ /**
699
+ * Single-pass variant of collectConstraints + extractImportSpecifiers.
700
+ * Collects factory constraints and named import bindings in one AST walk
701
+ * instead of two, for use in the hot Vite transform hook.
702
+ */
703
+ function collectFileDeclarations(source, calleeNames) {
704
+ const constraints = [];
705
+ const importSpecifiers = /* @__PURE__ */ new Map();
706
+ walkEach(source, (node) => {
707
+ if (ts.isVariableStatement(node)) processVariableStatement(node, calleeNames, constraints);
708
+ else if (ts.isImportDeclaration(node)) {
709
+ const spec = node.moduleSpecifier;
710
+ if (!ts.isStringLiteral(spec)) return;
711
+ const namedBindings = node.importClause?.namedBindings;
712
+ if (!namedBindings || !ts.isNamedImports(namedBindings)) return;
713
+ const specifier = spec.text;
714
+ iterate.forEach(namedBindings.elements, ({ isTypeOnly, name, propertyName }) => {
715
+ if (isTypeOnly) return;
716
+ const localName = name.text;
717
+ const importedName = propertyName?.text ?? localName;
718
+ importSpecifiers.set(localName, {
719
+ importedName,
720
+ specifier
721
+ });
722
+ });
723
+ }
724
+ });
725
+ return {
726
+ constraints,
727
+ importSpecifiers
728
+ };
729
+ }
730
+ //#endregion
731
+ //#region ../../plugins/vite/src/compound-prune.ts
732
+ /**
733
+ * Compile-time dead compound variant pruning.
734
+ *
735
+ * Removes entries from `styling.compounds` whose conditions can never fire:
736
+ * - condition key is not in `styling.variants`
737
+ * - condition value (string) is not a valid value for that variant key
738
+ * - condition value (array) has no element that is a valid value
739
+ *
740
+ * Only entries whose conditions are all statically evaluable (string/array
741
+ * literals throughout) are candidates for pruning — dynamic conditions (variable
742
+ * references, computed values) are left unchanged.
743
+ *
744
+ * Returns null if no factory calls with `styling.compounds` are found, or if
745
+ * every compound in every factory call passes the validity check.
746
+ */
747
+ /** Builds a variant map from a `styling.variants` object literal. */
748
+ function extractVariantMap(stylingObj) {
749
+ const result = /* @__PURE__ */ new Map();
750
+ const variantsObj = asObject(getProperty(stylingObj, "variants"));
751
+ if (!variantsObj) return result;
752
+ iterate.forEach(variantsObj.properties, (prop) => {
753
+ if (!ts.isPropertyAssignment(prop)) return;
754
+ const { name } = prop;
755
+ const key = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : void 0;
756
+ if (!key) return;
757
+ const valuesObj = asObject(prop.initializer);
758
+ if (!valuesObj) return;
759
+ const valid = /* @__PURE__ */ new Set();
760
+ iterate.forEach(valuesObj.properties, (vp) => {
761
+ if (!ts.isPropertyAssignment(vp)) return;
762
+ const { name } = vp;
763
+ const vk = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : void 0;
764
+ if (vk) valid.add(vk);
765
+ });
766
+ result.set(key, valid);
767
+ });
768
+ return result;
769
+ }
770
+ /**
771
+ * Returns true if the compound object literal entry is dead given `variantMap`.
772
+ *
773
+ * Conservative: returns false (not dead) whenever a condition cannot be fully
774
+ * evaluated as string/array literals.
775
+ */
776
+ function isDeadCompound(entry, variantMap) {
777
+ return iterate.find(entry.properties, (prop) => {
778
+ if (!ts.isPropertyAssignment(prop)) return null;
779
+ const { name } = prop;
780
+ const key = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : void 0;
781
+ if (!key || key === "class") return null;
782
+ const validValues = variantMap.get(key);
783
+ if (!validValues) return true;
784
+ const { initializer: val } = prop;
785
+ if (ts.isStringLiteral(val)) {
786
+ if (!validValues.has(val.text)) return true;
787
+ } else if (ts.isArrayLiteralExpression(val)) {
788
+ if (!val.elements.every(ts.isStringLiteral)) return null;
789
+ if (!val.elements.some((e) => ts.isStringLiteral(e) && validValues.has(e.text))) return true;
790
+ }
791
+ return null;
792
+ }) ?? false;
793
+ }
794
+ /** Returns a TS transformer that removes dead compound entries from every eligible factory call in a source file. */
795
+ function createCompoundPruner(factory, calleeNames, onPruned) {
796
+ return (context) => {
797
+ function visit(node) {
798
+ if (!ts.isCallExpression(node)) return ts.visitEachChild(node, visit, context);
799
+ if (!isFactoryCall(node, calleeNames)) return ts.visitEachChild(node, visit, context);
800
+ const arg = firstObjectArg(node);
801
+ if (!arg) return node;
802
+ const stylingObj = asObject(getProperty(arg, "styling"));
803
+ if (!stylingObj) return ts.visitEachChild(node, visit, context);
804
+ const compoundsArr = asArray(getProperty(stylingObj, "compounds"));
805
+ if (!compoundsArr || compoundsArr.elements.length === 0) return ts.visitEachChild(node, visit, context);
806
+ const variantMap = extractVariantMap(stylingObj);
807
+ if (variantMap.size === 0) return ts.visitEachChild(node, visit, context);
808
+ const liveEntries = compoundsArr.elements.filter((elem) => {
809
+ const obj = asObject(elem);
810
+ return !obj || !isDeadCompound(obj, variantMap);
811
+ });
812
+ if (liveEntries.length === compoundsArr.elements.length) return ts.visitEachChild(node, visit, context);
813
+ onPruned();
814
+ const newCompoundsArr = factory.createArrayLiteralExpression(liveEntries, true);
815
+ const newStylingProps = stylingObj.properties.map((p) => {
816
+ if (ts.isPropertyAssignment(p) && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) && p.name.text === "compounds") return factory.createPropertyAssignment(p.name, newCompoundsArr);
817
+ return p;
818
+ });
819
+ const newStylingObj = factory.createObjectLiteralExpression(newStylingProps, true);
820
+ const newArgProps = arg.properties.map((p) => {
821
+ if (ts.isPropertyAssignment(p) && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) && p.name.text === "styling") return factory.createPropertyAssignment(p.name, newStylingObj);
822
+ return p;
823
+ });
824
+ const newArg = factory.createObjectLiteralExpression(newArgProps, true);
825
+ return factory.createCallExpression(node.expression, node.typeArguments, [newArg, ...node.arguments.slice(1)]);
826
+ }
827
+ return (sourceFile) => ts.visitEachChild(sourceFile, visit, context);
828
+ };
829
+ }
830
+ /**
831
+ * Applies dead-compound pruning to the given TypeScript source file.
832
+ *
833
+ * Returns null when the file has no factory calls with `styling.compounds`, or
834
+ * when every compound entry passes validity — i.e., when no pruning is needed.
835
+ */
836
+ function pruneDeadCompounds(source, calleeNames) {
837
+ let hasCompounds = false;
838
+ walkEach(source, (n) => {
839
+ if (hasCompounds) return;
840
+ if (ts.isPropertyAssignment(n)) {
841
+ if ((ts.isIdentifier(n.name) || ts.isStringLiteral(n.name) ? n.name.text : void 0) === "compounds") hasCompounds = true;
842
+ }
843
+ });
844
+ if (!hasCompounds) return null;
845
+ let didPrune = false;
846
+ const result = ts.transform(source, [createCompoundPruner(ts.factory, calleeNames, () => {
847
+ didPrune = true;
848
+ })], { target: ts.ScriptTarget.Latest });
849
+ if (!didPrune) {
850
+ result.dispose();
851
+ return null;
852
+ }
853
+ const output = ts.createPrinter({
854
+ newLine: ts.NewLineKind.LineFeed,
855
+ removeComments: false
856
+ }).printFile(result.transformed[0]);
857
+ result.dispose();
858
+ return output;
859
+ }
860
+ //#endregion
861
+ //#region ../../plugins/vite/src/constants.ts
862
+ const DEFAULT_CALLEE_NAMES = ["createContractComponent", "createPolymorphicComponent"];
863
+ const JSX_EXTS = /* @__PURE__ */ new Set(["tsx", "jsx"]);
864
+ const ALL_EXTS = /* @__PURE__ */ new Set([
865
+ "ts",
866
+ "tsx",
867
+ "js",
868
+ "jsx"
869
+ ]);
870
+ //#endregion
871
+ //#region ../../plugins/vite/src/vite-diagnostics.ts
872
+ const ViteDiagnostics = {
873
+ cardinalityViolation(name, totalMin, totalMax, receivedMin, receivedMax) {
874
+ const rangeText = totalMax === Infinity ? `at least ${totalMin}` : totalMin === totalMax ? `exactly ${totalMin}` : `${totalMin}–${totalMax}`;
875
+ const childWord = totalMax === 1 && totalMin === 1 ? "child" : "children";
876
+ const receivedText = receivedMin === receivedMax ? `${receivedMin}` : `${receivedMin}–${receivedMax}`;
877
+ return {
878
+ code: DiagnosticCode.LintCardinalityViolation,
879
+ category: DiagnosticCategory.Lint,
880
+ message: `<${name}> expects ${rangeText} ${childWord} but received ${receivedText}.`
881
+ };
882
+ },
883
+ ariaTagOverride(tagName, asValue, defaultTag) {
884
+ return {
885
+ code: DiagnosticCode.LintAriaTagOverride,
886
+ category: DiagnosticCategory.Lint,
887
+ message: `<${tagName} as="${asValue}"> changes the element type from '${defaultTag}' — ARIA enforcement rules may not apply as expected.`
888
+ };
889
+ }
890
+ };
891
+ //#endregion
892
+ //#region ../../plugins/vite/src/diagnose.ts
893
+ /**
894
+ * Single-pass variant of diagnoseUsages + diagnoseAriaTagOverrides + collectJsxUsages.
895
+ * Visits each JSX node once and dispatches to all three checks, rather than walking
896
+ * the source three times. For use in the hot Vite transform hook.
897
+ */
898
+ function analyzeJsxSites(source, constraints, severity) {
899
+ const byName = new Map(constraints.filter((c) => c.rules.length > 0).map((c) => [c.name, c]));
900
+ const byNameAria = new Map(constraints.filter((c) => c.hasAriaRules && c.defaultTag !== void 0).map((c) => [c.name, c]));
901
+ const diagnostics = [];
902
+ const usages = [];
903
+ walkEach(source, (node) => {
904
+ let tagName;
905
+ let attributes;
906
+ let count;
907
+ if (ts.isJsxElement(node)) {
908
+ const opening = node.openingElement;
909
+ tagName = ts.isIdentifier(opening.tagName) ? opening.tagName.text : void 0;
910
+ attributes = opening.attributes;
911
+ count = countJsxChildren(node.children);
912
+ } else if (ts.isJsxSelfClosingElement(node)) {
913
+ tagName = ts.isIdentifier(node.tagName) ? node.tagName.text : void 0;
914
+ attributes = node.attributes;
915
+ count = ZERO;
916
+ }
917
+ if (!tagName) return;
918
+ const getPos = lazy(() => source.getLineAndCharacterOfPosition(node.getStart(source)));
919
+ if (count !== void 0) {
920
+ const c = byName.get(tagName);
921
+ if (c && (count.max < c.totalMin || c.exclusiveChildren && count.min > c.totalMax)) {
922
+ const { line, character } = getPos();
923
+ diagnostics.push({
924
+ diagnostic: ViteDiagnostics.cardinalityViolation(c.name, c.totalMin, c.totalMax, count.min, count.max),
925
+ line: line + 1,
926
+ col: character + 1,
927
+ severity
928
+ });
929
+ }
930
+ }
931
+ if (attributes) {
932
+ const c = byNameAria.get(tagName);
933
+ if (c) iterate.forEach(attributes.properties, (attr) => {
934
+ if (!ts.isJsxAttribute(attr)) return;
935
+ const { initializer, name } = attr;
936
+ if ((ts.isIdentifier(name) ? name.text : void 0) !== "as" || !initializer) return;
937
+ let asValue;
938
+ if (ts.isStringLiteral(initializer)) asValue = initializer.text;
939
+ else if (ts.isJsxExpression(initializer) && initializer.expression !== void 0 && ts.isStringLiteral(initializer.expression)) asValue = initializer.expression.text;
940
+ if (asValue !== void 0 && asValue !== c.defaultTag) {
941
+ const { line, character } = getPos();
942
+ diagnostics.push({
943
+ diagnostic: ViteDiagnostics.ariaTagOverride(tagName, asValue, c.defaultTag),
944
+ line: line + 1,
945
+ col: character + 1,
946
+ severity
947
+ });
948
+ }
949
+ });
950
+ }
951
+ if (/^[A-Z]/.test(tagName)) {
952
+ const { line, character } = getPos();
953
+ usages.push({
954
+ tagName,
955
+ count,
956
+ line: line + 1,
957
+ col: character + 1
958
+ });
959
+ }
960
+ });
961
+ return {
962
+ diagnostics,
963
+ usages
964
+ };
965
+ }
966
+ const ZERO = {
967
+ min: 0,
968
+ max: 0
969
+ };
970
+ const ONE = {
971
+ min: 1,
972
+ max: 1
973
+ };
974
+ /**
975
+ * Analyzes an expression to determine how many JSX children it renders.
976
+ *
977
+ * Returns a `ChildCount` range when the count is statically determinable —
978
+ * including partially-dynamic patterns like conditionals and array literals.
979
+ * Returns `undefined` for unknowable cases (`.map()`, variable references,
980
+ * spread elements).
981
+ *
982
+ * Handles:
983
+ * - `null`, `false`, `undefined`, empty `{}` → 0
984
+ * - `cond && <El />` → [0, 1]
985
+ * - `cond || <El />` / `cond ?? <El />` → [min(sides), max(sides)]
986
+ * - `cond ? <A /> : <B />` → [min(branches), max(branches)]
987
+ * - `[<A />, <B />]` (no spreads) → exact array count
988
+ * - JSX element / fragment → 1 / fragment child count
989
+ * - Parenthesized expressions → delegate to inner
990
+ */
991
+ function countExpression(node) {
992
+ if (node.kind === ts.SyntaxKind.NullKeyword) return ZERO;
993
+ if (node.kind === ts.SyntaxKind.FalseKeyword) return ZERO;
994
+ if (ts.isIdentifier(node) && node.text === "undefined") return ZERO;
995
+ if (ts.isParenthesizedExpression(node)) return countExpression(node.expression);
996
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) return ONE;
997
+ if (ts.isJsxFragment(node)) return countJsxChildren(node.children);
998
+ if (ts.isArrayLiteralExpression(node)) {
999
+ let min = 0;
1000
+ let max = 0;
1001
+ for (const el of node.elements) {
1002
+ if (ts.isSpreadElement(el)) return void 0;
1003
+ const c = countExpression(el);
1004
+ if (!c) return void 0;
1005
+ min += c.min;
1006
+ max += c.max;
1007
+ }
1008
+ return {
1009
+ min,
1010
+ max
1011
+ };
1012
+ }
1013
+ if (ts.isBinaryExpression(node)) {
1014
+ const op = node.operatorToken.kind;
1015
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken) {
1016
+ const right = countExpression(node.right);
1017
+ if (!right) return void 0;
1018
+ return {
1019
+ min: 0,
1020
+ max: right.max
1021
+ };
1022
+ }
1023
+ if (op === ts.SyntaxKind.BarBarToken || op === ts.SyntaxKind.QuestionQuestionToken) {
1024
+ const left = countExpression(node.left);
1025
+ const right = countExpression(node.right);
1026
+ if (!left || !right) return void 0;
1027
+ return {
1028
+ min: Math.min(left.min, right.min),
1029
+ max: Math.max(left.max, right.max)
1030
+ };
1031
+ }
1032
+ }
1033
+ if (ts.isConditionalExpression(node)) {
1034
+ const whenTrue = countExpression(node.whenTrue);
1035
+ const whenFalse = countExpression(node.whenFalse);
1036
+ if (!whenTrue || !whenFalse) return void 0;
1037
+ return {
1038
+ min: Math.min(whenTrue.min, whenFalse.min),
1039
+ max: Math.max(whenTrue.max, whenFalse.max)
1040
+ };
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Counts the meaningful children of a JSX element's child list.
1045
+ * Whitespace-only `JsxText` nodes are ignored. Fragments are flattened.
1046
+ * Returns `undefined` if any child contribution is unknowable.
1047
+ */
1048
+ function countJsxChildren(children) {
1049
+ let min = 0;
1050
+ let max = 0;
1051
+ for (const child of children) {
1052
+ let c;
1053
+ if (ts.isJsxExpression(child)) c = child.expression === void 0 ? ZERO : countExpression(child.expression);
1054
+ else if (ts.isJsxText(child)) c = child.text.trim().length > 0 ? ONE : ZERO;
1055
+ else if (ts.isJsxFragment(child)) c = countJsxChildren(child.children);
1056
+ else c = ONE;
1057
+ if (!c) return void 0;
1058
+ min += c.min;
1059
+ max += c.max;
1060
+ }
1061
+ return {
1062
+ min,
1063
+ max
1064
+ };
1065
+ }
1066
+ //#endregion
1067
+ //#region ../../plugins/vite/src/imports.ts
1068
+ /**
1069
+ * Extracts named import bindings from a source file.
1070
+ * Returns a Map from local binding name to its ImportBinding.
1071
+ *
1072
+ * `import { Button } from './button'` → Map { 'Button' => { importedName: 'Button', specifier: './button' } }
1073
+ * `import { Button as MyBtn } from './button'` → Map { 'MyBtn' => { importedName: 'Button', specifier: './button' } }
1074
+ * `import { Btn as Button } from './button'` → Map { 'Button' => { importedName: 'Btn', specifier: './button' } }
1075
+ *
1076
+ * Default imports and namespace imports (`* as X`) are ignored — only named
1077
+ * bindings that could correspond to component identifiers in JSX are collected.
1078
+ */
1079
+ function extractImportSpecifiers(source) {
1080
+ const result = /* @__PURE__ */ new Map();
1081
+ walkEach(source, (node) => {
1082
+ if (!ts.isImportDeclaration(node)) return;
1083
+ const moduleSpecifier = node.moduleSpecifier;
1084
+ if (!ts.isStringLiteral(moduleSpecifier)) return;
1085
+ const specifier = moduleSpecifier.text;
1086
+ const namedBindings = node.importClause?.namedBindings;
1087
+ if (!namedBindings || !ts.isNamedImports(namedBindings)) return;
1088
+ iterate.forEach(namedBindings.elements, (element) => {
1089
+ const { isTypeOnly, name, propertyName } = element;
1090
+ if (isTypeOnly) return;
1091
+ const { text: localName } = name;
1092
+ const importedName = propertyName?.text ?? localName;
1093
+ result.set(localName, {
1094
+ importedName,
1095
+ specifier
1096
+ });
1097
+ });
1098
+ });
1099
+ return result;
1100
+ }
1101
+ //#endregion
1102
+ //#region ../../plugins/vite/src/registry.ts
1103
+ /**
1104
+ * Accumulates constraint and import data across multiple `transform` calls so
1105
+ * that cross-file cardinality violations can be detected in `buildEnd`.
1106
+ *
1107
+ * Lifecycle:
1108
+ * 1. `registerConstraints(id, ...)` — called for every TSX/JSX file that
1109
+ * defines constrained components.
1110
+ * 2. `registerImports(id, resolvedMap)` — called when a file imports component
1111
+ * names that appear in JSX; the map keys are local binding names, values are
1112
+ * the absolute file IDs returned by `this.resolve()`.
1113
+ * 3. `addPendingUsage(id, usage)` — records a JSX usage whose tag name is
1114
+ * imported from another file, for deferred validation.
1115
+ * 4. `diagnostics(severity)` — called once in `buildEnd`; resolves each pending
1116
+ * usage to its source constraint and emits a `FileDiagnostic` for any
1117
+ * cardinality violation.
1118
+ */
1119
+ var ConstraintRegistry = class {
1120
+ constraints = /* @__PURE__ */ new Map();
1121
+ importMap = /* @__PURE__ */ new Map();
1122
+ pending = /* @__PURE__ */ new Map();
1123
+ /** Records the component constraints declared in a source file, keyed by component name. */
1124
+ registerConstraints(fileId, cs) {
1125
+ this.constraints.set(fileId, new Map(cs.map((c) => [c.name, c])));
1126
+ }
1127
+ /** resolvedImports: local name → absolute file ID of the exporting module. */
1128
+ registerImports(fileId, resolvedImports) {
1129
+ this.importMap.set(fileId, resolvedImports);
1130
+ }
1131
+ /** Queues a JSX usage site whose constraint lives in another file for deferred cross-file validation at buildEnd. */
1132
+ addPendingUsage(fileId, usage) {
1133
+ let list = this.pending.get(fileId);
1134
+ if (!list) {
1135
+ list = [];
1136
+ this.pending.set(fileId, list);
1137
+ }
1138
+ list.push(usage);
1139
+ }
1140
+ /** Resolves a component name used in `fileId` to its constraint definition. */
1141
+ resolveConstraint(fileId, name) {
1142
+ const imports = this.importMap.get(fileId);
1143
+ if (!imports) return void 0;
1144
+ const sourceId = imports.get(name);
1145
+ if (!sourceId) return void 0;
1146
+ return this.constraints.get(sourceId)?.get(name);
1147
+ }
1148
+ /** Returns cardinality violations across all pending cross-file usages. */
1149
+ diagnostics(severity) {
1150
+ const result = [];
1151
+ iterate.forEach(this.pending, ([fileId, usages]) => {
1152
+ iterate.forEach(usages, ({ col, count, line, tagName }) => {
1153
+ if (count === void 0) return;
1154
+ const constraint = this.resolveConstraint(fileId, tagName);
1155
+ if (!constraint) return;
1156
+ const { totalMin, totalMax, name, exclusiveChildren } = constraint;
1157
+ const { min, max } = count;
1158
+ if (!(max < totalMin) && !(exclusiveChildren && min > totalMax)) return;
1159
+ result.push({
1160
+ fileId,
1161
+ diagnostic: ViteDiagnostics.cardinalityViolation(name, totalMin, totalMax, min, max),
1162
+ line,
1163
+ col,
1164
+ severity
1165
+ });
1166
+ });
1167
+ });
1168
+ return result;
1169
+ }
1170
+ };
1171
+ //#endregion
1172
+ //#region ../../plugins/vite/src/slot-transform.ts
1173
+ /**
1174
+ * Compile-time asChild → render-prop transform.
1175
+ *
1176
+ * Rewrites JSX usage sites of the form:
1177
+ * <Component asChild ...props>
1178
+ * <childTag ...childProps>children</childTag>
1179
+ * </Component>
1180
+ *
1181
+ * to the render-prop form:
1182
+ * <Component render={(_p) => <childTag ...childProps {..._p} />} ...props />
1183
+ *
1184
+ * The render-prop form eliminates the Slot/cloneElement/mergeProps path at
1185
+ * runtime — resolved props are passed directly to the render callback with no
1186
+ * element cloning.
1187
+ *
1188
+ * **Safety conditions** — the transform is skipped if any of these are true:
1189
+ * 1. The child has a dynamic `className` expression (cannot merge safely).
1190
+ * A string-literal `className` IS handled: the transform generates
1191
+ * `{..._p, className: _p.className + ' childCls'}`.
1192
+ * 2. The child has a bare `style` or `on*` attribute without an initializer.
1193
+ * Static object-literal and expression-valued `style` props are merged:
1194
+ * `style={{..._p.style, ...childStyle}}`. Event handlers are composed:
1195
+ * `onClick={(_e) => { (childHandler)(_e); _p.onClick?.(_e); }}`.
1196
+ * 3. The component name starts with a lowercase letter (HTML intrinsic — not
1197
+ * a polymorphic component).
1198
+ * 4. There are zero or more than one meaningful child elements.
1199
+ *
1200
+ * The transform is conservative: any condition that is not statically clear
1201
+ * causes the node to be left unchanged.
1202
+ */
1203
+ /** Returns true when the first character of `s` is an uppercase ASCII letter (A–Z). */
1204
+ function isUpperCase(s) {
1205
+ return s.charCodeAt(0) >= 65 && s.charCodeAt(0) <= 90;
1206
+ }
1207
+ /** Returns the attribute name string; empty string for namespaced names. */
1208
+ function jsxAttrName(attr) {
1209
+ return ts.isIdentifier(attr.name) ? attr.name.text : "";
1210
+ }
1211
+ function getStaticClassName(child) {
1212
+ const attrs = ts.isJsxElement(child) ? child.openingElement.attributes.properties : child.attributes.properties;
1213
+ for (const attr of attrs) {
1214
+ if (!ts.isJsxAttribute(attr) || jsxAttrName(attr) !== "className") continue;
1215
+ const init = attr.initializer;
1216
+ if (!init) return {
1217
+ absent: false,
1218
+ value: ""
1219
+ };
1220
+ if (ts.isStringLiteral(init)) return {
1221
+ absent: false,
1222
+ value: init.text
1223
+ };
1224
+ if (ts.isJsxExpression(init) && init.expression !== void 0 && ts.isStringLiteral(init.expression)) return {
1225
+ absent: false,
1226
+ value: init.expression.text
1227
+ };
1228
+ return null;
1229
+ }
1230
+ return { absent: true };
1231
+ }
1232
+ function getStyleInfo(child) {
1233
+ const attrs = ts.isJsxElement(child) ? child.openingElement.attributes.properties : child.attributes.properties;
1234
+ for (const attr of attrs) {
1235
+ if (!ts.isJsxAttribute(attr) || jsxAttrName(attr) !== "style") continue;
1236
+ const init = attr.initializer;
1237
+ if (!init || ts.isStringLiteral(init)) return null;
1238
+ if (ts.isJsxExpression(init) && init.expression !== void 0) return {
1239
+ absent: false,
1240
+ expr: init.expression
1241
+ };
1242
+ return null;
1243
+ }
1244
+ return { absent: true };
1245
+ }
1246
+ function getEventHandlers(child) {
1247
+ const attrs = ts.isJsxElement(child) ? child.openingElement.attributes.properties : child.attributes.properties;
1248
+ const handlers = [];
1249
+ for (const attr of attrs) {
1250
+ if (!ts.isJsxAttribute(attr)) continue;
1251
+ const name = jsxAttrName(attr);
1252
+ if (!/^on[A-Z]/.test(name)) continue;
1253
+ const init = attr.initializer;
1254
+ if (!init) return null;
1255
+ if (ts.isJsxExpression(init) && init.expression !== void 0) {
1256
+ handlers.push({
1257
+ name,
1258
+ expr: init.expression
1259
+ });
1260
+ continue;
1261
+ }
1262
+ return null;
1263
+ }
1264
+ return handlers;
1265
+ }
1266
+ /** Returns true if the opening element has an `asChild` attribute (bare or `={true}`). */
1267
+ function hasAsChild(opening) {
1268
+ for (const attr of opening.attributes.properties) {
1269
+ if (!ts.isJsxAttribute(attr)) continue;
1270
+ if (jsxAttrName(attr) !== "asChild") continue;
1271
+ if (attr.initializer === void 0) return true;
1272
+ if (ts.isJsxExpression(attr.initializer) && attr.initializer.expression !== void 0 && attr.initializer.expression.kind === ts.SyntaxKind.TrueKeyword) return true;
1273
+ }
1274
+ return false;
1275
+ }
1276
+ /** Returns the single meaningful JSX element child, or undefined if there isn't exactly one. */
1277
+ function getSingleElementChild(node) {
1278
+ const meaningful = [];
1279
+ for (const child of node.children) {
1280
+ if (ts.isJsxText(child)) {
1281
+ if (child.text.trim().length > 0) return void 0;
1282
+ continue;
1283
+ }
1284
+ if (ts.isJsxExpression(child)) return void 0;
1285
+ if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child)) {
1286
+ meaningful.push(child);
1287
+ continue;
1288
+ }
1289
+ return;
1290
+ }
1291
+ return meaningful.length === 1 ? meaningful[0] : void 0;
1292
+ }
1293
+ /** Returns the tag name string if the opening element has a simple identifier tag. */
1294
+ function getTagName(child) {
1295
+ const tag = ts.isJsxElement(child) ? child.openingElement.tagName : child.tagName;
1296
+ return ts.isIdentifier(tag) ? tag.text : void 0;
1297
+ }
1298
+ /**
1299
+ * Builds the attribute list for the transformed parent, omitting `asChild`
1300
+ * and adding `render={(_p) => <childTag ...childAttrs {..._p} />}`.
1301
+ *
1302
+ * Merged props are placed after the `{..._p}` spread so they override _p:
1303
+ * - Static `className` → `className={_p.className + ' childCls'}`
1304
+ * - `style` → `style={{..._p.style, ...childStyleExpr}}`
1305
+ * - `on*` handlers → `onClick={(_e) => { (childHandler)(_e); _p.onClick?.(_e); }}`
1306
+ */
1307
+ function buildTransformedAttributes(factory, original, child, tagName, clsResult, styleInfo, handlers) {
1308
+ const parentAttrs = original.attributes.properties.filter((attr) => !(ts.isJsxAttribute(attr) && jsxAttrName(attr) === "asChild"));
1309
+ const hasStaticCls = !clsResult.absent;
1310
+ const hasStyle = !styleInfo.absent;
1311
+ const handlerNames = new Set(iterate.map(handlers, (h) => h.name));
1312
+ const childOpeningAttrs = (ts.isJsxElement(child) ? child.openingElement.attributes.properties : child.attributes.properties).filter((attr) => !(ts.isJsxAttribute(attr) && (jsxAttrName(attr) === "ref" || hasStaticCls && jsxAttrName(attr) === "className" || hasStyle && jsxAttrName(attr) === "style" || handlerNames.has(jsxAttrName(attr)))));
1313
+ const childContent = ts.isJsxElement(child) ? child.children : void 0;
1314
+ const spreadProp = factory.createJsxSpreadAttribute(factory.createIdentifier("_p"));
1315
+ const extraAttrs = [];
1316
+ if (hasStaticCls && clsResult.value !== "") {
1317
+ const mergedExpr = factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier("_p"), "className"), ts.SyntaxKind.PlusToken, factory.createStringLiteral(` ${clsResult.value}`));
1318
+ extraAttrs.push(factory.createJsxAttribute(factory.createIdentifier("className"), factory.createJsxExpression(void 0, mergedExpr)));
1319
+ }
1320
+ if (hasStyle) {
1321
+ const styleExpr = styleInfo.expr;
1322
+ const pStyleSpread = factory.createSpreadAssignment(factory.createPropertyAccessExpression(factory.createIdentifier("_p"), "style"));
1323
+ let mergedStyleObj;
1324
+ if (ts.isObjectLiteralExpression(styleExpr)) mergedStyleObj = factory.createObjectLiteralExpression([pStyleSpread, ...styleExpr.properties], false);
1325
+ else mergedStyleObj = factory.createObjectLiteralExpression([pStyleSpread, factory.createSpreadAssignment(styleExpr)], false);
1326
+ extraAttrs.push(factory.createJsxAttribute(factory.createIdentifier("style"), factory.createJsxExpression(void 0, mergedStyleObj)));
1327
+ }
1328
+ for (const { name, expr } of handlers) {
1329
+ const eParam = factory.createParameterDeclaration(void 0, void 0, "_e");
1330
+ const callChild = factory.createExpressionStatement(factory.createCallExpression(factory.createParenthesizedExpression(expr), void 0, [factory.createIdentifier("_e")]));
1331
+ const callParent = factory.createExpressionStatement(factory.createCallChain(factory.createPropertyAccessExpression(factory.createIdentifier("_p"), name), factory.createToken(ts.SyntaxKind.QuestionDotToken), void 0, [factory.createIdentifier("_e")]));
1332
+ const composedFn = factory.createArrowFunction(void 0, void 0, [eParam], void 0, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([callChild, callParent], true));
1333
+ extraAttrs.push(factory.createJsxAttribute(factory.createIdentifier(name), factory.createJsxExpression(void 0, composedFn)));
1334
+ }
1335
+ const childAttrsWithSpread = factory.createJsxAttributes([
1336
+ ...childOpeningAttrs,
1337
+ spreadProp,
1338
+ ...extraAttrs
1339
+ ]);
1340
+ const childElement = ts.isJsxElement(child) ? factory.createJsxElement(factory.createJsxOpeningElement(factory.createIdentifier(tagName), void 0, childAttrsWithSpread), childContent ?? [], factory.createJsxClosingElement(factory.createIdentifier(tagName))) : factory.createJsxSelfClosingElement(factory.createIdentifier(tagName), void 0, childAttrsWithSpread);
1341
+ const renderArrow = factory.createArrowFunction(void 0, void 0, [factory.createParameterDeclaration(void 0, void 0, "_p")], void 0, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createParenthesizedExpression(childElement));
1342
+ const renderAttr = factory.createJsxAttribute(factory.createIdentifier("render"), factory.createJsxExpression(void 0, renderArrow));
1343
+ return factory.createJsxAttributes([renderAttr, ...parentAttrs]);
1344
+ }
1345
+ /**
1346
+ * Returns a TypeScript transformer that rewrites safe `asChild` JSX patterns
1347
+ * to the render-prop form in a single source file.
1348
+ */
1349
+ function createAsChildTransformer(factory) {
1350
+ return (context) => {
1351
+ function visit(node) {
1352
+ if (!ts.isJsxElement(node)) return ts.visitEachChild(node, visit, context);
1353
+ const opening = node.openingElement;
1354
+ const tagName = ts.isIdentifier(opening.tagName) ? opening.tagName.text : void 0;
1355
+ if (!tagName || !isUpperCase(tagName)) return ts.visitEachChild(node, visit, context);
1356
+ if (!hasAsChild(opening)) return ts.visitEachChild(node, visit, context);
1357
+ const child = getSingleElementChild(node);
1358
+ if (!child) return ts.visitEachChild(node, visit, context);
1359
+ const clsResult = getStaticClassName(child);
1360
+ if (clsResult === null) return ts.visitEachChild(node, visit, context);
1361
+ const styleInfo = getStyleInfo(child);
1362
+ if (styleInfo === null) return ts.visitEachChild(node, visit, context);
1363
+ const handlers = getEventHandlers(child);
1364
+ if (handlers === null) return ts.visitEachChild(node, visit, context);
1365
+ const childTag = getTagName(child);
1366
+ if (!childTag) return ts.visitEachChild(node, visit, context);
1367
+ const newAttrs = buildTransformedAttributes(factory, opening, child, childTag, clsResult, styleInfo, handlers);
1368
+ return factory.createJsxSelfClosingElement(opening.tagName, opening.typeArguments, newAttrs);
1369
+ }
1370
+ return (sourceFile) => ts.visitEachChild(sourceFile, visit, context);
1371
+ };
1372
+ }
1373
+ /**
1374
+ * Applies the asChild → render-prop transform to the given TypeScript source
1375
+ * file and returns the printed output.
1376
+ *
1377
+ * Returns null if no `asChild` attribute is found in the source (fast path —
1378
+ * avoids parsing overhead for files that don't use the pattern).
1379
+ */
1380
+ function transformAsChild(source) {
1381
+ if (!iterate.some(walk(source), (node) => ts.isJsxAttribute(node) && jsxAttrName(node) === "asChild")) return null;
1382
+ const result = ts.transform(source, [createAsChildTransformer(ts.factory)], {
1383
+ jsx: ts.JsxEmit.Preserve,
1384
+ target: ts.ScriptTarget.Latest
1385
+ });
1386
+ const output = ts.createPrinter({
1387
+ newLine: ts.NewLineKind.LineFeed,
1388
+ removeComments: false
1389
+ }).printFile(result.transformed[0]);
1390
+ result.dispose();
1391
+ return output;
1392
+ }
1393
+ //#endregion
1394
+ //#region ../../plugins/vite/src/static-compose.ts
1395
+ /**
1396
+ * Compile-time static composition transform.
1397
+ *
1398
+ * For factory calls that have `precomputedClasses` injected (by classExtractPlugin),
1399
+ * replaces static JSX usage sites with direct element creation — bypassing the
1400
+ * runtime render pipeline entirely.
1401
+ *
1402
+ * Example:
1403
+ * // Source (same file defines Button and uses it)
1404
+ * const Button = createContractComponent({ tag: 'button', styling: { precomputedClasses: {...} } })
1405
+ * <Button size="lg">Click</Button>
1406
+ *
1407
+ * // Output
1408
+ * const Button = createContractComponent({ ... }) // unchanged — still exported
1409
+ * <button className="btn btn-lg">Click</button> // inlined!
1410
+ *
1411
+ * **Eligibility conditions** — a usage site is inlined only when:
1412
+ * 1. The component is defined in the same file or imported from a file already
1413
+ * transformed by this plugin, with `precomputedClasses` injected
1414
+ * 2. No `as`, `asChild`, `render`, or spread attributes at the usage site
1415
+ * 3. All variant props are static string literals
1416
+ * 4. `className` is absent or a static string literal
1417
+ * 5. The factory config has no top-level `defaults` and no `enforcement`
1418
+ * (either would require runtime prop normalization that inlining skips)
1419
+ *
1420
+ * The factory call itself is intentionally left in the output so the component
1421
+ * remains exportable for cross-file consumption that falls back to the runtime
1422
+ * path. Dead-code elimination at the bundler level can remove it when no
1423
+ * runtime path remains.
1424
+ *
1425
+ * **Cross-file known limitations:**
1426
+ * - Barrel re-exports are not resolved (import from index.ts re-exporting ./Button)
1427
+ * - Aliased imports are not matched (`export { Btn as Button }` won't be found)
1428
+ * - Dev-mode ordering: definition file may not yet be transformed when consumer runs
1429
+ * All three degrade gracefully to the runtime path — no error, no broken output.
1430
+ */
1431
+ /** Returns the text of a string literal node, or undefined. */
1432
+ function asStringLiteral(node) {
1433
+ return node !== void 0 && ts.isStringLiteral(node) ? node.text : void 0;
1434
+ }
1435
+ /**
1436
+ * Walks the source file and extracts metadata for each same-file factory call
1437
+ * that is eligible for static composition.
1438
+ */
1439
+ function extractStaticComponents(source, calleeNames) {
1440
+ const result = /* @__PURE__ */ new Map();
1441
+ walkEach(source, (node) => {
1442
+ if (!ts.isVariableDeclaration(node)) return;
1443
+ if (!ts.isIdentifier(node.name)) return;
1444
+ const varName = node.name.text;
1445
+ const init = node.initializer;
1446
+ if (!init) return;
1447
+ let call;
1448
+ if (ts.isCallExpression(init) && isFactoryCall(init, calleeNames)) call = init;
1449
+ else if (ts.isAsExpression(init) && ts.isCallExpression(init.expression) && isFactoryCall(init.expression, calleeNames)) call = init.expression;
1450
+ if (!call) return;
1451
+ const arg = firstObjectArg(call);
1452
+ if (!arg) return;
1453
+ const defaultTag = asStringLiteral(getProperty(arg, "tag"));
1454
+ if (!defaultTag) return;
1455
+ const stylingObj = asObject(getProperty(arg, "styling"));
1456
+ if (!stylingObj) return;
1457
+ const precomputedNode = asObject(getProperty(stylingObj, "precomputedClasses"));
1458
+ if (!precomputedNode) return;
1459
+ const precomputedClasses = {};
1460
+ if (iterate.find(precomputedNode.properties, (prop) => {
1461
+ if (!ts.isPropertyAssignment(prop)) return true;
1462
+ const { initializer, name } = prop;
1463
+ const key = ts.isStringLiteral(name) ? name.text : void 0;
1464
+ const val = asStringLiteral(initializer);
1465
+ if (key === void 0 || val === void 0) return true;
1466
+ precomputedClasses[key] = val;
1467
+ return null;
1468
+ })) return;
1469
+ if (getProperty(arg, "defaults") !== void 0) return;
1470
+ if (getProperty(arg, "enforcement") !== void 0) return;
1471
+ const variantKeys = /* @__PURE__ */ new Set();
1472
+ const variantsObj = asObject(getProperty(stylingObj, "variants"));
1473
+ if (variantsObj) iterate.forEach(variantsObj.properties, (prop) => {
1474
+ if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) variantKeys.add(prop.name.text);
1475
+ });
1476
+ result.set(varName, {
1477
+ defaultTag,
1478
+ variantKeys,
1479
+ precomputedClasses
1480
+ });
1481
+ });
1482
+ return result;
1483
+ }
1484
+ /** Reads the value of a named JSX attribute from the attribute list: absent, a static string, or dynamic. */
1485
+ function readAttrValue(attrs, name) {
1486
+ for (const attr of attrs) {
1487
+ if (!ts.isJsxAttribute(attr)) continue;
1488
+ if (!(ts.isIdentifier(attr.name) && attr.name.text === name)) continue;
1489
+ const init = attr.initializer;
1490
+ if (!init) return {
1491
+ kind: "string",
1492
+ value: ""
1493
+ };
1494
+ if (ts.isStringLiteral(init)) return {
1495
+ kind: "string",
1496
+ value: init.text
1497
+ };
1498
+ if (ts.isJsxExpression(init) && init.expression !== void 0) {
1499
+ if (ts.isStringLiteral(init.expression)) return {
1500
+ kind: "string",
1501
+ value: init.expression.text
1502
+ };
1503
+ return { kind: "dynamic" };
1504
+ }
1505
+ return { kind: "dynamic" };
1506
+ }
1507
+ return { kind: "absent" };
1508
+ }
1509
+ /** Returns a TS transformer that replaces eligible JSX usage sites with direct element creation. */
1510
+ function createStaticCompositionTransformer(factory, components, onInlined) {
1511
+ return (context) => {
1512
+ function visit(node) {
1513
+ const isSelfClose = ts.isJsxSelfClosingElement(node);
1514
+ const isOpen = ts.isJsxElement(node);
1515
+ if (!isSelfClose && !isOpen) return ts.visitEachChild(node, visit, context);
1516
+ const tagNode = isOpen ? node.openingElement.tagName : node.tagName;
1517
+ if (!ts.isIdentifier(tagNode)) return ts.visitEachChild(node, visit, context);
1518
+ const info = components.get(tagNode.text);
1519
+ if (!info) return ts.visitEachChild(node, visit, context);
1520
+ const attrList = isOpen ? node.openingElement.attributes.properties : node.attributes.properties;
1521
+ if (attrList.some(ts.isJsxSpreadAttribute)) return ts.visitEachChild(node, visit, context);
1522
+ if (readAttrValue(attrList, "asChild").kind !== "absent") return ts.visitEachChild(node, visit, context);
1523
+ if (readAttrValue(attrList, "render").kind !== "absent") return ts.visitEachChild(node, visit, context);
1524
+ const asVal = readAttrValue(attrList, "as");
1525
+ if (asVal.kind === "dynamic") return ts.visitEachChild(node, visit, context);
1526
+ const outputTag = asVal.kind === "string" ? asVal.value : info.defaultTag;
1527
+ const variantProps = {};
1528
+ for (const propName of info.variantKeys) {
1529
+ const val = readAttrValue(attrList, propName);
1530
+ if (val.kind === "absent") continue;
1531
+ if (val.kind === "string") {
1532
+ variantProps[propName] = val.value;
1533
+ continue;
1534
+ }
1535
+ return ts.visitEachChild(node, visit, context);
1536
+ }
1537
+ const cacheKey = buildCacheKey(variantProps);
1538
+ const baseClass = info.precomputedClasses[cacheKey];
1539
+ if (baseClass === void 0) return ts.visitEachChild(node, visit, context);
1540
+ const clsVal = readAttrValue(attrList, "className");
1541
+ if (clsVal.kind === "dynamic") return ts.visitEachChild(node, visit, context);
1542
+ const finalClass = clsVal.kind === "string" && clsVal.value ? `${baseClass} ${clsVal.value}` : baseClass;
1543
+ const strip = /* @__PURE__ */ new Set([
1544
+ ...info.variantKeys,
1545
+ "as",
1546
+ "asChild",
1547
+ "render",
1548
+ "className"
1549
+ ]);
1550
+ const outputAttrs = [factory.createJsxAttribute(factory.createIdentifier("className"), factory.createStringLiteral(finalClass))];
1551
+ for (const attr of attrList) {
1552
+ if (ts.isJsxSpreadAttribute(attr)) continue;
1553
+ if (!ts.isJsxAttribute(attr)) continue;
1554
+ const name = ts.isIdentifier(attr.name) ? attr.name.text : "";
1555
+ if (strip.has(name)) continue;
1556
+ outputAttrs.push(attr);
1557
+ }
1558
+ const newAttrs = factory.createJsxAttributes(outputAttrs);
1559
+ const outputTagIdent = factory.createIdentifier(outputTag);
1560
+ onInlined();
1561
+ if (isSelfClose) return factory.createJsxSelfClosingElement(outputTagIdent, void 0, newAttrs);
1562
+ const visitedChildren = node.children.map((c) => ts.visitNode(c, visit));
1563
+ return factory.createJsxElement(factory.createJsxOpeningElement(outputTagIdent, void 0, newAttrs), visitedChildren, factory.createJsxClosingElement(outputTagIdent));
1564
+ }
1565
+ return (sourceFile) => ts.visitEachChild(sourceFile, visit, context);
1566
+ };
1567
+ }
1568
+ /**
1569
+ * Applies the static composition transform to the given source file.
1570
+ *
1571
+ * `importedComponents` contains cross-file metadata resolved by the plugin
1572
+ * registry — components defined in other files that were already transformed.
1573
+ * When empty (dev mode ordering race or barrel re-export), same-file components
1574
+ * still inline normally; cross-file sites fall through to the runtime path.
1575
+ *
1576
+ * Returns null when:
1577
+ * - No eligible factory calls or imported components are present
1578
+ * - No eligible usage sites are found after analysis
1579
+ */
1580
+ function composeStatically(source, calleeNames, importedComponents = /* @__PURE__ */ new Map()) {
1581
+ const sameFile = extractStaticComponents(source, calleeNames);
1582
+ const components = importedComponents.size > 0 ? new Map([...importedComponents, ...sameFile]) : sameFile;
1583
+ if (components.size === 0) return null;
1584
+ const componentNames = new Set(components.keys());
1585
+ let hasEligibleTag = false;
1586
+ walkEach(source, (n) => {
1587
+ if (hasEligibleTag) return;
1588
+ const tagNode = ts.isJsxElement(n) ? n.openingElement.tagName : ts.isJsxSelfClosingElement(n) ? n.tagName : void 0;
1589
+ if (tagNode && ts.isIdentifier(tagNode) && componentNames.has(tagNode.text)) hasEligibleTag = true;
1590
+ });
1591
+ if (!hasEligibleTag) return null;
1592
+ let didInline = false;
1593
+ const transformResult = ts.transform(source, [createStaticCompositionTransformer(ts.factory, components, () => {
1594
+ didInline = true;
1595
+ })], {
1596
+ jsx: ts.JsxEmit.Preserve,
1597
+ target: ts.ScriptTarget.Latest
1598
+ });
1599
+ if (!didInline) {
1600
+ transformResult.dispose();
1601
+ return null;
1602
+ }
1603
+ const output = ts.createPrinter({
1604
+ newLine: ts.NewLineKind.LineFeed,
1605
+ removeComments: false
1606
+ }).printFile(transformResult.transformed[0]);
1607
+ transformResult.dispose();
1608
+ return output;
1609
+ }
1610
+ //#endregion
1611
+ //#region ../../lib/tailwind/src/layout-keys.ts
1612
+ /**
1613
+ * Canonical list of reserved layout prop names.
1614
+ *
1615
+ * This array is the single source of truth for every supported CSS `display`
1616
+ * value exposed as a boolean prop. The `LayoutKey` type is derived directly
1617
+ * from this list.
1618
+ *
1619
+ * To add a new display mode:
1620
+ * 1. Add the display value here.
1621
+ * 2. Register its layout family in `LAYOUT_FAMILY_MAP`.
1622
+ *
1623
+ * Prop names intentionally match the corresponding Tailwind/CSS display
1624
+ * utilities, so no additional prop-to-class mapping is required.
1625
+ */
1626
+ const layoutKeys = [
1627
+ "flex",
1628
+ "inline-flex",
1629
+ "grid",
1630
+ "inline-grid",
1631
+ "block",
1632
+ "inline-block",
1633
+ "inline",
1634
+ "hidden",
1635
+ "contents",
1636
+ "flow-root",
1637
+ "list-item",
1638
+ "table",
1639
+ "inline-table",
1640
+ "table-caption",
1641
+ "table-cell",
1642
+ "table-column",
1643
+ "table-column-group",
1644
+ "table-footer-group",
1645
+ "table-header-group",
1646
+ "table-row-group",
1647
+ "table-row"
1648
+ ];
1649
+ //#endregion
1650
+ //#region ../../plugins/vite/src/design-tokens.ts
1651
+ /**
1652
+ * Compile-time design token collection.
1653
+ *
1654
+ * Walks factory call ASTs and extracts every statically-declared class string
1655
+ * used in `styling.base`, `styling.variants`, `styling.compounds`, and
1656
+ * `styling.tags`. Collects them into a per-component manifest and writes a
1657
+ * JSON file on `writeBundle`.
1658
+ *
1659
+ * The primary use case is Tailwind's `safelist` — point the emitted file at
1660
+ * Tailwind's content so none of the variant classes are purged:
1661
+ *
1662
+ * // tailwind.config.js
1663
+ * export default { content: ['./src/**', './praxis-tokens.json'] }
1664
+ *
1665
+ * This is a Tailwind v3-style `content`-array mechanism: v4 does not scan
1666
+ * `tailwind.config.js` unless the app explicitly adds `@config` to its CSS,
1667
+ * which is not the standard v4 setup. On v4, display classes (flex, grid,
1668
+ * inline-block, etc.) are not covered by this manifest at all — those are
1669
+ * runtime-assembled and require importing `praxis-kit/tailwind.css` instead
1670
+ * (see lib/tailwind/src/tailwind-safelist.css).
1671
+ *
1672
+ * The JSON schema is intentionally stable and human-readable:
1673
+ *
1674
+ * {
1675
+ * "components": {
1676
+ * "Button": {
1677
+ * "base": "btn",
1678
+ * "variantClasses": ["btn-sm", "btn-md", "btn-lg"],
1679
+ * "compoundClasses": ["btn-compact"],
1680
+ * "tagClasses": ["link-style"]
1681
+ * }
1682
+ * },
1683
+ * "allClasses": ["btn", "btn-sm", ...]
1684
+ * }
1685
+ */
1686
+ /** Recursively appends non-empty string literals from `node` into `out`. Handles string and array literals; ignores other shapes. */
1687
+ function collectStringValues(node, out) {
1688
+ if (!node) return;
1689
+ if (ts.isStringLiteral(node)) {
1690
+ if (node.text.trim()) out.push(node.text);
1691
+ return;
1692
+ }
1693
+ if (ts.isArrayLiteralExpression(node)) iterate.forEach(node.elements, (elem) => {
1694
+ collectStringValues(elem, out);
1695
+ });
1696
+ }
1697
+ /** Extracts all statically-declared class strings from a `styling` object's `base`, `variants`, `compounds`, and `tags` fields. */
1698
+ function extractStylingTokens(stylingObj) {
1699
+ const base = [];
1700
+ const variantClasses = [];
1701
+ const compoundClasses = [];
1702
+ const tagClasses = [];
1703
+ collectStringValues(getProperty(stylingObj, "base"), base);
1704
+ const variantsObj = asObject(getProperty(stylingObj, "variants"));
1705
+ if (variantsObj) iterate.forEach(variantsObj.properties, (dimProp) => {
1706
+ if (!ts.isPropertyAssignment(dimProp)) return;
1707
+ const valuesObj = asObject(dimProp.initializer);
1708
+ if (!valuesObj) return;
1709
+ iterate.forEach(valuesObj.properties, (vp) => {
1710
+ if (!ts.isPropertyAssignment(vp)) return;
1711
+ collectStringValues(vp.initializer, variantClasses);
1712
+ });
1713
+ });
1714
+ const compoundsArr = asArray(getProperty(stylingObj, "compounds"));
1715
+ if (compoundsArr) iterate.forEach(compoundsArr.elements, (elem) => {
1716
+ const obj = asObject(elem);
1717
+ if (!obj) return;
1718
+ collectStringValues(getProperty(obj, "class"), compoundClasses);
1719
+ });
1720
+ const tagsObj = asObject(getProperty(stylingObj, "tags"));
1721
+ if (tagsObj) iterate.forEach(tagsObj.properties, (tp) => {
1722
+ if (!ts.isPropertyAssignment(tp)) return;
1723
+ collectStringValues(tp.initializer, tagClasses);
1724
+ });
1725
+ return {
1726
+ base,
1727
+ variantClasses,
1728
+ compoundClasses,
1729
+ tagClasses
1730
+ };
1731
+ }
1732
+ /**
1733
+ * Collects design tokens from all factory calls in a single source file.
1734
+ * Each entry in the returned map is keyed by the component variable name.
1735
+ *
1736
+ * Only `const X = factory(...)` declarations are handled; exported or
1737
+ * destructured patterns fall through (same scope as `collectConstraints`).
1738
+ */
1739
+ function collectFileTokens(source, calleeNames) {
1740
+ const result = /* @__PURE__ */ new Map();
1741
+ ts.forEachChild(source, (stmt) => {
1742
+ if (!ts.isVariableStatement(stmt)) return;
1743
+ iterate.forEach(stmt.declarationList.declarations, (decl) => {
1744
+ const { initializer, name } = decl;
1745
+ if (!initializer || !ts.isCallExpression(initializer)) return;
1746
+ if (!isFactoryCall(initializer, calleeNames)) return;
1747
+ const arg = firstObjectArg(initializer);
1748
+ if (!arg) return;
1749
+ const stylingObj = asObject(getProperty(arg, "styling"));
1750
+ if (!stylingObj) return;
1751
+ const definedName = ts.isIdentifier(name) ? name.text : void 0;
1752
+ if (!definedName) return;
1753
+ result.set(definedName, extractStylingTokens(stylingObj));
1754
+ });
1755
+ });
1756
+ return result;
1757
+ }
1758
+ /** Merges two ComponentTokens with deduplication per class list. Returns `incoming` when `existing` is undefined. */
1759
+ function mergeTokens(existing, incoming) {
1760
+ if (!existing) return incoming;
1761
+ return {
1762
+ base: [.../* @__PURE__ */ new Set([...existing.base, ...incoming.base])],
1763
+ variantClasses: [.../* @__PURE__ */ new Set([...existing.variantClasses, ...incoming.variantClasses])],
1764
+ compoundClasses: [.../* @__PURE__ */ new Set([...existing.compoundClasses, ...incoming.compoundClasses])],
1765
+ tagClasses: [.../* @__PURE__ */ new Set([...existing.tagClasses, ...incoming.tagClasses])]
1766
+ };
1767
+ }
1768
+ /**
1769
+ * Builds a DesignTokenManifest from the accumulated per-component token maps,
1770
+ * including a flat sorted `allClasses` union.
1771
+ *
1772
+ * `allClasses` always includes every `layoutKeys` display value (`flex`,
1773
+ * `inline-flex`, `grid`, `hidden`, etc.) even when no scanned component uses
1774
+ * them literally. Those classes are only ever assembled at runtime by
1775
+ * `createTailwindPipeline` from boolean props, so they never appear as a
1776
+ * string literal for Tailwind's content scanner (or this manifest) to find
1777
+ * on its own — without this, Tailwind silently drops their generated CSS.
1778
+ */
1779
+ function buildManifest(allTokens) {
1780
+ const components = {};
1781
+ const seen = new Set(layoutKeys);
1782
+ iterate.forEach(allTokens, ([name, tokens]) => {
1783
+ components[name] = tokens;
1784
+ iterate.forEach([
1785
+ ...tokens.base,
1786
+ ...tokens.variantClasses,
1787
+ ...tokens.compoundClasses,
1788
+ ...tokens.tagClasses
1789
+ ], (cls) => {
1790
+ iterate.forEach(cls.split(/\s+/), (part) => {
1791
+ if (part) seen.add(part);
1792
+ });
1793
+ });
1794
+ });
1795
+ return {
1796
+ components,
1797
+ allClasses: [...seen].sort()
1798
+ };
1799
+ }
1800
+ /**
1801
+ * Vite plugin that collects every statically-declared class string from
1802
+ * `createContractComponent` factory calls and writes a design token manifest
1803
+ * to a JSON file on each build.
1804
+ *
1805
+ * The manifest contains per-component class lists and a flat `allClasses`
1806
+ * union that can be used as a Tailwind content source to prevent purging of
1807
+ * variant classes.
1808
+ *
1809
+ * @example
1810
+ * // vite.config.ts
1811
+ * import { designTokensPlugin } from 'praxis-kit/vite-plugin'
1812
+ * export default { plugins: [designTokensPlugin({ outFile: 'praxis-tokens.json' })] }
1813
+ *
1814
+ * @example
1815
+ * // tailwind.config.js
1816
+ * export default { content: ['./src/**', './praxis-tokens.json'] }
1817
+ */
1818
+ function designTokensPlugin(options) {
1819
+ const calleeNames = new Set(options?.calleeNames ?? DEFAULT_CALLEE_NAMES);
1820
+ const outFile = options?.outFile ?? "praxis-tokens.json";
1821
+ const accumulated = /* @__PURE__ */ new Map();
1822
+ let root = process.cwd();
1823
+ return {
1824
+ name: "praxis-kit:design-tokens",
1825
+ configResolved(config) {
1826
+ root = config.root;
1827
+ },
1828
+ buildStart() {
1829
+ accumulated.clear();
1830
+ },
1831
+ transform(code, id) {
1832
+ const ext = id.split(".").pop() ?? "";
1833
+ if (!ALL_EXTS.has(ext)) return null;
1834
+ const source = parseSource(id, code);
1835
+ iterate.forEach(collectFileTokens(source, calleeNames), ([name, tokens]) => {
1836
+ accumulated.set(name, mergeTokens(accumulated.get(name), tokens));
1837
+ });
1838
+ return null;
1839
+ },
1840
+ writeBundle() {
1841
+ if (accumulated.size === 0) return;
1842
+ const manifest = buildManifest(accumulated);
1843
+ writeFileSync(resolve(root, outFile), JSON.stringify(manifest, null, 2));
1844
+ }
1845
+ };
1846
+ }
1847
+ //#endregion
1848
+ //#region ../../plugins/vite/src/index.ts
1849
+ /**
1850
+ * Vite plugin that performs static enforcement.children cardinality checks at
1851
+ * build time for components created with createContractComponent.
1852
+ *
1853
+ * **Single-file scope:** Components defined and used in the same `.tsx` / `.jsx`
1854
+ * file are validated during `transform`. JSX children containing expressions
1855
+ * (`{...}`) are skipped — their count is unknowable at compile time.
1856
+ *
1857
+ * **Cross-file scope:** Components imported from other files are validated in
1858
+ * `buildEnd` once the full constraint registry is populated. Only named imports
1859
+ * whose source file was also transformed by this plugin are checked.
1860
+ *
1861
+ * @example
1862
+ * // vite.config.ts
1863
+ * import { contractPlugin } from 'praxis-kit/vite-plugin'
1864
+ * export default { plugins: [contractPlugin()] }
1865
+ */
1866
+ function contractPlugin(options) {
1867
+ const registry = new ConstraintRegistry();
1868
+ const calleeNames = new Set(options?.calleeNames ?? DEFAULT_CALLEE_NAMES);
1869
+ const severity = options?.severity ?? "warning";
1870
+ return {
1871
+ name: "praxis-kit:contract",
1872
+ async transform(code, id) {
1873
+ const ext = id.split(".").pop() ?? "";
1874
+ if (!JSX_EXTS.has(ext)) return null;
1875
+ const source = parseSource(id, code);
1876
+ const { constraints, importSpecifiers } = collectFileDeclarations(source, calleeNames);
1877
+ registry.registerConstraints(id, constraints);
1878
+ const { diagnostics, usages: allUsages } = analyzeJsxSites(source, constraints, severity);
1879
+ iterate.forEach(diagnostics, ({ col, diagnostic, line, severity }) => {
1880
+ const loc = {
1881
+ file: id,
1882
+ line,
1883
+ column: col
1884
+ };
1885
+ if (severity === "error") this.error({
1886
+ message: diagnostic.message,
1887
+ loc
1888
+ });
1889
+ else this.warn({
1890
+ message: diagnostic.message,
1891
+ loc
1892
+ });
1893
+ });
1894
+ const localNames = new Set(constraints.map((c) => c.name));
1895
+ const importedTagsInUse = new Set(allUsages.filter((u) => !localNames.has(u.tagName) && importSpecifiers.has(u.tagName)).map((u) => u.tagName));
1896
+ if (importedTagsInUse.size > 0) {
1897
+ const resolvedImports = /* @__PURE__ */ new Map();
1898
+ for (const [localName, { specifier }] of importSpecifiers) {
1899
+ if (!importedTagsInUse.has(localName)) continue;
1900
+ const resolved = await this.resolve(specifier, id);
1901
+ if (resolved) resolvedImports.set(localName, resolved.id);
1902
+ }
1903
+ registry.registerImports(id, resolvedImports);
1904
+ iterate.forEach(allUsages, (usage) => {
1905
+ if (importedTagsInUse.has(usage.tagName)) registry.addPendingUsage(id, usage);
1906
+ });
1907
+ }
1908
+ },
1909
+ buildEnd() {
1910
+ iterate.forEach(registry.diagnostics(severity), ({ col, diagnostic, fileId, line, severity }) => {
1911
+ const loc = {
1912
+ file: fileId,
1913
+ line,
1914
+ column: col
1915
+ };
1916
+ if (severity === "error") this.error({
1917
+ message: diagnostic.message,
1918
+ loc
1919
+ });
1920
+ else this.warn({
1921
+ message: diagnostic.message,
1922
+ loc
1923
+ });
1924
+ });
1925
+ }
1926
+ };
1927
+ }
1928
+ /**
1929
+ * Vite plugin that removes dead `styling.compounds` entries from factory calls
1930
+ * at build time, reducing bundle size and eliminating unreachable CVA compound
1931
+ * checks at runtime.
1932
+ *
1933
+ * A compound entry is dead when any of its conditions reference a variant key
1934
+ * that does not exist in `styling.variants`, or a value that is not valid for
1935
+ * that key. Only entries whose conditions are fully static (string/array
1936
+ * literals) are pruned — dynamic conditions are left unchanged.
1937
+ *
1938
+ * Place before `contractPlugin` so the pruned source is what gets analyzed.
1939
+ *
1940
+ * @example
1941
+ * // vite.config.ts
1942
+ * import { compoundPrunePlugin, contractPlugin } from 'praxis-kit/vite-plugin'
1943
+ * export default { plugins: [compoundPrunePlugin(), contractPlugin()] }
1944
+ */
1945
+ function compoundPrunePlugin(options) {
1946
+ const calleeNames = new Set(options?.calleeNames ?? DEFAULT_CALLEE_NAMES);
1947
+ return {
1948
+ name: "praxis-kit:compound-prune",
1949
+ transform(code, id) {
1950
+ const ext = id.split(".").pop() ?? "";
1951
+ if (!ALL_EXTS.has(ext)) return null;
1952
+ const result = pruneDeadCompounds(parseSource(id, code), calleeNames);
1953
+ return result !== null ? { code: result } : null;
1954
+ }
1955
+ };
1956
+ }
1957
+ /**
1958
+ * Vite plugin that pre-computes variant class strings at build time and injects
1959
+ * them as a static `precomputedClasses` map into each factory call's `styling`
1960
+ * object.
1961
+ *
1962
+ * At runtime, `VariantClassResolver` checks this map before calling CVA — a
1963
+ * plain object lookup replaces a CVA invocation + LRU cache write for every
1964
+ * statically-known combination. Only combinations that appear in the map are
1965
+ * accelerated; invalid or dynamic variant values fall through to the existing
1966
+ * compute path unchanged.
1967
+ *
1968
+ * Injection is skipped when:
1969
+ * - `styling.variants` is absent or contains non-literal values
1970
+ * - `styling.compounds` contains non-literal conditions or classes
1971
+ * - The number of valid combinations exceeds 512
1972
+ *
1973
+ * Place after `compoundPrunePlugin` so the injected map reflects the live
1974
+ * compound set.
1975
+ *
1976
+ * @example
1977
+ * // vite.config.ts
1978
+ * import { compoundPrunePlugin, classExtractPlugin, contractPlugin } from 'praxis-kit/vite-plugin'
1979
+ * export default { plugins: [compoundPrunePlugin(), classExtractPlugin(), contractPlugin()] }
1980
+ */
1981
+ function classExtractPlugin(options) {
1982
+ const calleeNames = new Set(options?.calleeNames ?? DEFAULT_CALLEE_NAMES);
1983
+ return {
1984
+ name: "praxis-kit:class-extract",
1985
+ transform(code, id) {
1986
+ const ext = id.split(".").pop() ?? "";
1987
+ if (!ALL_EXTS.has(ext)) return null;
1988
+ const result = injectPrecomputedClasses(parseSource(id, code), calleeNames);
1989
+ return result !== null ? { code: result } : null;
1990
+ }
1991
+ };
1992
+ }
1993
+ /**
1994
+ * Vite plugin that transforms `asChild` JSX usage sites to the render-prop form
1995
+ * at build time, eliminating the Slot/cloneElement/mergeProps runtime path.
1996
+ *
1997
+ * Only transforms sites where the transform is semantically safe:
1998
+ * - Exactly one static child element
1999
+ * - Child has no `className`, `style`, or event handler props
2000
+ *
2001
+ * Complex asChild patterns (conflicting props, dynamic children, Slottable
2002
+ * siblings) are left unchanged and handled by the runtime Slot path.
2003
+ *
2004
+ * Place before `contractPlugin` so cardinality analysis sees the transformed
2005
+ * source.
2006
+ *
2007
+ * @example
2008
+ * // vite.config.ts
2009
+ * import { slotTransformPlugin, contractPlugin } from 'praxis-kit/vite-plugin'
2010
+ * export default { plugins: [slotTransformPlugin(), contractPlugin()] }
2011
+ */
2012
+ function slotTransformPlugin() {
2013
+ return {
2014
+ name: "praxis-kit:slot-transform",
2015
+ transform(code, id) {
2016
+ const ext = id.split(".").pop() ?? "";
2017
+ if (!JSX_EXTS.has(ext)) return null;
2018
+ const result = transformAsChild(parseSource(id, code));
2019
+ return result !== null ? { code: result } : null;
2020
+ }
2021
+ };
2022
+ }
2023
+ /**
2024
+ * Vite plugin that replaces polymorphic component usage sites with direct
2025
+ * element creation at build time, eliminating the runtime render pipeline for
2026
+ * statically-analyzable usages.
2027
+ *
2028
+ * **Requires classExtractPlugin to run first** so that `precomputedClasses` is
2029
+ * present in the factory call before this plugin reads it. Place after
2030
+ * `classExtractPlugin` in the plugins array.
2031
+ *
2032
+ * A usage site is inlined when:
2033
+ * - The component is defined in the same file or imported from a file already
2034
+ * transformed by this plugin, with `precomputedClasses` injected
2035
+ * - No `as`, `asChild`, `render`, or spread attributes at the site
2036
+ * - All variant props are static string literals
2037
+ * - `className` is absent or a static string literal
2038
+ * - The factory config has no `defaults` or `enforcement`
2039
+ *
2040
+ * Cross-file inlining degrades gracefully when the definition file has not yet
2041
+ * been transformed (dev mode ordering, barrel re-exports, aliased imports).
2042
+ *
2043
+ * @example
2044
+ * // vite.config.ts
2045
+ * import { classExtractPlugin, staticCompositionPlugin } from 'praxis-kit/vite-plugin'
2046
+ * export default { plugins: [classExtractPlugin(), staticCompositionPlugin()] }
2047
+ */
2048
+ function staticCompositionPlugin(options) {
2049
+ const calleeNames = new Set(options?.calleeNames ?? DEFAULT_CALLEE_NAMES);
2050
+ const registry = /* @__PURE__ */ new Map();
2051
+ return {
2052
+ name: "praxis-kit:static-compose",
2053
+ buildStart() {
2054
+ registry.clear();
2055
+ },
2056
+ async transform(code, id) {
2057
+ const ext = id.split(".").pop() ?? "";
2058
+ if (!JSX_EXTS.has(ext)) return null;
2059
+ const source = parseSource(id, code);
2060
+ const sameFile = extractStaticComponents(source, calleeNames);
2061
+ if (sameFile.size > 0) registry.set(id, sameFile);
2062
+ const importedComponents = /* @__PURE__ */ new Map();
2063
+ const importSpecifiers = extractImportSpecifiers(source);
2064
+ for (const [localName, { importedName, specifier }] of importSpecifiers) {
2065
+ const resolved = await this.resolve(specifier, id);
2066
+ if (!resolved) continue;
2067
+ const entry = registry.get(resolved.id);
2068
+ if (!entry) continue;
2069
+ const component = entry.get(importedName);
2070
+ if (component) importedComponents.set(localName, component);
2071
+ }
2072
+ const result = composeStatically(source, calleeNames, importedComponents);
2073
+ return result !== null ? { code: result } : null;
2074
+ }
2075
+ };
2076
+ }
2077
+ /**
2078
+ * Convenience plugin bundle that applies all three build-time rendering
2079
+ * optimisations in the correct dependency order:
2080
+ *
2081
+ * 1. `slotTransformPlugin` — rewrites `asChild` → render-prop form,
2082
+ * eliminating `cloneElement` for static sites
2083
+ * 2. `classExtractPlugin` — injects `precomputedClasses` into factory
2084
+ * calls for O(1) variant class resolution
2085
+ * 3. `staticCompositionPlugin` — inlines same-file static usages into direct
2086
+ * element creation, bypassing the runtime
2087
+ * pipeline entirely
2088
+ *
2089
+ * Place before `contractPlugin` so cardinality analysis sees the transformed
2090
+ * source. Especially effective for SSR builds where each component renders
2091
+ * exactly once per request and eliminates per-render pipeline overhead.
2092
+ *
2093
+ * @example
2094
+ * // vite.config.ts
2095
+ * import { ssrOptimizePlugin, contractPlugin } from 'praxis-kit/vite-plugin'
2096
+ * export default { plugins: [ssrOptimizePlugin(), contractPlugin()] }
2097
+ */
2098
+ function ssrOptimizePlugin(options) {
2099
+ return [
2100
+ slotTransformPlugin(),
2101
+ classExtractPlugin(options),
2102
+ staticCompositionPlugin(options)
2103
+ ];
2104
+ }
2105
+ //#endregion
2106
+ export { classExtractPlugin, compoundPrunePlugin, contractPlugin, designTokensPlugin, slotTransformPlugin, ssrOptimizePlugin, staticCompositionPlugin };