@lovrozagar/flare 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 (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/package.json +146 -0
  4. package/scripts/download-fonts.ts +136 -0
  5. package/scripts/populate-fonts.ts +721 -0
  6. package/src/await.ts +2 -0
  7. package/src/boundaries/index.ts +99 -0
  8. package/src/broadcast/channel.ts +65 -0
  9. package/src/broadcast/context.ts +12 -0
  10. package/src/broadcast/index.ts +75 -0
  11. package/src/broadcast/provider.tsx +7 -0
  12. package/src/build-location.ts +2 -0
  13. package/src/caches/index.ts +384 -0
  14. package/src/client/index.ts +75 -0
  15. package/src/client.tsx +2 -0
  16. package/src/codegen/index.ts +41 -0
  17. package/src/components/await.tsx +140 -0
  18. package/src/components/dev-error-overlay.tsx +154 -0
  19. package/src/components/direction-script.tsx +22 -0
  20. package/src/components/index.ts +150 -0
  21. package/src/components/locale-script.tsx +26 -0
  22. package/src/components/reset-css.tsx +14 -0
  23. package/src/components/ssr-context.tsx +37 -0
  24. package/src/components/theme-script.tsx +22 -0
  25. package/src/components/view-transition-css.tsx +21 -0
  26. package/src/config/index.ts +84 -0
  27. package/src/create-client-only-fn.ts +1 -0
  28. package/src/create-isomorphic-fn.ts +2 -0
  29. package/src/create-layout.ts +1 -0
  30. package/src/create-page.ts +1 -0
  31. package/src/create-path-segment.ts +1 -0
  32. package/src/create-query-client-getter.ts +2 -0
  33. package/src/create-root-layout.ts +1 -0
  34. package/src/create-router.ts +8 -0
  35. package/src/create-server-only-fn.ts +1 -0
  36. package/src/dedupe/index.ts +151 -0
  37. package/src/defer/index.ts +82 -0
  38. package/src/direction/index.tsx +163 -0
  39. package/src/direction.ts +3 -0
  40. package/src/duration/index.ts +18 -0
  41. package/src/env-fn/index.ts +101 -0
  42. package/src/env-fn.ts +2 -0
  43. package/src/errors/index.ts +166 -0
  44. package/src/fonts/abeezee.ts +50 -0
  45. package/src/fonts/abel.ts +26 -0
  46. package/src/fonts/abril-fatface.ts +34 -0
  47. package/src/fonts/acme.ts +26 -0
  48. package/src/fonts/advent-pro.ts +92 -0
  49. package/src/fonts/alegreya-sans.ts +228 -0
  50. package/src/fonts/alegreya.ts +228 -0
  51. package/src/fonts/alex-brush.ts +42 -0
  52. package/src/fonts/alfa-slab-one.ts +42 -0
  53. package/src/fonts/alice.ts +48 -0
  54. package/src/fonts/amatic-sc.ts +94 -0
  55. package/src/fonts/amiri.ts +114 -0
  56. package/src/fonts/antic-slab.ts +26 -0
  57. package/src/fonts/anton.ts +42 -0
  58. package/src/fonts/archivo-black.ts +34 -0
  59. package/src/fonts/archivo-narrow.ts +114 -0
  60. package/src/fonts/archivo.ts +66 -0
  61. package/src/fonts/arimo.ts +256 -0
  62. package/src/fonts/arvo.ts +50 -0
  63. package/src/fonts/asap.ts +66 -0
  64. package/src/fonts/assistant.ts +64 -0
  65. package/src/fonts/baloo-2.ts +82 -0
  66. package/src/fonts/barlow-condensed.ts +114 -0
  67. package/src/fonts/barlow-semi-condensed.ts +114 -0
  68. package/src/fonts/barlow.ts +114 -0
  69. package/src/fonts/be-vietnam-pro.ts +114 -0
  70. package/src/fonts/bebas-neue.ts +34 -0
  71. package/src/fonts/besley.ts +82 -0
  72. package/src/fonts/biorhyme.ts +50 -0
  73. package/src/fonts/bitter.ts +94 -0
  74. package/src/fonts/black-ops-one.ts +49 -0
  75. package/src/fonts/bree-serif.ts +34 -0
  76. package/src/fonts/bricolage-grotesque.ts +66 -0
  77. package/src/fonts/cabin.ts +114 -0
  78. package/src/fonts/cairo.ts +66 -0
  79. package/src/fonts/cardo.ts +193 -0
  80. package/src/fonts/catamaran.ts +41 -0
  81. package/src/fonts/caveat.ts +78 -0
  82. package/src/fonts/chakra-petch.ts +142 -0
  83. package/src/fonts/chivo.ts +66 -0
  84. package/src/fonts/cinzel.ts +50 -0
  85. package/src/fonts/comfortaa.ts +109 -0
  86. package/src/fonts/commissioner.ts +64 -0
  87. package/src/fonts/component.tsx +27 -0
  88. package/src/fonts/cormorant-garamond.ts +171 -0
  89. package/src/fonts/cormorant.ts +170 -0
  90. package/src/fonts/courgette.ts +34 -0
  91. package/src/fonts/courier-prime.ts +82 -0
  92. package/src/fonts/create-font.ts +47 -0
  93. package/src/fonts/create-registry-font.ts +40 -0
  94. package/src/fonts/crete-round.ts +50 -0
  95. package/src/fonts/crimson-text.ts +114 -0
  96. package/src/fonts/css.ts +86 -0
  97. package/src/fonts/dancing-script.ts +66 -0
  98. package/src/fonts/dm-mono.ts +50 -0
  99. package/src/fonts/dm-sans.ts +50 -0
  100. package/src/fonts/dm-serif-display.ts +50 -0
  101. package/src/fonts/domine.ts +50 -0
  102. package/src/fonts/dosis.ts +66 -0
  103. package/src/fonts/eb-garamond.ts +228 -0
  104. package/src/fonts/eczar.ts +94 -0
  105. package/src/fonts/encode-sans.ts +42 -0
  106. package/src/fonts/epilogue.ts +66 -0
  107. package/src/fonts/exo-2.ts +94 -0
  108. package/src/fonts/exo.ts +66 -0
  109. package/src/fonts/figtree.ts +82 -0
  110. package/src/fonts/fira-code.ts +121 -0
  111. package/src/fonts/fira-sans-condensed.ts +228 -0
  112. package/src/fonts/fira-sans.ts +228 -0
  113. package/src/fonts/fjalla-one.ts +49 -0
  114. package/src/fonts/font-urls.gen.json +1962 -0
  115. package/src/fonts/fraunces.ts +66 -0
  116. package/src/fonts/fredoka.ts +64 -0
  117. package/src/fonts/gelasio.ts +114 -0
  118. package/src/fonts/gloria-hallelujah.ts +34 -0
  119. package/src/fonts/gothic-a1.ts +124 -0
  120. package/src/fonts/great-vibes.ts +64 -0
  121. package/src/fonts/gudea.ts +66 -0
  122. package/src/fonts/heebo.ts +57 -0
  123. package/src/fonts/hind-siliguri.ts +66 -0
  124. package/src/fonts/hind.ts +66 -0
  125. package/src/fonts/ibm-plex-mono.ts +172 -0
  126. package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
  127. package/src/fonts/ibm-plex-sans.ts +199 -0
  128. package/src/fonts/ibm-plex-serif.ts +171 -0
  129. package/src/fonts/inconsolata.ts +66 -0
  130. package/src/fonts/index.ts +4 -0
  131. package/src/fonts/indie-flower.ts +34 -0
  132. package/src/fonts/instrument-sans.ts +82 -0
  133. package/src/fonts/instrument-serif.ts +50 -0
  134. package/src/fonts/inter-display.ts +52 -0
  135. package/src/fonts/inter-tight.ts +124 -0
  136. package/src/fonts/inter.ts +123 -0
  137. package/src/fonts/jetbrains-mono.ts +199 -0
  138. package/src/fonts/josefin-sans.ts +114 -0
  139. package/src/fonts/josefin-slab.ts +50 -0
  140. package/src/fonts/jost.ts +64 -0
  141. package/src/fonts/kalam.ts +66 -0
  142. package/src/fonts/kanit.ts +142 -0
  143. package/src/fonts/karla.ts +82 -0
  144. package/src/fonts/khand.ts +66 -0
  145. package/src/fonts/lato.ts +82 -0
  146. package/src/fonts/league-spartan.ts +42 -0
  147. package/src/fonts/lexend-deca.ts +42 -0
  148. package/src/fonts/lexend.ts +42 -0
  149. package/src/fonts/libre-baskerville.ts +82 -0
  150. package/src/fonts/libre-franklin.ts +95 -0
  151. package/src/fonts/lilita-one.ts +34 -0
  152. package/src/fonts/lobster-two.ts +50 -0
  153. package/src/fonts/lobster.ts +56 -0
  154. package/src/fonts/lora.ts +235 -0
  155. package/src/fonts/m-plus-rounded-1c.ts +138 -0
  156. package/src/fonts/manrope.ts +109 -0
  157. package/src/fonts/maven-pro.ts +66 -0
  158. package/src/fonts/merriweather-sans.ts +142 -0
  159. package/src/fonts/merriweather.ts +171 -0
  160. package/src/fonts/montserrat-alternates.ts +171 -0
  161. package/src/fonts/montserrat.ts +94 -0
  162. package/src/fonts/mukta.ts +66 -0
  163. package/src/fonts/mulish.ts +170 -0
  164. package/src/fonts/nanum-gothic.ts +34 -0
  165. package/src/fonts/nanum-myeongjo.ts +34 -0
  166. package/src/fonts/neuton.ts +66 -0
  167. package/src/fonts/newsreader.ts +114 -0
  168. package/src/fonts/noticia-text.ts +114 -0
  169. package/src/fonts/noto-sans-arabic.ts +58 -0
  170. package/src/fonts/noto-sans-hk.ts +49 -0
  171. package/src/fonts/noto-sans-jp.ts +49 -0
  172. package/src/fonts/noto-sans-kr.ts +49 -0
  173. package/src/fonts/noto-sans-sc.ts +49 -0
  174. package/src/fonts/noto-sans-tc.ts +49 -0
  175. package/src/fonts/noto-sans.ts +140 -0
  176. package/src/fonts/noto-serif-jp.ts +80 -0
  177. package/src/fonts/noto-serif.ts +140 -0
  178. package/src/fonts/nunito-sans.ts +170 -0
  179. package/src/fonts/nunito.ts +170 -0
  180. package/src/fonts/old-standard-tt.ts +133 -0
  181. package/src/fonts/onest.ts +48 -0
  182. package/src/fonts/open-sans.ts +340 -0
  183. package/src/fonts/orbitron.ts +34 -0
  184. package/src/fonts/oswald.ts +94 -0
  185. package/src/fonts/outfit.ts +34 -0
  186. package/src/fonts/overpass.ts +94 -0
  187. package/src/fonts/oxygen.ts +50 -0
  188. package/src/fonts/pacifico.ts +56 -0
  189. package/src/fonts/passion-one.ts +50 -0
  190. package/src/fonts/pathway-gothic-one.ts +34 -0
  191. package/src/fonts/permanent-marker.ts +26 -0
  192. package/src/fonts/philosopher.ts +172 -0
  193. package/src/fonts/play.ts +109 -0
  194. package/src/fonts/playfair-display.ts +142 -0
  195. package/src/fonts/plus-jakarta-sans.ts +142 -0
  196. package/src/fonts/poppins.ts +114 -0
  197. package/src/fonts/prompt.ts +142 -0
  198. package/src/fonts/pt-sans-narrow.ts +78 -0
  199. package/src/fonts/pt-sans.ts +138 -0
  200. package/src/fonts/pt-serif.ts +138 -0
  201. package/src/fonts/public-sans.ts +66 -0
  202. package/src/fonts/questrial.ts +42 -0
  203. package/src/fonts/quicksand.ts +66 -0
  204. package/src/fonts/rajdhani.ts +66 -0
  205. package/src/fonts/raleway.ts +94 -0
  206. package/src/fonts/recursive.ts +80 -0
  207. package/src/fonts/red-hat-display.ts +82 -0
  208. package/src/fonts/red-rose.ts +66 -0
  209. package/src/fonts/registry.gen.ts +173 -0
  210. package/src/fonts/righteous.ts +34 -0
  211. package/src/fonts/roboto-condensed.ts +124 -0
  212. package/src/fonts/roboto-flex.ts +64 -0
  213. package/src/fonts/roboto-mono.ts +199 -0
  214. package/src/fonts/roboto-slab.ts +72 -0
  215. package/src/fonts/roboto.ts +156 -0
  216. package/src/fonts/rokkitt.ts +66 -0
  217. package/src/fonts/rubik.ts +199 -0
  218. package/src/fonts/sacramento.ts +34 -0
  219. package/src/fonts/saira.ts +66 -0
  220. package/src/fonts/sarabun.ts +142 -0
  221. package/src/fonts/satisfy.ts +26 -0
  222. package/src/fonts/sawarabi-mincho.ts +41 -0
  223. package/src/fonts/shadows-into-light.ts +34 -0
  224. package/src/fonts/signika-negative.ts +66 -0
  225. package/src/fonts/signika.ts +66 -0
  226. package/src/fonts/slabo-27px.ts +34 -0
  227. package/src/fonts/sora.ts +50 -0
  228. package/src/fonts/source-code-pro.ts +228 -0
  229. package/src/fonts/source-sans-3.ts +228 -0
  230. package/src/fonts/source-serif-4.ts +199 -0
  231. package/src/fonts/space-grotesk.ts +66 -0
  232. package/src/fonts/space-mono.ts +114 -0
  233. package/src/fonts/spectral.ts +170 -0
  234. package/src/fonts/tajawal.ts +50 -0
  235. package/src/fonts/teko.ts +66 -0
  236. package/src/fonts/tenor-sans.ts +41 -0
  237. package/src/fonts/tinos.ts +256 -0
  238. package/src/fonts/titillium-web.ts +82 -0
  239. package/src/fonts/types.ts +43 -0
  240. package/src/fonts/ubuntu-condensed.ts +63 -0
  241. package/src/fonts/ubuntu-mono.ts +195 -0
  242. package/src/fonts/ubuntu.ts +195 -0
  243. package/src/fonts/unbounded.ts +94 -0
  244. package/src/fonts/unicode-ranges.gen.ts +28 -0
  245. package/src/fonts/urbanist.ts +50 -0
  246. package/src/fonts/varela-round.ts +49 -0
  247. package/src/fonts/vollkorn.ts +199 -0
  248. package/src/fonts/work-sans.ts +66 -0
  249. package/src/fonts/yanone-kaffeesatz.ts +128 -0
  250. package/src/fonts/yantramanav.ts +66 -0
  251. package/src/fonts/yellowtail.ts +34 -0
  252. package/src/fonts/young-serif.ts +34 -0
  253. package/src/fonts/zen-kaku-gothic-new.ts +64 -0
  254. package/src/fonts/zilla-slab.ts +82 -0
  255. package/src/form/index.tsx +205 -0
  256. package/src/generators/index.ts +1589 -0
  257. package/src/head-client/index.ts +649 -0
  258. package/src/history/index.ts +210 -0
  259. package/src/hooks.ts +61 -0
  260. package/src/hydrate/index.tsx +381 -0
  261. package/src/hydration/index.ts +187 -0
  262. package/src/i18n/index.ts +250 -0
  263. package/src/i18n/jsx.tsx +42 -0
  264. package/src/i18n/message-format.ts +174 -0
  265. package/src/image/index.tsx +473 -0
  266. package/src/image.ts +12 -0
  267. package/src/intercept-outlet/index.tsx +18 -0
  268. package/src/intercept-outlet.ts +2 -0
  269. package/src/internal/index.ts +143 -0
  270. package/src/internal/once-idle.ts +24 -0
  271. package/src/internal.ts +12 -0
  272. package/src/lazy/index.tsx +205 -0
  273. package/src/link/index.tsx +367 -0
  274. package/src/loader-pipeline/index.ts +639 -0
  275. package/src/locale/index.tsx +137 -0
  276. package/src/locale.ts +3 -0
  277. package/src/logger.ts +22 -0
  278. package/src/middleware/builtins/api-proxy.ts +64 -0
  279. package/src/middleware/builtins/cdn-proxy.ts +58 -0
  280. package/src/middleware/builtins/i18n.ts +256 -0
  281. package/src/middleware/builtins/keepalive.ts +24 -0
  282. package/src/middleware/builtins/markdown-negotiation.ts +141 -0
  283. package/src/middleware/builtins/static-assets.ts +32 -0
  284. package/src/middleware/index.ts +223 -0
  285. package/src/module-graph/index.ts +181 -0
  286. package/src/mount/index.ts +146 -0
  287. package/src/navigate.ts +1 -0
  288. package/src/navigation/index.ts +1278 -0
  289. package/src/navigation/types.ts +59 -0
  290. package/src/navigation-progress/index.tsx +247 -0
  291. package/src/navigation-progress.ts +1 -0
  292. package/src/ndjson-client/index.ts +376 -0
  293. package/src/ndjson-server/index.ts +284 -0
  294. package/src/outlet/index.tsx +633 -0
  295. package/src/outlet/types.ts +243 -0
  296. package/src/plugins/css-transform.ts +70 -0
  297. package/src/plugins/dev-cdn-cache.ts +454 -0
  298. package/src/plugins/dev-dashboard/client.tsx +2603 -0
  299. package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
  300. package/src/plugins/dev-dashboard/plugin.ts +404 -0
  301. package/src/plugins/dev-prerender.ts +168 -0
  302. package/src/plugins/dev-server.ts +327 -0
  303. package/src/plugins/generate-watch.ts +32 -0
  304. package/src/plugins/image-plugin.ts +171 -0
  305. package/src/plugins/index.ts +445 -0
  306. package/src/plugins/options.ts +37 -0
  307. package/src/plugins/parse-utils.ts +59 -0
  308. package/src/plugins/prerender-plugin.ts +144 -0
  309. package/src/plugins/purge.ts +242 -0
  310. package/src/plugins/server-fn.ts +641 -0
  311. package/src/plugins/service-worker.ts +225 -0
  312. package/src/plugins/solid-jsx.d.ts +12 -0
  313. package/src/plugins/sx-ast/atomic.ts +166 -0
  314. package/src/plugins/sx-ast/evaluator.ts +235 -0
  315. package/src/plugins/sx-ast/index.ts +372 -0
  316. package/src/plugins/sx-ast/parser.ts +22 -0
  317. package/src/plugins/sx-ast/rewrite.ts +763 -0
  318. package/src/plugins/sx.d.ts +14 -0
  319. package/src/plugins/tw-compile.ts +240 -0
  320. package/src/plugins/types.ts +41 -0
  321. package/src/plugins/virtual.d.ts +110 -0
  322. package/src/plugins/virtual.ts +184 -0
  323. package/src/preload/index.ts +77 -0
  324. package/src/prerender/index.ts +431 -0
  325. package/src/query-client/index.tsx +168 -0
  326. package/src/registry/index.ts +88 -0
  327. package/src/reset-css.ts +1 -0
  328. package/src/revalidation/index.ts +84 -0
  329. package/src/rewrite/index.ts +83 -0
  330. package/src/route-builder/create-layout.ts +655 -0
  331. package/src/route-builder/create-page.ts +806 -0
  332. package/src/route-builder/create-path-segment.ts +34 -0
  333. package/src/route-builder/create-root-layout.ts +608 -0
  334. package/src/route-builder/index.ts +49 -0
  335. package/src/route-builder/register.ts +242 -0
  336. package/src/route-builder/types.ts +294 -0
  337. package/src/router-config/index.ts +91 -0
  338. package/src/router-primitives/index.ts +25 -0
  339. package/src/router-primitives/location.ts +29 -0
  340. package/src/router-primitives/match-id.ts +111 -0
  341. package/src/router-primitives/paths.ts +109 -0
  342. package/src/router-primitives/tree.ts +243 -0
  343. package/src/router-primitives/types.ts +74 -0
  344. package/src/router.ts +31 -0
  345. package/src/search-engine/bing.ts +32 -0
  346. package/src/search-engine/google-jwt.ts +95 -0
  347. package/src/search-engine/google.ts +139 -0
  348. package/src/search-engine/index-now.ts +64 -0
  349. package/src/search-engine/index.ts +12 -0
  350. package/src/security/index.ts +230 -0
  351. package/src/server/index.ts +286 -0
  352. package/src/server-context/browser.ts +113 -0
  353. package/src/server-context/index.ts +217 -0
  354. package/src/server-fn/index.ts +710 -0
  355. package/src/server-fn-query.ts +169 -0
  356. package/src/server-handler/etag.ts +51 -0
  357. package/src/server-handler/headers.ts +5 -0
  358. package/src/server-handler/index.ts +1664 -0
  359. package/src/server-handler/types.ts +24 -0
  360. package/src/server-handler/validate-static-params.ts +130 -0
  361. package/src/server.ts +2 -0
  362. package/src/service-worker/template.ts +153 -0
  363. package/src/service-worker-hook/index.ts +70 -0
  364. package/src/sitemap/index.ts +231 -0
  365. package/src/ssr/critical-css.ts +150 -0
  366. package/src/ssr/head-prefix.ts +65 -0
  367. package/src/ssr/head.ts +259 -0
  368. package/src/ssr/index.tsx +879 -0
  369. package/src/state-parser/index.ts +266 -0
  370. package/src/store/filesystem.ts +131 -0
  371. package/src/store/index.ts +29 -0
  372. package/src/styles/index.ts +1200 -0
  373. package/src/styles/sx-types.ts +11 -0
  374. package/src/testing/index.ts +251 -0
  375. package/src/testing.ts +2 -0
  376. package/src/theme/index.tsx +232 -0
  377. package/src/theme.ts +3 -0
  378. package/src/tracing/index.ts +4 -0
  379. package/src/tracing/noop.ts +13 -0
  380. package/src/tracing/otel.ts +37 -0
  381. package/src/tracing/timing.ts +34 -0
  382. package/src/tracing/types.ts +9 -0
  383. package/src/url/index.ts +140 -0
  384. package/src/use-router.ts +11 -0
  385. package/src/use-suspense-query.ts +2 -0
  386. package/src/validation/index.ts +57 -0
  387. package/src/view-transition-css.ts +1 -0
@@ -0,0 +1,763 @@
1
+ import MagicString from "magic-string";
2
+ import type { Expression, JSXAttribute, JSXOpeningElement, Node, ObjectExpression, Program } from "oxc-parser";
3
+ import { parseSource } from "./parser.ts";
4
+ import { evaluateSxObject } from "./evaluator.ts";
5
+ import { emitAtomic } from "./atomic.ts";
6
+
7
+ export interface RewriteCtx {
8
+ sourcePath: string;
9
+ layer: "sx" | "app";
10
+ mode: "dev" | "prod";
11
+ cssEmit: (rule: string) => void;
12
+ /**
13
+ * Per-token Tailwind compile fn. Returns CSS body text for known utilities,
14
+ * null for unknown tokens. Omit to disable Tailwind compilation entirely.
15
+ */
16
+ twCompile?: (token: string) => string | null;
17
+ }
18
+
19
+ export interface RewriteResult {
20
+ code: string;
21
+ map: null;
22
+ emittedClasses: Set<string>;
23
+ }
24
+
25
+ /* Layer arg embedded at transform time based on enclosing module's layer. */
26
+ function layerArg(layer: "sx" | "app"): string {
27
+ return layer === "sx" ? '"user.lib"' : '"user.app"';
28
+ }
29
+
30
+ type FlareImport = "compileSx" | "compileCss" | "cn";
31
+
32
+ function srcOf(source: string, node: { start: number; end: number }): string {
33
+ return source.slice(node.start, node.end);
34
+ }
35
+
36
+ function isObjectExpression(n: unknown): n is ObjectExpression {
37
+ return typeof n === "object" && n !== null && (n as { type: string }).type === "ObjectExpression";
38
+ }
39
+
40
+ function isStringLiteral(n: unknown): n is { type: "Literal"; value: string; start: number; end: number } {
41
+ return (
42
+ typeof n === "object" &&
43
+ n !== null &&
44
+ (n as { type: string }).type === "Literal" &&
45
+ typeof (n as { value: unknown }).value === "string"
46
+ );
47
+ }
48
+
49
+ function isJSXExpressionContainer(
50
+ n: unknown,
51
+ ): n is { type: "JSXExpressionContainer"; expression: Expression; start: number; end: number } {
52
+ return typeof n === "object" && n !== null && (n as { type: string }).type === "JSXExpressionContainer";
53
+ }
54
+
55
+ function isCallExpression(n: unknown): n is {
56
+ type: "CallExpression";
57
+ callee: { type: string; name?: string };
58
+ arguments: Expression[];
59
+ start: number;
60
+ end: number;
61
+ } {
62
+ return typeof n === "object" && n !== null && (n as { type: string }).type === "CallExpression";
63
+ }
64
+
65
+ function isArrayExpression(
66
+ n: unknown,
67
+ ): n is { type: "ArrayExpression"; elements: Array<Expression | null>; start: number; end: number } {
68
+ return typeof n === "object" && n !== null && (n as { type: string }).type === "ArrayExpression";
69
+ }
70
+
71
+ function isIdentifier(n: unknown): n is { type: "Identifier"; name: string; start: number; end: number } {
72
+ return typeof n === "object" && n !== null && (n as { type: string }).type === "Identifier";
73
+ }
74
+
75
+ function tagName(opening: JSXOpeningElement): string {
76
+ const name = opening.name as { type: string; name?: string };
77
+ if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
78
+ return "Component";
79
+ }
80
+
81
+ function isDOMElement(tag: string): boolean {
82
+ return tag.length > 0 && tag[0] !== "" && tag[0] === tag[0].toLowerCase();
83
+ }
84
+
85
+ function isCnCall(expr: Expression): boolean {
86
+ if (!isCallExpression(expr)) return false;
87
+ return expr.callee.type === "Identifier" && expr.callee.name === "cn";
88
+ }
89
+
90
+ function isTemplateLiteral(n: unknown): n is {
91
+ type: "TemplateLiteral";
92
+ quasis: Array<{ type: "TemplateElement"; value: { cooked: string | null }; tail: boolean }>;
93
+ expressions: Expression[];
94
+ } {
95
+ return typeof n === "object" && n !== null && (n as { type: string }).type === "TemplateLiteral";
96
+ }
97
+
98
+ /* Matches group/peer marker tokens — must remain as literal class names on the DOM element. */
99
+ const MARKER_TOKEN_RE = /^(?:group|peer)(?:\/[\w-]+)?$/;
100
+
101
+ function cssEscapeClass(cls: string): string {
102
+ return cls.replace(/([^a-zA-Z0-9_-])/g, "\\$1");
103
+ }
104
+
105
+ /**
106
+ * Walk an expression and collect every string literal that appears as a class token.
107
+ * Handles: plain string literals, array elements (recursive), LogicalExpression right-arms,
108
+ * ternary branches, cn() arguments, TemplateLiteral quasis and string-literal expressions.
109
+ */
110
+ function collectClassLiterals(expr: unknown): string[] {
111
+ if (isStringLiteral(expr)) return [expr.value];
112
+
113
+ if (isTemplateLiteral(expr)) {
114
+ const tokens: string[] = [];
115
+ /* Static quasis */
116
+ for (const quasi of expr.quasis) {
117
+ const cooked = quasi.value.cooked ?? "";
118
+ for (const tok of cooked.split(/\s+/).filter(Boolean)) tokens.push(tok);
119
+ }
120
+ /* String-literal expressions inside template */
121
+ for (const e of expr.expressions) {
122
+ tokens.push(...collectClassLiterals(e));
123
+ }
124
+ return tokens;
125
+ }
126
+
127
+ if (isArrayExpression(expr)) {
128
+ const tokens: string[] = [];
129
+ for (const el of expr.elements) {
130
+ if (el === null) continue;
131
+ tokens.push(...collectClassLiterals(el));
132
+ }
133
+ return tokens;
134
+ }
135
+
136
+ if (isCallExpression(expr)) {
137
+ /* Scan inside cn(...) and any other call's arguments */
138
+ const tokens: string[] = [];
139
+ for (const arg of expr.arguments) {
140
+ tokens.push(...collectClassLiterals(arg));
141
+ }
142
+ return tokens;
143
+ }
144
+
145
+ const node = expr as {
146
+ type?: string;
147
+ left?: unknown;
148
+ right?: unknown;
149
+ consequent?: unknown;
150
+ alternate?: unknown;
151
+ } | null;
152
+ if (!node || typeof node.type !== "string") return [];
153
+
154
+ if (node.type === "LogicalExpression") {
155
+ /* Only scan the right arm — left arm is never a class string */
156
+ return collectClassLiterals(node.right);
157
+ }
158
+
159
+ if (node.type === "ConditionalExpression") {
160
+ return [...collectClassLiterals(node.consequent), ...collectClassLiterals(node.alternate)];
161
+ }
162
+
163
+ return [];
164
+ }
165
+
166
+ /**
167
+ * Scan all string literals in a class attribute value expression,
168
+ * compile recognized Tailwind utility tokens, and emit CSS rules via ctx.cssEmit.
169
+ * Marker tokens (group, peer) are skipped — they must remain as literal class names.
170
+ */
171
+ function compileTwFromExpr(expr: unknown, ctx: RewriteCtx): void {
172
+ if (!ctx.twCompile) return;
173
+ const tokens = collectClassLiterals(expr);
174
+ for (const raw of tokens) {
175
+ /* Each raw value may be a space-separated multi-token string */
176
+ for (const token of raw.split(/\s+/).filter(Boolean)) {
177
+ if (MARKER_TOKEN_RE.test(token)) continue;
178
+ const body = ctx.twCompile(token);
179
+ if (!body) continue;
180
+ ctx.cssEmit(buildTwRule(token, body));
181
+ }
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Compile Tailwind tokens from a plain string value (e.g. class="bg-blue-500 p-4").
187
+ */
188
+ function compileTwFromString(value: string, ctx: RewriteCtx): void {
189
+ if (!ctx.twCompile) return;
190
+ for (const token of value.split(/\s+/).filter(Boolean)) {
191
+ if (MARKER_TOKEN_RE.test(token)) continue;
192
+ const body = ctx.twCompile(token);
193
+ if (!body) continue;
194
+ ctx.cssEmit(buildTwRule(token, body));
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Build a CSS rule string from a Tailwind token and the body returned by the compiler.
200
+ *
201
+ * Three body shapes from the compiler:
202
+ * - plain declarations: `"background-color: red"` → `.token { decls }`
203
+ * - pseudo variant: `"&:hover { decls }"` → `.token:hover { decls }` (& replaced)
204
+ * - at-rule variant: `"@media (...) { decls }"` → `@media (...) { .token { decls } }`
205
+ */
206
+ function buildTwRule(token: string, body: string): string {
207
+ const escaped = cssEscapeClass(token);
208
+ const trimmed = body.trimStart();
209
+ if (trimmed.startsWith("&")) {
210
+ /* Replace & with the class selector */
211
+ return body.replace(/&/g, `.${escaped}`);
212
+ }
213
+ if (trimmed.startsWith("@")) {
214
+ /* At-rule: inject .selector { decls } inside the at-block */
215
+ const braceIdx = trimmed.indexOf("{");
216
+ if (braceIdx !== -1) {
217
+ const atPart = trimmed.slice(0, braceIdx).trim();
218
+ /* Extract content between outermost braces */
219
+ let depth = 0;
220
+ let innerStart = -1;
221
+ let innerEnd = -1;
222
+ for (let i = braceIdx; i < trimmed.length; i++) {
223
+ if (trimmed[i] === "{") {
224
+ if (depth++ === 0) innerStart = i + 1;
225
+ } else if (trimmed[i] === "}") {
226
+ if (--depth === 0) {
227
+ innerEnd = i;
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ const inner = innerStart !== -1 && innerEnd !== -1 ? trimmed.slice(innerStart, innerEnd).trim() : "";
233
+ return `${atPart} { .${escaped} { ${inner} } }`;
234
+ }
235
+ }
236
+ return `.${escaped} { ${body} }`;
237
+ }
238
+
239
+ function allStaticStrings(elements: Array<Expression | null>): string[] | null {
240
+ const result: string[] = [];
241
+ for (const el of elements) {
242
+ if (el === null) continue;
243
+ if (!isStringLiteral(el)) return null;
244
+ result.push(el.value);
245
+ }
246
+ return result;
247
+ }
248
+
249
+ /*
250
+ * Resolve a mixed-type class array to either a literal string or a template expr string.
251
+ * LogicalExpression `cond && "str"` → ternary guard in template.
252
+ */
253
+ function resolveClassArray(
254
+ source: string,
255
+ elements: Array<Expression | null>,
256
+ ): { kind: "literal"; value: string } | { kind: "template"; expr: string } {
257
+ const allStatic = allStaticStrings(elements);
258
+ if (allStatic !== null) {
259
+ return { kind: "literal", value: allStatic.filter(Boolean).join(" ") };
260
+ }
261
+
262
+ const parts: string[] = [];
263
+ let first = true;
264
+ for (const el of elements) {
265
+ if (el === null) continue;
266
+ if (isStringLiteral(el)) {
267
+ parts.push(first ? el.value : ` ${el.value}`);
268
+ first = false;
269
+ } else if (
270
+ typeof el === "object" &&
271
+ el !== null &&
272
+ (el as { type: string }).type === "LogicalExpression" &&
273
+ (el as { operator?: string }).operator === "&&"
274
+ ) {
275
+ const logic = el as { left: Expression; right: Expression };
276
+ const leftSrc = srcOf(source, logic.left as { start: number; end: number });
277
+ const rightEl = logic.right;
278
+ if (isStringLiteral(rightEl)) {
279
+ const sep = first ? "" : " ";
280
+ parts.push(`\${${leftSrc} ? "${sep}${rightEl.value}" : ""}`);
281
+ } else {
282
+ const sep = first ? "" : " ";
283
+ const rightSrc = srcOf(source, rightEl as { start: number; end: number });
284
+ parts.push(`\${${leftSrc} ? \`${sep}\${${rightSrc}}\` : ""}`);
285
+ }
286
+ first = false;
287
+ } else {
288
+ /* Skip null/false literals and the `undefined` identifier — they contribute nothing. */
289
+ const isNullLit = (el as { type: string }).type === "Literal" && (el as { value: unknown }).value === null;
290
+ const isFalseLit = (el as { type: string }).type === "Literal" && (el as { value: unknown }).value === false;
291
+ const isUndefined = isIdentifier(el) && el.name === "undefined";
292
+ if (isNullLit || isFalseLit || isUndefined) continue;
293
+ const sep = first ? "" : " ";
294
+ parts.push(`${sep}\${${srcOf(source, el as { start: number; end: number })}}`);
295
+ first = false;
296
+ }
297
+ }
298
+
299
+ return { expr: parts.join(""), kind: "template" };
300
+ }
301
+
302
+ function findAttr(opening: JSXOpeningElement, name: string): JSXAttribute | null {
303
+ for (const attr of opening.attributes) {
304
+ if (attr.type !== "JSXAttribute") continue;
305
+ const attrName = (attr as JSXAttribute).name;
306
+ if (attrName.type === "JSXIdentifier" && attrName.name === name) {
307
+ return attr as JSXAttribute;
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+
313
+ function hasSpread(opening: JSXOpeningElement): { identName: string } | null {
314
+ for (const attr of opening.attributes) {
315
+ if (attr.type === "JSXSpreadAttribute") {
316
+ const arg = (attr as unknown as { argument: Expression }).argument;
317
+ if (isIdentifier(arg)) return { identName: arg.name };
318
+ }
319
+ }
320
+ return null;
321
+ }
322
+
323
+ /*
324
+ * Resolve an existing class= attribute value to a canonical form.
325
+ * Returns either a literal string (for `class="..."`) or an expression string (for `class={expr}`).
326
+ */
327
+ function resolveClassAttr(
328
+ source: string,
329
+ classAttr: JSXAttribute,
330
+ ): { kind: "literal"; value: string } | { kind: "expr"; expr: string; wasArray: boolean; wasDynamic: boolean } {
331
+ const cv = classAttr.value;
332
+ if (cv === null) return { kind: "literal", value: "" };
333
+
334
+ if (isStringLiteral(cv)) return { kind: "literal", value: cv.value };
335
+
336
+ if (isJSXExpressionContainer(cv)) {
337
+ const expr = cv.expression;
338
+ if (isStringLiteral(expr)) return { kind: "literal", value: expr.value };
339
+ if (isArrayExpression(expr)) {
340
+ const resolved = resolveClassArray(source, expr.elements);
341
+ if (resolved.kind === "literal") return { kind: "literal", value: resolved.value };
342
+ return { expr: `\`${resolved.expr}\``, kind: "expr", wasArray: true, wasDynamic: false };
343
+ }
344
+ if (isCnCall(expr)) {
345
+ /* Explicit cn() — preserve as-is */
346
+ return {
347
+ expr: srcOf(source, expr as { start: number; end: number }),
348
+ kind: "expr",
349
+ wasArray: false,
350
+ wasDynamic: false,
351
+ };
352
+ }
353
+ /* Dynamic expression → wrap in cn() */
354
+ return {
355
+ expr: `cn(${srcOf(source, expr as { start: number; end: number })})`,
356
+ kind: "expr",
357
+ wasArray: false,
358
+ wasDynamic: true,
359
+ };
360
+ }
361
+ /* c8 ignore next 2 -- JSX attr values are always null | Literal | JSXExpressionContainer */
362
+ return { kind: "literal", value: "" };
363
+ }
364
+
365
+ export function rewriteModule(source: string, ctx: RewriteCtx): RewriteResult | null {
366
+ if (!source.includes("sx=") && !source.includes("css=") && !source.includes("class=")) {
367
+ return null;
368
+ }
369
+
370
+ const { program } = parseSource(source, ctx.sourcePath);
371
+ const ms = new MagicString(source);
372
+
373
+ /*
374
+ * Collect replacements as { start, end, text } tuples.
375
+ * Zero-length (start === end) = insertion; use prependLeft/appendLeft, not update().
376
+ * Applied in reverse offset order to preserve positions.
377
+ */
378
+ const replacements: Array<{ start: number; end: number; text: string }> = [];
379
+ const insertsBefore: Array<{ pos: number; text: string }> = [];
380
+
381
+ const neededImports = new Set<FlareImport>();
382
+ const emittedClasses = new Set<string>();
383
+ let changed = false;
384
+
385
+ function replace(start: number, end: number, text: string): void {
386
+ replacements.push({ end, start, text });
387
+ changed = true;
388
+ }
389
+
390
+ function insertBefore(pos: number, text: string): void {
391
+ insertsBefore.push({ pos, text });
392
+ changed = true;
393
+ }
394
+
395
+ function walkNode(node: unknown): void {
396
+ /* istanbul ignore next -- oxc-parser AST never has null/primitive children at top-level */
397
+ if (typeof node !== "object" || node === null) return;
398
+ const n = node as Record<string, unknown>;
399
+ if (n["type"] === "JSXOpeningElement") {
400
+ handleOpeningElement(node as JSXOpeningElement);
401
+ }
402
+ for (const key of Object.keys(n)) {
403
+ /* istanbul ignore next -- oxc-parser AST nodes don't have a `parent` back-reference */
404
+ if (key === "parent") continue;
405
+ const val = n[key];
406
+ if (Array.isArray(val)) {
407
+ for (const child of val) walkNode(child);
408
+ } else if (val !== null && typeof val === "object") {
409
+ walkNode(val);
410
+ }
411
+ }
412
+ }
413
+
414
+ function emitClassAttr(
415
+ classAttr: JSXAttribute | null,
416
+ insertPos: number,
417
+ literal: string | null,
418
+ expr: string | null,
419
+ ): void {
420
+ let attrText: string;
421
+ if (literal !== null) {
422
+ attrText = `class="${literal}"`;
423
+ } else {
424
+ /* istanbul ignore next -- expr is always non-null when literal is null at all call sites */
425
+ attrText = `class={${expr ?? '""'}}`;
426
+ }
427
+
428
+ if (classAttr) {
429
+ replace(classAttr.start, classAttr.end, attrText);
430
+ } else {
431
+ insertBefore(insertPos, `${attrText} `);
432
+ }
433
+ }
434
+
435
+ function handleOpeningElement(opening: JSXOpeningElement): void {
436
+ const tag = tagName(opening);
437
+ const dom = isDOMElement(tag);
438
+
439
+ const sxAttr = findAttr(opening, "sx");
440
+ const classAttr = findAttr(opening, "class");
441
+ const cssAttr = findAttr(opening, "css");
442
+ const spread = dom ? hasSpread(opening) : null;
443
+
444
+ /* Resolve existing class= to a canonical form */
445
+ let classLiteral: string | null = null;
446
+ let classExpr: string | null = null;
447
+ let classWasArray = false;
448
+ let classWasDynamic = false;
449
+ let classNeedsRewrite = false;
450
+
451
+ if (classAttr) {
452
+ const resolved = resolveClassAttr(source, classAttr);
453
+ if (resolved.kind === "literal") {
454
+ classLiteral = resolved.value;
455
+ } else {
456
+ classExpr = resolved.expr;
457
+ classWasArray = resolved.wasArray;
458
+ classWasDynamic = resolved.wasDynamic;
459
+ if (classWasDynamic) neededImports.add("cn");
460
+ }
461
+
462
+ /* Compile Tailwind utilities found in class= string literals */
463
+ const cv = classAttr.value;
464
+ if (cv === null) {
465
+ /* boolean attr — nothing to compile */
466
+ } else if (isStringLiteral(cv)) {
467
+ compileTwFromString(cv.value, ctx);
468
+ } else if (isJSXExpressionContainer(cv)) {
469
+ const expr = cv.expression;
470
+ if (isStringLiteral(expr)) {
471
+ compileTwFromString(expr.value, ctx);
472
+ } else {
473
+ compileTwFromExpr(expr, ctx);
474
+ }
475
+ }
476
+ }
477
+
478
+ /* Spread + existing class on a DOM element → merge */
479
+ if (spread && classAttr && dom) {
480
+ const spreadClass = `${spread.identName}.class`;
481
+ if (classLiteral !== null) {
482
+ classExpr = `\`${classLiteral} \${${spreadClass} ?? ""}\``;
483
+ classLiteral = null;
484
+ } /* c8 ignore next 4 -- classExpr always non-null here: spread+classAttr converts literal→expr above */ else if (
485
+ classExpr !== null
486
+ ) {
487
+ classExpr = `cn(${classExpr}, ${spreadClass})`;
488
+ neededImports.add("cn");
489
+ }
490
+ classNeedsRewrite = true;
491
+ }
492
+
493
+ /* Handle sx= */
494
+ if (sxAttr) {
495
+ const sxVal = sxAttr.value;
496
+ let sxIsDynamic = false;
497
+
498
+ if (sxVal !== null && isJSXExpressionContainer(sxAttr.value as unknown)) {
499
+ const container = sxVal as unknown as { expression: Expression };
500
+ const expr = container.expression;
501
+ if (isObjectExpression(expr)) {
502
+ const evalResult = evaluateSxObject(expr);
503
+ if (evalResult.ir !== null) {
504
+ const atomic = emitAtomic(evalResult.ir, ctx.mode);
505
+ for (const cls of atomic.classes) emittedClasses.add(cls);
506
+ for (const [cls, rule] of atomic.cssRules) {
507
+ emittedClasses.add(cls);
508
+ ctx.cssEmit(rule);
509
+ }
510
+
511
+ const atomicStr = atomic.classes.join(" ");
512
+
513
+ /* Remove sx= attr */
514
+ replace(sxAttr.start, sxAttr.end, "");
515
+
516
+ /* Emit style vars for dynamic CSS custom properties */
517
+ if (atomic.vars.length > 0) {
518
+ const styleEntries = atomic.vars
519
+ .map((v) => `"--_${v.index}": ${srcOf(source, v.exprNode as { start: number; end: number })}`)
520
+ .join(", ");
521
+ /* Insert style prop at element opening position (before first attr or tag end) */
522
+ /* istanbul ignore next -- sx itself is always an attribute, so length > 0 always */
523
+ const insertAt =
524
+ opening.attributes.length > 0
525
+ ? opening.attributes[0].start
526
+ : opening.end - (opening.selfClosing ? 2 : 1);
527
+ insertBefore(insertAt, `style={{ ${styleEntries} }} `);
528
+ }
529
+
530
+ /* Merge atomics into class */
531
+ if (spread && dom) {
532
+ const spreadClass = `${spread.identName}.class`;
533
+ /* istanbul ignore next -- classLiteral is always null here: spread+classAttr converts it to classExpr at line 284 */
534
+ if (classLiteral !== null) {
535
+ const combined = [classLiteral, atomicStr].filter(Boolean).join(" ");
536
+ classExpr = `\`${combined} \${${spreadClass} ?? ""}\``;
537
+ classLiteral = null;
538
+ } else if (classExpr !== null) {
539
+ classExpr = `cn(${classExpr}, "${atomicStr}", ${spreadClass})`;
540
+ neededImports.add("cn");
541
+ } else {
542
+ classExpr = `\`${atomicStr} \${${spreadClass} ?? ""}\``;
543
+ }
544
+ } else {
545
+ if (classLiteral !== null) {
546
+ classLiteral = [classLiteral, atomicStr].filter(Boolean).join(" ");
547
+ } else if (classExpr !== null) {
548
+ classExpr = `cn(${classExpr}, "${atomicStr}")`;
549
+ neededImports.add("cn");
550
+ } else {
551
+ classLiteral = atomicStr;
552
+ }
553
+ }
554
+
555
+ emitClassAttr(classAttr, sxAttr.start, classLiteral, classExpr);
556
+
557
+ /* Continue — css= on same element must also be processed */
558
+ if (cssAttr) {
559
+ handleCssAttr(cssAttr, classAttr, {
560
+ expr: classExpr,
561
+ insertPos: sxAttr.start,
562
+ literal: classLiteral,
563
+ });
564
+ }
565
+ return;
566
+ }
567
+ sxIsDynamic = true;
568
+ } else {
569
+ sxIsDynamic = true;
570
+ }
571
+ } else {
572
+ sxIsDynamic = true;
573
+ }
574
+
575
+ /* istanbul ignore else -- static path returns early above; reaching here means sxIsDynamic is always true */
576
+ if (sxIsDynamic) {
577
+ const container = sxVal as unknown as { expression: Expression } | null;
578
+ if (container && isJSXExpressionContainer(sxAttr.value as unknown)) {
579
+ const exprSrc = srcOf(source, container.expression as { start: number; end: number });
580
+ const layer = layerArg(ctx.layer);
581
+ replace(sxAttr.start, sxAttr.end, `{...compileSx(${exprSrc}, ${layer})}`);
582
+ neededImports.add("compileSx");
583
+ }
584
+ /* For dynamic sx, also handle class merge if needed */
585
+ if (classNeedsRewrite && classAttr) {
586
+ emitClassAttr(classAttr, sxAttr.start, classLiteral, classExpr);
587
+ }
588
+ }
589
+ /* css= on same element must be processed even when sx is dynamic */
590
+ if (cssAttr) {
591
+ handleCssAttr(cssAttr, classAttr);
592
+ }
593
+ return;
594
+ }
595
+
596
+ /* Also rewrite when classAttr had an array expression that resolved to a literal (flatten it) */
597
+ const classAttrWasArray = classAttr
598
+ ? (() => {
599
+ const cv = classAttr.value;
600
+ if (!cv || !isJSXExpressionContainer(cv)) return false;
601
+ return isArrayExpression((cv as { expression: unknown }).expression);
602
+ })()
603
+ : false;
604
+ if (classAttr && (classAttrWasArray || classWasArray || classWasDynamic || classNeedsRewrite)) {
605
+ emitClassAttr(classAttr, classAttr.start, classLiteral, classExpr);
606
+ } else if (!classAttr && spread && dom) {
607
+ /* Spread on DOM with no class attr — don't inject anything */
608
+ }
609
+
610
+ /* Handle css= attr */
611
+ if (cssAttr) {
612
+ handleCssAttr(cssAttr, classAttr);
613
+ }
614
+ }
615
+
616
+ /*
617
+ * precomputed: when sx= was already processed on the same element, pass the
618
+ * sx-merged class state so handleCssAttr doesn't re-resolve from stale source.
619
+ * Without it, classAttr in source still reflects the original value, not the
620
+ * sx-updated one — leading to a double-replace on the same span.
621
+ */
622
+ function handleCssAttr(
623
+ cssAttr: JSXAttribute,
624
+ classAttr: JSXAttribute | null,
625
+ precomputed?: { literal: string | null; expr: string | null; insertPos: number },
626
+ ): void {
627
+ const layer = layerArg(ctx.layer);
628
+ const val = cssAttr.value;
629
+
630
+ if (val === null) return;
631
+
632
+ let compileCssCall: string;
633
+
634
+ if (isStringLiteral(val)) {
635
+ compileCssCall = `compileCss(${JSON.stringify(val.value)}, ${layer})`;
636
+ } else if (isJSXExpressionContainer(val)) {
637
+ const expr = val.expression;
638
+ if (isStringLiteral(expr)) {
639
+ compileCssCall = `compileCss(${JSON.stringify(expr.value)}, ${layer})`;
640
+ } else {
641
+ const exprSrc = srcOf(source, expr as { start: number; end: number });
642
+ compileCssCall = `compileCss(${exprSrc}, ${layer})`;
643
+ }
644
+ } /* c8 ignore next 4 -- unreachable: JSX attr values are null | Literal | JSXExpressionContainer only */ else {
645
+ /* c8 ignore next */
646
+ return;
647
+ }
648
+
649
+ neededImports.add("compileCss");
650
+
651
+ /* Remove css= attr */
652
+ replace(cssAttr.start, cssAttr.end, "");
653
+
654
+ /* Fold into class= or insert new class= */
655
+ if (precomputed) {
656
+ /* sx already emitted a class= replacement — fold compileCss into that same output */
657
+ const { literal, expr, insertPos } = precomputed;
658
+ if (classAttr && literal !== null) {
659
+ if (literal) {
660
+ replace(classAttr.start, classAttr.end, `class={cn("${literal}", ${compileCssCall})}`);
661
+ neededImports.add("cn");
662
+ } else {
663
+ replace(classAttr.start, classAttr.end, `class={${compileCssCall}}`);
664
+ }
665
+ } else if (classAttr && expr !== null) {
666
+ replace(classAttr.start, classAttr.end, `class={cn(${expr}, ${compileCssCall})}`);
667
+ neededImports.add("cn");
668
+ } else {
669
+ /* sx produced no class (shouldn't happen for static sx) — insert at insertPos */
670
+ insertBefore(insertPos, `class={${compileCssCall}} `);
671
+ }
672
+ } else if (classAttr) {
673
+ const existing = resolveClassAttr(source, classAttr);
674
+ if (existing.kind === "literal") {
675
+ if (existing.value) {
676
+ replace(classAttr.start, classAttr.end, `class={cn("${existing.value}", ${compileCssCall})}`);
677
+ neededImports.add("cn");
678
+ } else {
679
+ replace(classAttr.start, classAttr.end, `class={${compileCssCall}}`);
680
+ }
681
+ } else {
682
+ replace(classAttr.start, classAttr.end, `class={cn(${existing.expr}, ${compileCssCall})}`);
683
+ neededImports.add("cn");
684
+ }
685
+ } else {
686
+ /* Insert class={compileCssCall} at css= position (which is removed) */
687
+ insertBefore(cssAttr.start, `class={${compileCssCall}} `);
688
+ }
689
+ }
690
+
691
+ walkNode(program as unknown as Node);
692
+
693
+ if (!changed) return null;
694
+
695
+ /* Apply replacements in reverse order (no zero-length update calls) */
696
+ replacements.sort((a, b) => b.start - a.start);
697
+ for (const r of replacements) {
698
+ /* istanbul ignore next -- zero-length replacements don't arise from normal JSX attrs */
699
+ if (r.start === r.end) {
700
+ ms.appendLeft(r.start, r.text);
701
+ } else {
702
+ ms.update(r.start, r.end, r.text);
703
+ }
704
+ }
705
+
706
+ /* Apply insertions */
707
+ insertsBefore.sort((a, b) => b.pos - a.pos);
708
+ for (const ins of insertsBefore) {
709
+ ms.prependLeft(ins.pos, ins.text);
710
+ }
711
+
712
+ /* Inject missing flare/styles imports */
713
+ if (neededImports.size > 0) {
714
+ const prog = program as unknown as Program;
715
+ let lastImportEnd = 0;
716
+ let existingStylesImport: {
717
+ start: number;
718
+ end: number;
719
+ specifiers: Array<{ local: { name: string }; end: number }>;
720
+ } | null = null;
721
+
722
+ for (const stmt of prog.body) {
723
+ if ((stmt as { type: string }).type !== "ImportDeclaration") continue;
724
+ lastImportEnd = Math.max(lastImportEnd, (stmt as { end: number }).end);
725
+ const s = stmt as {
726
+ source: { value: string };
727
+ start: number;
728
+ end: number;
729
+ specifiers: Array<{ local: { name: string }; end: number }>;
730
+ };
731
+ if (s.source.value === "@lovrozagar/flare/styles" || s.source.value === "flare/styles") {
732
+ existingStylesImport = s;
733
+ }
734
+ }
735
+
736
+ if (existingStylesImport) {
737
+ /* Merge needed specifiers into the existing import, skipping already-present ones */
738
+ const presentNames = new Set(existingStylesImport.specifiers.map((sp) => sp.local.name));
739
+ const missing = [...neededImports].filter((name) => !presentNames.has(name)).sort();
740
+ if (missing.length > 0) {
741
+ const toAppend = missing.map((name) => `, ${name}`).join("");
742
+ /* Insert after the last specifier's end position (before `}`) */
743
+ const lastSpec = existingStylesImport.specifiers.at(-1);
744
+ if (lastSpec) {
745
+ ms.prependLeft(lastSpec.end, toAppend);
746
+ } else {
747
+ /* Empty import `import {} from "..."` — insert inside braces */
748
+ ms.prependLeft(existingStylesImport.start + "import {".length, missing.join(", "));
749
+ }
750
+ }
751
+ } else {
752
+ const specifiers = [...neededImports].sort().join(", ");
753
+ const importLine = `import { ${specifiers} } from "@lovrozagar/flare/styles"\n`;
754
+ if (lastImportEnd > 0) {
755
+ ms.prependLeft(lastImportEnd, `\n${importLine}`);
756
+ } else {
757
+ ms.prepend(importLine);
758
+ }
759
+ }
760
+ }
761
+
762
+ return { code: ms.toString(), emittedClasses, map: null };
763
+ }