zyzz 0.0.28 → 0.0.30

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 (302) hide show
  1. package/binding/wasi/wasi-worker.mjs +186 -0
  2. package/binding/wasi/zyzz.wasi.cjs +2302 -0
  3. package/binding/wasi/zyzz.wasm32-wasi.wasm +0 -0
  4. package/binding/zyzz.linux-x64-gnu.node +0 -0
  5. package/dist/babel/index.d.ts.map +1 -1
  6. package/dist/babel/index.js +2 -4
  7. package/dist/babel/index.js.map +1 -1
  8. package/dist/babel/internal/Compilation.d.ts +0 -1
  9. package/dist/babel/internal/Compilation.d.ts.map +1 -1
  10. package/dist/compiler/Graph.d.ts +14 -11
  11. package/dist/compiler/Graph.d.ts.map +1 -1
  12. package/dist/compiler/Graph.js +57 -1442
  13. package/dist/compiler/Graph.js.map +1 -1
  14. package/dist/compiler/Native.d.ts +7 -9
  15. package/dist/compiler/Native.d.ts.map +1 -1
  16. package/dist/compiler/Native.js +12 -716
  17. package/dist/compiler/Native.js.map +1 -1
  18. package/dist/compiler/Source.d.ts +122 -24
  19. package/dist/compiler/Source.d.ts.map +1 -1
  20. package/dist/compiler/Source.js +10 -1267
  21. package/dist/compiler/Source.js.map +1 -1
  22. package/dist/compiler/Transform.d.ts +4 -3
  23. package/dist/compiler/Transform.d.ts.map +1 -1
  24. package/dist/compiler/Transform.js +13 -961
  25. package/dist/compiler/Transform.js.map +1 -1
  26. package/dist/compiler/internal/AtRules.d.ts +5 -1
  27. package/dist/compiler/internal/AtRules.d.ts.map +1 -1
  28. package/dist/compiler/internal/AtRules.js +12 -106
  29. package/dist/compiler/internal/AtRules.js.map +1 -1
  30. package/dist/compiler/internal/Context.d.ts +11 -0
  31. package/dist/compiler/internal/Context.d.ts.map +1 -0
  32. package/dist/compiler/internal/Context.js +11 -0
  33. package/dist/compiler/internal/Context.js.map +1 -0
  34. package/dist/compiler/internal/Results.d.ts +148 -0
  35. package/dist/compiler/internal/Results.d.ts.map +1 -0
  36. package/dist/compiler/internal/Results.js +259 -0
  37. package/dist/compiler/internal/Results.js.map +1 -0
  38. package/dist/compiler/internal/Syntax.d.ts +0 -2
  39. package/dist/compiler/internal/Syntax.d.ts.map +1 -1
  40. package/dist/compiler/internal/Syntax.js +0 -2
  41. package/dist/compiler/internal/Syntax.js.map +1 -1
  42. package/dist/compiler/internal/Wire.d.ts +17 -0
  43. package/dist/compiler/internal/Wire.d.ts.map +1 -0
  44. package/dist/compiler/internal/Wire.js +795 -0
  45. package/dist/compiler/internal/Wire.js.map +1 -0
  46. package/dist/internal/Addon.d.ts +28 -0
  47. package/dist/internal/Addon.d.ts.map +1 -0
  48. package/dist/internal/Addon.js +87 -0
  49. package/dist/internal/Addon.js.map +1 -0
  50. package/dist/internal/Literal.d.ts +4 -1
  51. package/dist/internal/Literal.d.ts.map +1 -1
  52. package/dist/internal/Literal.js +4 -2
  53. package/dist/internal/Literal.js.map +1 -1
  54. package/dist/internal/Token.d.ts +2 -1
  55. package/dist/internal/Token.d.ts.map +1 -1
  56. package/dist/internal/Token.js +2 -2
  57. package/dist/internal/Token.js.map +1 -1
  58. package/dist/internal/Vlq.d.ts +14 -0
  59. package/dist/internal/Vlq.d.ts.map +1 -0
  60. package/dist/internal/Vlq.js +47 -0
  61. package/dist/internal/Vlq.js.map +1 -0
  62. package/dist/internal/Wasm.d.ts +22 -0
  63. package/dist/internal/Wasm.d.ts.map +1 -0
  64. package/dist/internal/Wasm.js +83 -0
  65. package/dist/internal/Wasm.js.map +1 -0
  66. package/dist/metro/Graph.d.ts +0 -1
  67. package/dist/metro/Graph.d.ts.map +1 -1
  68. package/dist/metro/transformer.d.ts.map +1 -1
  69. package/dist/metro/transformer.js +0 -1
  70. package/dist/metro/transformer.js.map +1 -1
  71. package/dist/next/internal/Project.d.ts.map +1 -1
  72. package/dist/next/internal/Project.js +2 -5
  73. package/dist/next/internal/Project.js.map +1 -1
  74. package/dist/node/Host.d.ts.map +1 -1
  75. package/dist/node/Host.js +0 -2
  76. package/dist/node/Host.js.map +1 -1
  77. package/dist/node/internal/Contracts.d.ts.map +1 -1
  78. package/dist/node/internal/Contracts.js +3 -7
  79. package/dist/node/internal/Contracts.js.map +1 -1
  80. package/dist/node/internal/Snapshot.d.ts +0 -1
  81. package/dist/node/internal/Snapshot.d.ts.map +1 -1
  82. package/dist/node/internal/Snapshot.js +0 -6
  83. package/dist/node/internal/Snapshot.js.map +1 -1
  84. package/dist/runtime/Composition.d.ts +2 -3
  85. package/dist/runtime/Composition.d.ts.map +1 -1
  86. package/dist/runtime/Composition.js +51 -30
  87. package/dist/runtime/Composition.js.map +1 -1
  88. package/dist/runtime/Recipe.d.ts.map +1 -1
  89. package/dist/runtime/Recipe.js +16 -12
  90. package/dist/runtime/Recipe.js.map +1 -1
  91. package/dist/unplugin/index.d.ts.map +1 -1
  92. package/dist/unplugin/index.js +3 -6
  93. package/dist/unplugin/index.js.map +1 -1
  94. package/dist/vite/index.d.ts.map +1 -1
  95. package/dist/vite/index.js +696 -201
  96. package/dist/vite/index.js.map +1 -1
  97. package/dist/web/Css.d.ts +8 -3
  98. package/dist/web/Css.d.ts.map +1 -1
  99. package/dist/web/Css.js +5 -721
  100. package/dist/web/Css.js.map +1 -1
  101. package/dist/web/internal/Block.d.ts +0 -2
  102. package/dist/web/internal/Block.d.ts.map +1 -1
  103. package/dist/web/internal/Block.js +1 -12
  104. package/dist/web/internal/Block.js.map +1 -1
  105. package/dist/web/internal/ClassName.d.ts +23 -18
  106. package/dist/web/internal/ClassName.d.ts.map +1 -1
  107. package/dist/web/internal/ClassName.js +26 -55
  108. package/dist/web/internal/ClassName.js.map +1 -1
  109. package/dist/web/internal/Contributions.d.ts +3 -5
  110. package/dist/web/internal/Contributions.d.ts.map +1 -1
  111. package/dist/web/internal/Contributions.js +0 -45
  112. package/dist/web/internal/Contributions.js.map +1 -1
  113. package/dist/web/internal/Engine.browser.d.ts +8 -0
  114. package/dist/web/internal/Engine.browser.d.ts.map +1 -0
  115. package/dist/web/internal/Engine.browser.js +42 -0
  116. package/dist/web/internal/Engine.browser.js.map +1 -0
  117. package/dist/web/internal/Engine.d.ts +9 -0
  118. package/dist/web/internal/Engine.d.ts.map +1 -0
  119. package/dist/web/internal/Engine.js +14 -0
  120. package/dist/web/internal/Engine.js.map +1 -0
  121. package/dist/web/internal/Themes.d.ts +1 -9
  122. package/dist/web/internal/Themes.d.ts.map +1 -1
  123. package/dist/web/internal/Themes.js +3 -221
  124. package/dist/web/internal/Themes.js.map +1 -1
  125. package/dist/web/internal/Wasm.generated.d.ts +4 -0
  126. package/dist/web/internal/Wasm.generated.d.ts.map +1 -0
  127. package/dist/web/internal/Wasm.generated.js +4 -0
  128. package/dist/web/internal/Wasm.generated.js.map +1 -0
  129. package/dist/web/internal/Wire.d.ts +19 -0
  130. package/dist/web/internal/Wire.d.ts.map +1 -0
  131. package/dist/web/internal/Wire.js +387 -0
  132. package/dist/web/internal/Wire.js.map +1 -0
  133. package/package.json +20 -1
  134. package/src/babel/index.ts +2 -5
  135. package/src/babel/internal/Compilation.ts +0 -1
  136. package/src/compiler/Graph.ts +89 -1930
  137. package/src/compiler/Native.ts +18 -871
  138. package/src/compiler/Source.ts +144 -1803
  139. package/src/compiler/Transform.ts +16 -1315
  140. package/src/compiler/internal/AtRules.ts +27 -115
  141. package/src/compiler/internal/Context.ts +11 -0
  142. package/src/compiler/internal/Results.ts +406 -0
  143. package/src/compiler/internal/Syntax.ts +0 -3
  144. package/src/compiler/internal/Wire.ts +1045 -0
  145. package/src/internal/Addon.ts +118 -0
  146. package/src/internal/Literal.ts +4 -2
  147. package/src/internal/Token.ts +2 -2
  148. package/src/internal/Vlq.ts +59 -0
  149. package/src/internal/Wasm.ts +111 -0
  150. package/src/metro/transformer.ts +0 -1
  151. package/src/next/internal/Project.ts +2 -9
  152. package/src/node/Host.ts +0 -2
  153. package/src/node/internal/Contracts.ts +3 -7
  154. package/src/node/internal/Snapshot.ts +0 -8
  155. package/src/runtime/Composition.ts +48 -25
  156. package/src/runtime/Recipe.ts +21 -14
  157. package/src/unplugin/index.ts +6 -6
  158. package/src/vite/index.ts +922 -221
  159. package/src/web/Css.ts +12 -908
  160. package/src/web/internal/Block.ts +0 -14
  161. package/src/web/internal/ClassName.ts +52 -85
  162. package/src/web/internal/Contributions.ts +3 -59
  163. package/src/web/internal/Engine.browser.ts +51 -0
  164. package/src/web/internal/Engine.ts +17 -0
  165. package/src/web/internal/Themes.ts +3 -279
  166. package/src/web/internal/Wasm.generated.ts +3 -0
  167. package/src/web/internal/Wire.ts +466 -0
  168. package/src/web/internal/wasm.d.ts +9 -0
  169. package/dist/compiler/internal/Applications.d.ts +0 -55
  170. package/dist/compiler/internal/Applications.d.ts.map +0 -1
  171. package/dist/compiler/internal/Applications.js +0 -393
  172. package/dist/compiler/internal/Applications.js.map +0 -1
  173. package/dist/compiler/internal/ColorProfiles.d.ts +0 -3
  174. package/dist/compiler/internal/ColorProfiles.d.ts.map +0 -1
  175. package/dist/compiler/internal/ColorProfiles.js +0 -84
  176. package/dist/compiler/internal/ColorProfiles.js.map +0 -1
  177. package/dist/compiler/internal/CompositionBindings.d.ts +0 -16
  178. package/dist/compiler/internal/CompositionBindings.d.ts.map +0 -1
  179. package/dist/compiler/internal/CompositionBindings.js +0 -89
  180. package/dist/compiler/internal/CompositionBindings.js.map +0 -1
  181. package/dist/compiler/internal/Compositions.d.ts +0 -37
  182. package/dist/compiler/internal/Compositions.d.ts.map +0 -1
  183. package/dist/compiler/internal/Compositions.js +0 -494
  184. package/dist/compiler/internal/Compositions.js.map +0 -1
  185. package/dist/compiler/internal/Configurations.d.ts +0 -26
  186. package/dist/compiler/internal/Configurations.d.ts.map +0 -1
  187. package/dist/compiler/internal/Configurations.js +0 -259
  188. package/dist/compiler/internal/Configurations.js.map +0 -1
  189. package/dist/compiler/internal/Contract.d.ts +0 -30
  190. package/dist/compiler/internal/Contract.d.ts.map +0 -1
  191. package/dist/compiler/internal/Contract.js +0 -753
  192. package/dist/compiler/internal/Contract.js.map +0 -1
  193. package/dist/compiler/internal/Contributions.d.ts +0 -54
  194. package/dist/compiler/internal/Contributions.d.ts.map +0 -1
  195. package/dist/compiler/internal/Contributions.js +0 -1151
  196. package/dist/compiler/internal/Contributions.js.map +0 -1
  197. package/dist/compiler/internal/Descriptors.d.ts +0 -15
  198. package/dist/compiler/internal/Descriptors.d.ts.map +0 -1
  199. package/dist/compiler/internal/Descriptors.js +0 -192
  200. package/dist/compiler/internal/Descriptors.js.map +0 -1
  201. package/dist/compiler/internal/Dynamic.d.ts +0 -14
  202. package/dist/compiler/internal/Dynamic.d.ts.map +0 -1
  203. package/dist/compiler/internal/Dynamic.js +0 -175
  204. package/dist/compiler/internal/Dynamic.js.map +0 -1
  205. package/dist/compiler/internal/Expression.d.ts +0 -19
  206. package/dist/compiler/internal/Expression.d.ts.map +0 -1
  207. package/dist/compiler/internal/Expression.js +0 -164
  208. package/dist/compiler/internal/Expression.js.map +0 -1
  209. package/dist/compiler/internal/Functions.d.ts +0 -5
  210. package/dist/compiler/internal/Functions.d.ts.map +0 -1
  211. package/dist/compiler/internal/Functions.js +0 -191
  212. package/dist/compiler/internal/Functions.js.map +0 -1
  213. package/dist/compiler/internal/Keyframes.d.ts +0 -5
  214. package/dist/compiler/internal/Keyframes.d.ts.map +0 -1
  215. package/dist/compiler/internal/Keyframes.js +0 -51
  216. package/dist/compiler/internal/Keyframes.js.map +0 -1
  217. package/dist/compiler/internal/Names.d.ts +0 -5
  218. package/dist/compiler/internal/Names.d.ts.map +0 -1
  219. package/dist/compiler/internal/Names.js +0 -37
  220. package/dist/compiler/internal/Names.js.map +0 -1
  221. package/dist/compiler/internal/NativeBindings.d.ts +0 -72
  222. package/dist/compiler/internal/NativeBindings.d.ts.map +0 -1
  223. package/dist/compiler/internal/NativeBindings.js +0 -171
  224. package/dist/compiler/internal/NativeBindings.js.map +0 -1
  225. package/dist/compiler/internal/NativeMedia.d.ts +0 -14
  226. package/dist/compiler/internal/NativeMedia.d.ts.map +0 -1
  227. package/dist/compiler/internal/NativeMedia.js +0 -287
  228. package/dist/compiler/internal/NativeMedia.js.map +0 -1
  229. package/dist/compiler/internal/NativeVars.d.ts +0 -28
  230. package/dist/compiler/internal/NativeVars.d.ts.map +0 -1
  231. package/dist/compiler/internal/NativeVars.js +0 -160
  232. package/dist/compiler/internal/NativeVars.js.map +0 -1
  233. package/dist/compiler/internal/PackedStyles.d.ts +0 -37
  234. package/dist/compiler/internal/PackedStyles.d.ts.map +0 -1
  235. package/dist/compiler/internal/PackedStyles.js +0 -344
  236. package/dist/compiler/internal/PackedStyles.js.map +0 -1
  237. package/dist/compiler/internal/Properties.d.ts +0 -5
  238. package/dist/compiler/internal/Properties.d.ts.map +0 -1
  239. package/dist/compiler/internal/Properties.js +0 -82
  240. package/dist/compiler/internal/Properties.js.map +0 -1
  241. package/dist/compiler/internal/RecipeConditions.d.ts +0 -41
  242. package/dist/compiler/internal/RecipeConditions.d.ts.map +0 -1
  243. package/dist/compiler/internal/RecipeConditions.js +0 -82
  244. package/dist/compiler/internal/RecipeConditions.js.map +0 -1
  245. package/dist/compiler/internal/RecipePayloads.d.ts +0 -31
  246. package/dist/compiler/internal/RecipePayloads.d.ts.map +0 -1
  247. package/dist/compiler/internal/RecipePayloads.js +0 -99
  248. package/dist/compiler/internal/RecipePayloads.js.map +0 -1
  249. package/dist/compiler/internal/Recipes.d.ts +0 -32
  250. package/dist/compiler/internal/Recipes.d.ts.map +0 -1
  251. package/dist/compiler/internal/Recipes.js +0 -339
  252. package/dist/compiler/internal/Recipes.js.map +0 -1
  253. package/dist/compiler/internal/Selectors.d.ts +0 -13
  254. package/dist/compiler/internal/Selectors.d.ts.map +0 -1
  255. package/dist/compiler/internal/Selectors.js +0 -280
  256. package/dist/compiler/internal/Selectors.js.map +0 -1
  257. package/dist/compiler/internal/Static.d.ts +0 -39
  258. package/dist/compiler/internal/Static.d.ts.map +0 -1
  259. package/dist/compiler/internal/Static.js +0 -534
  260. package/dist/compiler/internal/Static.js.map +0 -1
  261. package/dist/compiler/internal/Themes.d.ts +0 -190
  262. package/dist/compiler/internal/Themes.d.ts.map +0 -1
  263. package/dist/compiler/internal/Themes.js +0 -1577
  264. package/dist/compiler/internal/Themes.js.map +0 -1
  265. package/dist/compiler/internal/Vars.d.ts +0 -33
  266. package/dist/compiler/internal/Vars.d.ts.map +0 -1
  267. package/dist/compiler/internal/Vars.js +0 -387
  268. package/dist/compiler/internal/Vars.js.map +0 -1
  269. package/dist/compiler/internal/ViewTransitions.d.ts +0 -3
  270. package/dist/compiler/internal/ViewTransitions.d.ts.map +0 -1
  271. package/dist/compiler/internal/ViewTransitions.js +0 -68
  272. package/dist/compiler/internal/ViewTransitions.js.map +0 -1
  273. package/dist/internal/Cascade.d.ts +0 -74
  274. package/dist/internal/Cascade.d.ts.map +0 -1
  275. package/dist/internal/Cascade.js +0 -210
  276. package/dist/internal/Cascade.js.map +0 -1
  277. package/src/compiler/internal/Applications.ts +0 -610
  278. package/src/compiler/internal/ColorProfiles.ts +0 -93
  279. package/src/compiler/internal/CompositionBindings.ts +0 -120
  280. package/src/compiler/internal/Compositions.ts +0 -678
  281. package/src/compiler/internal/Configurations.ts +0 -349
  282. package/src/compiler/internal/Contract.ts +0 -1028
  283. package/src/compiler/internal/Contributions.ts +0 -1561
  284. package/src/compiler/internal/Descriptors.ts +0 -242
  285. package/src/compiler/internal/Dynamic.ts +0 -275
  286. package/src/compiler/internal/Expression.ts +0 -218
  287. package/src/compiler/internal/Functions.ts +0 -206
  288. package/src/compiler/internal/Keyframes.ts +0 -62
  289. package/src/compiler/internal/Names.ts +0 -48
  290. package/src/compiler/internal/NativeBindings.ts +0 -235
  291. package/src/compiler/internal/NativeMedia.ts +0 -342
  292. package/src/compiler/internal/NativeVars.ts +0 -231
  293. package/src/compiler/internal/PackedStyles.ts +0 -473
  294. package/src/compiler/internal/Properties.ts +0 -121
  295. package/src/compiler/internal/RecipeConditions.ts +0 -139
  296. package/src/compiler/internal/RecipePayloads.ts +0 -132
  297. package/src/compiler/internal/Recipes.ts +0 -531
  298. package/src/compiler/internal/Selectors.ts +0 -353
  299. package/src/compiler/internal/Static.ts +0 -784
  300. package/src/compiler/internal/Themes.ts +0 -2398
  301. package/src/compiler/internal/Vars.ts +0 -516
  302. package/src/compiler/internal/ViewTransitions.ts +0 -82
@@ -1,1561 +0,0 @@
1
- /** Extracts module-level stylesheet effects without evaluating application code. @module */
2
- import * as Identity from '../../internal/Identity.js'
3
- import type * as Ast from '@oxc-project/types'
4
- import type * as Block from '../../web/internal/Block.js'
5
- import * as Condition from '../../internal/Condition.js'
6
- import type * as Css from '../../web/Css.js'
7
- import type { cssFunction } from '../../web/cssFunction.js'
8
- import * as Descriptors from './Descriptors.js'
9
- import * as Expression from './Expression.js'
10
- import * as Functions from './Functions.js'
11
- import * as FunctionSyntax from '../../internal/FunctionSyntax.js'
12
- import * as Identifiers from './Identifiers.js'
13
- import * as Keyframes from './Keyframes.js'
14
- import * as Lexical from '../../internal/Lexical.js'
15
- import * as Lightning from 'lightningcss'
16
- import * as Literal from '../../internal/Literal.js'
17
- import * as Properties from './Properties.js'
18
- import * as RuleReference from '../../internal/RuleReference.js'
19
- import type * as Scope from './Scope.js'
20
- import * as Style from '../../Style.js'
21
- import * as Theme from '../../internal/Theme.js'
22
- import * as Themes from './Themes.js'
23
- import type * as Token from '../../internal/Token.js'
24
- import * as Walker from 'oxc-walker'
25
-
26
- type Kind =
27
- | 'importCss'
28
- | 'namespace'
29
- | 'fontFeatureValues'
30
- | 'page'
31
- | 'property'
32
- | 'viewTransition'
33
- | 'fontFace'
34
- | 'global'
35
- | 'keyframes'
36
- | 'layers'
37
- | RuleReference.Kind
38
- const named = [
39
- 'cssFunction',
40
- 'customMedia',
41
- 'colorProfile',
42
- 'counterStyle',
43
- 'fontPaletteValues',
44
- 'keyframes',
45
- 'positionTry',
46
- ]
47
- /** Stylesheet factory exports recognized by source and graph linking. */
48
- export const factories: readonly string[] = [
49
- 'importCss',
50
- 'namespace',
51
- 'fontFace',
52
- 'fontFeatureValues',
53
- 'global',
54
- 'layers',
55
- 'page',
56
- 'property',
57
- 'viewTransition',
58
- ...named,
59
- ]
60
-
61
- /** Source-owned factory replacement and optional animation identity. */
62
- export type Call = {
63
- /** CSS function signature populated by literal extraction. */
64
- readonly function?: NonNullable<Themes.Call['function']> | undefined
65
- readonly start: number
66
- readonly end: number
67
- readonly kind: Kind
68
- readonly context?: Ast.Node | undefined
69
- readonly argument: Ast.Node
70
- readonly name?: string | undefined
71
- readonly binding?: number | undefined
72
- readonly exported?: boolean | undefined
73
- }
74
-
75
- /** Collects lexical macro calls and animation references. */
76
- export function scan(
77
- program: Ast.Program,
78
- scope: Scope.Tracker,
79
- namespace: string,
80
- links: Readonly<Record<string, Themes.Link>> = {},
81
- factoryImports: Readonly<Record<string, string>> = {},
82
- ) {
83
- const imports = new Map<number, Kind>()
84
-
85
- for (const node of program.body)
86
- if (node.type === 'ImportDeclaration' && node.importKind !== 'type')
87
- for (const specifier of node.specifiers)
88
- if (
89
- specifier.type === 'ImportSpecifier' &&
90
- specifier.importKind !== 'type'
91
- ) {
92
- const imported =
93
- specifier.imported.type === 'Identifier'
94
- ? specifier.imported.name
95
- : specifier.imported.value
96
- const name =
97
- node.source.value === 'zyzz/web'
98
- ? imported
99
- : factoryImports[specifier.local.name]
100
- if (name && factories.includes(name))
101
- imports.set(specifier.start, name as Kind)
102
- }
103
-
104
- const exported: Record<string, Themes.Link> = Object.create(null)
105
- const linkedNames = new Map<string, Themes.Link>()
106
- const imported = new Map<number, string>()
107
-
108
- for (const node of program.body)
109
- if (node.type === 'ImportDeclaration')
110
- for (const specifier of node.specifiers) {
111
- const link = links[specifier.local.name]
112
- if (link?.kind === 'animation' || link?.kind === 'rule-reference') {
113
- imported.set(specifier.start, link.call.name)
114
- linkedNames.set(specifier.local.name, link)
115
- }
116
- }
117
-
118
- const calls: Call[] = []
119
- const functionInputs: {
120
- name: string
121
- signature: NonNullable<Themes.Call['function']>
122
- values: readonly (string | undefined)[]
123
- node: Extract<Ast.Node, { type: 'CallExpression' }>
124
- }[] = []
125
- const bindings = new Map<number, Call>()
126
- const references = new Map<number, string>()
127
- const queryKeys = new Map<number, string>()
128
- const used = new Set<string>()
129
- const undefinedValues = new Set<number>()
130
- const ancestors: Ast.Node[] = []
131
-
132
- function kind(node: Ast.Node): Kind | undefined {
133
- if (node.type === 'Identifier') {
134
- const binding = scope.getDeclaration(node.name)
135
- const name =
136
- binding?.type === 'Import' ? imports.get(binding.node.start) : undefined
137
-
138
- return name
139
- }
140
-
141
- return undefined
142
- }
143
-
144
- if (imports.size || imported.size)
145
- Walker.walk(program, {
146
- scopeTracker: scope,
147
- enter(node, parent) {
148
- ancestors.push(node)
149
-
150
- if (
151
- node.type === 'Identifier' &&
152
- node.name === 'undefined' &&
153
- !scope.getDeclaration(node.name)
154
- )
155
- undefinedValues.add(node.start)
156
-
157
- if (
158
- node.type === 'VariableDeclarator' &&
159
- node.id.type === 'Identifier' &&
160
- node.init &&
161
- Expression.unwrap(node.init).type === 'Identifier'
162
- ) {
163
- const init = Expression.unwrap(node.init) as Extract<
164
- Ast.Node,
165
- { type: 'Identifier' }
166
- >
167
- const declaration = scope.getDeclaration(init.name)
168
- const identity = declaration
169
- ? imported.get(declaration.node.start)
170
- : undefined
171
-
172
- const link = identity
173
- ? [...linkedNames.values()].find(
174
- (link) => link.call.name === identity,
175
- )
176
- : undefined
177
-
178
- if (link) {
179
- if (
180
- parent?.type !== 'VariableDeclaration' ||
181
- parent.kind !== 'const'
182
- )
183
- throw new Themes.InvalidError(
184
- 'Animation aliases require const bindings.',
185
- node,
186
- )
187
-
188
- imported.set(node.start, link.call.name)
189
-
190
- if (
191
- ancestors.at(-3)?.type === 'Program' ||
192
- ancestors.at(-3)?.type === 'ExportNamedDeclaration'
193
- )
194
- linkedNames.set(node.id.name, link)
195
-
196
- if (
197
- ancestors.some((node) => node.type === 'ExportNamedDeclaration')
198
- )
199
- exported[node.id.name] = link
200
- }
201
- }
202
-
203
- if (node.type !== 'CallExpression') return
204
-
205
- const type = kind(node.callee)
206
- if (!type) return
207
-
208
- const variable =
209
- parent?.type === 'VariableDeclarator' && parent.init === node
210
- ? parent
211
- : undefined
212
- const statement = variable ? ancestors.at(-3) : parent
213
- if (
214
- node.optional ||
215
- (node.arguments.length !== 1 &&
216
- (![
217
- 'fontFace',
218
- 'fontFeatureValues',
219
- 'page',
220
- 'property',
221
- 'viewTransition',
222
- ...named,
223
- ].includes(type) ||
224
- node.arguments.length !== 2)) ||
225
- (!variable && parent?.type !== 'ExpressionStatement') ||
226
- ancestors
227
- .slice(0, -1)
228
- .some(
229
- (ancestor) =>
230
- ![
231
- 'Program',
232
- 'ExportNamedDeclaration',
233
- 'VariableDeclaration',
234
- 'VariableDeclarator',
235
- 'ExpressionStatement',
236
- ].includes(ancestor.type),
237
- ) ||
238
- (variable &&
239
- (statement?.type !== 'VariableDeclaration' ||
240
- statement.kind !== 'const' ||
241
- variable.id.type !== 'Identifier'))
242
- )
243
- throw new Themes.InvalidError(
244
- 'Stylesheet contributions require direct module-level calls and constant named stylesheet bindings.',
245
- node,
246
- )
247
- if (named.includes(type) && !variable)
248
- throw new Themes.InvalidError(
249
- 'Named stylesheet definitions require a module-level named constant.',
250
- node,
251
- )
252
-
253
- const context = node.arguments[1]
254
- const idProperty =
255
- context?.type === 'ObjectExpression'
256
- ? context.properties.find(
257
- (property) =>
258
- property.type === 'Property' &&
259
- !property.computed &&
260
- (property.key.type === 'Identifier'
261
- ? property.key.name
262
- : property.key.type === 'Literal'
263
- ? property.key.value
264
- : undefined) === 'id',
265
- )
266
- : undefined
267
- if (
268
- idProperty &&
269
- (idProperty.type !== 'Property' ||
270
- idProperty.value.type !== 'Literal' ||
271
- typeof idProperty.value.value !== 'string' ||
272
- !idProperty.value.value)
273
- )
274
- throw new Themes.InvalidError(
275
- 'Stylesheet ids must be nonempty string literals.',
276
- node,
277
- )
278
-
279
- const explicit =
280
- idProperty?.type === 'Property' &&
281
- idProperty.value.type === 'Literal' &&
282
- typeof idProperty.value.value === 'string'
283
- ? idProperty.value.value
284
- : undefined
285
- const name =
286
- explicit !== undefined
287
- ? Identity.contribution(type, explicit)
288
- : variable?.id.type === 'Identifier'
289
- ? `${type === 'keyframes' ? 'z-k-' : `${type === 'counterStyle' ? '' : '--'}z-${type.toLowerCase()}-`}${Identity.name(variable.id.name)}`
290
- : undefined
291
-
292
- const call: Call = {
293
- kind: type,
294
- start: node.start,
295
- end: node.end,
296
- argument: node.arguments[0]!,
297
- context: node.arguments[1],
298
- ...(type === 'cssFunction'
299
- ? { function: { parameters: [], returns: '*' as const } }
300
- : {}),
301
- ...(named.includes(type)
302
- ? {
303
- name,
304
- binding: variable!.start,
305
- exported: ancestors.some(
306
- (node) => node.type === 'ExportNamedDeclaration',
307
- ),
308
- }
309
- : {}),
310
- }
311
-
312
- calls.push(call)
313
- if (named.includes(call.kind) && variable?.id.type === 'Identifier') {
314
- const link: Themes.Link = {
315
- binding: call.name!,
316
- kind: call.kind === 'keyframes' ? 'animation' : 'rule-reference',
317
- definition: Theme.define({}),
318
- call: {
319
- start: call.start,
320
- end: call.end,
321
- name: call.name!,
322
- tokenType: '{}',
323
- ...(call.function ? { function: call.function } : {}),
324
- ...(call.kind !== 'keyframes'
325
- ? { reference: call.kind as RuleReference.Kind }
326
- : {}),
327
- },
328
- }
329
-
330
- imported.set(variable.start, call.name!)
331
- linkedNames.set(variable.id.name, link)
332
-
333
- if (call.exported) exported[variable.id.name] = link
334
- }
335
-
336
- if (call.binding !== undefined) bindings.set(call.binding, call)
337
- },
338
- leave() {
339
- ancestors.pop()
340
- },
341
- })
342
-
343
- function read(
344
- node: Extract<Ast.Node, { type: 'Identifier' | 'JSXIdentifier' }>,
345
- parent: Ast.Node,
346
- binding: Walker.ScopeTrackerNode | null,
347
- ) {
348
- if (
349
- (binding?.type === 'Import' || binding?.type === 'Variable') &&
350
- imported.has(binding.node.start)
351
- ) {
352
- const name = imported.get(binding.node.start)!
353
- const link = [...linkedNames.values()].find(
354
- (link) => link.call.name === name,
355
- )
356
- const text =
357
- link?.call.reference === 'customMedia' ? `@media (${name})` : name
358
- references.set(node.start, name)
359
- if (link?.call.reference === 'customMedia')
360
- queryKeys.set(node.start, text)
361
- if (
362
- link?.call.reference === 'cssFunction' &&
363
- parent.type === 'CallExpression' &&
364
- parent.callee === node
365
- ) {
366
- // A call and its callee share their start offset. Never keep the bare name
367
- // when an argument cannot be serialized as a static CSS expression.
368
- references.delete(node.start)
369
- const args = parent.arguments.map((argument) => {
370
- const input = Expression.unwrap(argument)
371
- if (
372
- input.type === 'Literal' &&
373
- (typeof input.value === 'string' || typeof input.value === 'number')
374
- )
375
- return String(input.value)
376
- if (
377
- input.type === 'UnaryExpression' &&
378
- (input.operator === '-' || input.operator === '+') &&
379
- input.argument.type === 'Literal' &&
380
- typeof input.argument.value === 'number'
381
- )
382
- return String(
383
- input.operator === '-'
384
- ? -input.argument.value
385
- : input.argument.value,
386
- )
387
- return undefined
388
- })
389
- if (link.call.function)
390
- functionInputs.push({
391
- name,
392
- signature: link.call.function,
393
- values: args,
394
- node: parent,
395
- })
396
- if (args.every((value) => value !== undefined))
397
- references.set(
398
- parent.start,
399
- `${name}(${args.map((value) => (typeof value === 'string' && value.includes(',') && !value.trimStart().startsWith('{') ? `{${value}}` : value)).join(',')})`,
400
- )
401
- }
402
- used.add(imported.get(binding.node.start)!)
403
-
404
- return
405
- }
406
-
407
- if (binding?.type !== 'Variable') return
408
-
409
- const call = bindings.get(binding.node.start)
410
- if (
411
- !call?.name ||
412
- (parent.type === 'VariableDeclarator' && parent.id === node)
413
- )
414
- return
415
-
416
- references.set(node.start, call.name)
417
- used.add(call.name)
418
- }
419
-
420
- for (const statement of program.body) {
421
- if (statement.type === 'ExportDefaultDeclaration') {
422
- const declaration = Expression.unwrap(statement.declaration)
423
- const link =
424
- declaration.type === 'Identifier'
425
- ? linkedNames.get(declaration.name)
426
- : undefined
427
-
428
- if (link) {
429
- used.add(link.call.name)
430
- exported.default = link
431
- }
432
- }
433
-
434
- if (
435
- statement.type === 'ExportNamedDeclaration' &&
436
- statement.exportKind !== 'type' &&
437
- !statement.source
438
- )
439
- for (const specifier of statement.specifiers) {
440
- if ('exportKind' in specifier && specifier.exportKind === 'type')
441
- continue
442
-
443
- const name =
444
- specifier.local.type === 'Identifier'
445
- ? specifier.local.name
446
- : specifier.local.value
447
- const link = linkedNames.get(name)
448
-
449
- if (link) {
450
- used.add(link.call.name)
451
- exported[
452
- specifier.exported.type === 'Identifier'
453
- ? specifier.exported.name
454
- : specifier.exported.value
455
- ] = link
456
- }
457
- }
458
- }
459
- const kinds = new Map(
460
- [...linkedNames.values()].flatMap((link) =>
461
- link.call.reference
462
- ? [[link.call.name, link.call.reference] as const]
463
- : [],
464
- ),
465
- )
466
- return {
467
- functionInputs,
468
- namespace,
469
- queryKeys,
470
- calls,
471
- kinds,
472
- references,
473
- read,
474
- used,
475
- undefinedValues,
476
- exports: exported,
477
- }
478
- }
479
-
480
- /** Resolves nested static invocations after lexical reference discovery. */
481
- export function resolve(scanned: ReturnType<typeof scan>): void {
482
- for (const input of [...scanned.functionInputs].reverse()) {
483
- input.values = input.values.map((value, index) => {
484
- const node = Expression.unwrap(input.node.arguments[index]!)
485
- return (
486
- value ??
487
- (node.type === 'CallExpression'
488
- ? scanned.references.get(node.start)
489
- : undefined)
490
- )
491
- })
492
- if (input.values.every((value) => value !== undefined))
493
- scanned.references.set(
494
- input.node.start,
495
- `${input.name}(${input.values.map((value) => (value!.includes(',') && !value!.trimStart().startsWith('{') ? `{${value}}` : value)).join(',')})`,
496
- )
497
- }
498
- }
499
-
500
- /** Builds ordered pure web data after lexical theme references are resolved. */
501
- export function extract(
502
- scanned: ReturnType<typeof scan>,
503
- tokens: ReadonlyMap<number, { end: number; reference: Token.Reference }>,
504
- starts?: number[],
505
- resolve: (node: Ast.Node) => Ast.Node = Expression.unwrap,
506
- ): readonly Css.Contribution[] {
507
- const result: Css.Contribution[] = []
508
- function value(node: Ast.Node, property?: string): unknown {
509
- const reference = tokens.get(node.start)
510
- if (reference?.end === node.end) return reference.reference
511
- node = Expression.unwrap(resolve(node))
512
-
513
- const animation = scanned.references.get(node.start)
514
- if (animation) return animation
515
-
516
- node = Expression.unwrap(node)
517
-
518
- if (scanned.undefinedValues.has(node.start)) return undefined
519
-
520
- if (
521
- node.type === 'UnaryExpression' &&
522
- node.operator === 'void' &&
523
- Expression.unwrap(node.argument).type === 'Literal'
524
- )
525
- return undefined
526
- if (
527
- node.type === 'Literal' &&
528
- (typeof node.value === 'string' ||
529
- typeof node.value === 'number' ||
530
- typeof node.value === 'boolean')
531
- ) {
532
- return node.value
533
- }
534
-
535
- if (
536
- node.type === 'UnaryExpression' &&
537
- (node.operator === '-' || node.operator === '+') &&
538
- node.argument.type === 'Literal' &&
539
- typeof node.argument.value === 'number'
540
- )
541
- return node.operator === '-' ? -node.argument.value : node.argument.value
542
-
543
- if (node.type === 'TemplateLiteral') {
544
- const template = Expression.template(node, 0, (expression, prefix) => {
545
- const node = Expression.unwrap(resolve(expression))
546
- if (node.type === 'Literal' && typeof node.value === 'string')
547
- return node.value
548
- const name = scanned.references.get(node.start)
549
- const kind = name ? scanned.kinds.get(name) : undefined
550
- if (
551
- kind &&
552
- property &&
553
- !RuleReference.acceptsExpression(kind, property, prefix)
554
- )
555
- throw new Themes.InvalidError(
556
- 'Named stylesheet reference is incompatible with this descriptor.',
557
- node,
558
- )
559
- return name ?? tokens.get(node.start)?.reference
560
- })
561
- if (template !== undefined) return template
562
- }
563
-
564
- if (node.type === 'ArrayExpression')
565
- return node.elements.map((element) => {
566
- if (!element || element.type === 'SpreadElement')
567
- throw new Themes.InvalidError(
568
- 'Contribution arrays require dense literal entries.',
569
- node,
570
- )
571
- return value(element, property)
572
- })
573
-
574
- if (node.type !== 'ObjectExpression')
575
- throw new Themes.InvalidError(
576
- 'Stylesheet contributions require literal data.',
577
- node,
578
- )
579
-
580
- const output: Record<string, unknown> = Object.create(null)
581
-
582
- for (const property of node.properties) {
583
- if (
584
- property.type !== 'Property' ||
585
- (property.computed && !scanned.queryKeys.has(property.key.start)) ||
586
- property.shorthand ||
587
- property.method ||
588
- property.kind !== 'init'
589
- )
590
- throw new Themes.InvalidError(
591
- 'Contribution objects require explicit literal properties.',
592
- property,
593
- )
594
-
595
- const key =
596
- scanned.queryKeys.get(property.key.start) ??
597
- (property.key.type === 'Identifier'
598
- ? property.key.name
599
- : property.key.type === 'Literal' &&
600
- typeof property.key.value === 'string'
601
- ? property.key.value
602
- : undefined)
603
- if (key === undefined || Object.hasOwn(output, key))
604
- throw new Themes.InvalidError(
605
- 'Contribution keys must be unique literal strings.',
606
- property,
607
- )
608
- const item = Expression.unwrap(property.value)
609
- const name = scanned.references.get(item.start)
610
- const kind = name ? scanned.kinds.get(name) : undefined
611
- if (kind && !RuleReference.accepts(kind, key))
612
- throw new Themes.InvalidError(
613
- 'Named stylesheet reference is incompatible with this descriptor.',
614
- item,
615
- )
616
- output[key] = value(property.value, key)
617
- }
618
-
619
- return output
620
- }
621
-
622
- function record(value: unknown): Record<string, unknown> {
623
- if (!value || typeof value !== 'object' || Array.isArray(value))
624
- throw new Error('Expected a contribution object.')
625
-
626
- return value as Record<string, unknown>
627
- }
628
-
629
- function style(value: unknown): Style.NamedStyle {
630
- return (
631
- Style.define as unknown as (
632
- styles: Record<string, unknown>,
633
- ) => Style.Definition
634
- )({ contribution: record(value) }).styles[0]!
635
- }
636
- function grouping(selector: string): boolean {
637
- if (!selector.startsWith('@')) return false
638
- if (!Condition.is(selector))
639
- throw new Error('Expected a selector or supported grouping rule.')
640
- if (
641
- /^@container(?=[\t\n\r\f (/])/.test(selector) &&
642
- !selector.includes('(')
643
- )
644
- throw new Error(
645
- 'Container rules require a query after the optional name.',
646
- )
647
- Condition.normalize(selector)
648
- return true
649
- }
650
-
651
- function global(input: unknown): Style.NamedStyle {
652
- return {
653
- name: 'global',
654
- declarations: [],
655
- rules: Object.entries(record(input)).map(([selector, input]) => ({
656
- condition: selector,
657
- style: grouping(selector) ? global(input) : style(input),
658
- })),
659
- }
660
- }
661
-
662
- function definitions(
663
- input: unknown,
664
- headers: readonly string[] = [],
665
- ): { input: unknown; headers: readonly string[] }[] {
666
- const entries = Object.entries(record(input))
667
- const groups = new Set(
668
- entries
669
- .filter(
670
- ([key]) =>
671
- key === '@layer' ||
672
- /^@(media|supports|container|layer)(?=[\t\n\r\f (/])/.test(key),
673
- )
674
- .map(([key]) => key),
675
- )
676
- const descriptors = entries.filter(([key]) => !groups.has(key))
677
- const body = Object.fromEntries(descriptors)
678
- if (!entries.length) return [{ input: body, headers }]
679
-
680
- // A complete descriptor block occupies its first descriptor's authored position.
681
- return entries.flatMap(([header, child]) =>
682
- groups.has(header)
683
- ? definitions(child, [...headers, Condition.normalize(header)])
684
- : header === descriptors[0]?.[0]
685
- ? [{ input: body, headers }]
686
- : [],
687
- )
688
- }
689
-
690
- for (const call of scanned.calls) {
691
- try {
692
- const argument = value(call.argument)
693
- if (call.name && !call.exported && !scanned.used.has(call.name)) continue
694
- const contextValue = call.context ? value(call.context) : undefined
695
- if (
696
- contextValue !== undefined &&
697
- Object.keys(record(contextValue)).some((key) => key !== 'id')
698
- )
699
- throw new Error(
700
- 'Unknown contribution context option. Use nested at-rule keys for enclosing groups.',
701
- )
702
-
703
- const inputs = [
704
- 'importCss',
705
- 'namespace',
706
- 'customMedia',
707
- 'layers',
708
- 'global',
709
- ].includes(call.kind)
710
- ? [{ input: argument, headers: [] }]
711
- : definitions(argument)
712
- let functionSignature: string | undefined
713
- for (const { input, headers } of inputs) {
714
- const before = result.length
715
- if (call.kind === 'importCss') {
716
- const options = record(input)
717
- if (
718
- Object.keys(options).some(
719
- (key) => !['layer', 'media', 'supports', 'url'].includes(key),
720
- ) ||
721
- typeof options.url !== 'string' ||
722
- Object.entries(options).some(
723
- ([key, value]) =>
724
- key !== 'url' &&
725
- value !== undefined &&
726
- typeof value !== 'string' &&
727
- !(key === 'layer' && value === true),
728
- )
729
- )
730
- throw new Error(
731
- 'Expected a stylesheet URL and static import conditions.',
732
- )
733
- result.push({
734
- kind: 'import',
735
- url: options.url,
736
- ...(options.layer !== undefined
737
- ? { layer: options.layer as string | true }
738
- : {}),
739
- ...(options.supports !== undefined
740
- ? { supports: options.supports as string }
741
- : {}),
742
- ...(options.media !== undefined
743
- ? { media: options.media as string }
744
- : {}),
745
- })
746
- } else if (call.kind === 'namespace') {
747
- const options = record(input)
748
- if (
749
- Object.keys(options).some(
750
- (key) => !['prefix', 'uri'].includes(key),
751
- ) ||
752
- typeof options.uri !== 'string' ||
753
- (options.prefix !== undefined &&
754
- (typeof options.prefix !== 'string' ||
755
- Identifiers.read(options.prefix) === undefined))
756
- )
757
- throw new Error(
758
- 'Expected a namespace URI and optional identifier prefix.',
759
- )
760
- result.push({
761
- kind: 'namespace',
762
- uri: options.uri,
763
- ...(options.prefix !== undefined
764
- ? { prefix: options.prefix as string }
765
- : {}),
766
- name: `z-n${scanned.namespace}-${call.start.toString(36)}`,
767
- })
768
- } else if (call.kind === 'customMedia') {
769
- if (typeof input !== 'string' && typeof input !== 'boolean')
770
- throw new Error('Expected a media query or boolean.')
771
- if (typeof input === 'string') Descriptors.media(input)
772
- result.push({ kind: 'custom-media', name: call.name!, query: input })
773
- } else if (call.kind === 'cssFunction') {
774
- const options = record(input)
775
- if (
776
- Object.keys(options).some(
777
- (key) => !['body', 'parameters', 'returns'].includes(key),
778
- ) ||
779
- !Array.isArray(options.parameters) ||
780
- (options.returns !== undefined &&
781
- (typeof options.returns !== 'string' ||
782
- !FunctionSyntax.accepts(options.returns)))
783
- )
784
- throw new Error(
785
- 'Expected CSS function parameters, body, and optional return syntax.',
786
- )
787
- const names = new Set<string>()
788
- const parameters = options.parameters.map((input) => {
789
- const parameter = record(input)
790
- if (
791
- Object.keys(parameter).some(
792
- (key) => !['default', 'name', 'syntax'].includes(key),
793
- ) ||
794
- typeof parameter.name !== 'string' ||
795
- !dashed(parameter.name) ||
796
- names.has(Identifiers.read(parameter.name)!) ||
797
- (parameter.syntax !== undefined &&
798
- (typeof parameter.syntax !== 'string' ||
799
- !FunctionSyntax.accepts(parameter.syntax))) ||
800
- (parameter.default !== undefined &&
801
- typeof parameter.default !== 'string' &&
802
- typeof parameter.default !== 'number')
803
- )
804
- throw new Error(
805
- 'Expected unique CSS parameters with supported syntaxes and scalar defaults.',
806
- )
807
- names.add(Identifiers.read(parameter.name)!)
808
- if (parameter.default !== undefined)
809
- Functions.value(
810
- String(parameter.syntax ?? '*'),
811
- String(parameter.default),
812
- )
813
- return `${parameter.name}${parameter.syntax ? ` ${parameter.syntax === '*' ? 'type(*)' : parameter.syntax}` : ''}${parameter.default !== undefined ? `: ${parameter.default}` : ''}`
814
- })
815
- function body(input: unknown): readonly Block.Entry[] {
816
- return Object.entries(record(input)).flatMap(
817
- ([key, value]): Block.Entry[] => {
818
- if (value === undefined) return []
819
- if (/^@(media|supports|container) /.test(key))
820
- return [
821
- {
822
- kind: 'block',
823
- header: Condition.normalize(key),
824
- entries: body(value),
825
- },
826
- ]
827
- if (
828
- (key !== 'result' && !dashed(key)) ||
829
- (typeof value !== 'string' && typeof value !== 'number')
830
- )
831
- throw new Error(
832
- 'CSS function bodies accept result, local variables, and conditional groups.',
833
- )
834
- Functions.value('*', String(value))
835
- return [{ kind: 'descriptor', name: key, value }]
836
- },
837
- )
838
- }
839
- const signature = JSON.stringify({
840
- parameters: options.parameters.map((input) => {
841
- const parameter = record(input)
842
- return [
843
- parameter.name,
844
- parameter.syntax ?? '*',
845
- parameter.default,
846
- ]
847
- }),
848
- returns: options.returns ?? '*',
849
- })
850
- if (
851
- functionSignature !== undefined &&
852
- functionSignature !== signature
853
- )
854
- throw new Error(
855
- 'Grouped CSS function definitions must use the same parameters and return syntax.',
856
- )
857
- functionSignature = signature
858
- call.function!.parameters =
859
- options.parameters as readonly cssFunction.Parameter[]
860
- call.function!.returns = (options.returns ??
861
- '*') as cssFunction.Syntax
862
- result.push({
863
- kind: 'block',
864
- header: `@function ${call.name}(${parameters.join(',')})${options.returns ? ` returns ${options.returns === '*' ? 'type(*)' : options.returns}` : ''}`,
865
- entries: body(options.body),
866
- })
867
- } else if (call.kind === 'layers') {
868
- if (
869
- !Array.isArray(input) ||
870
- input.some((value) => typeof value !== 'string')
871
- )
872
- throw new Error('Layers require a literal string list.')
873
-
874
- result.push({ kind: 'layers', names: input })
875
- } else if (call.kind === 'global') {
876
- for (const [selector, child] of Object.entries(record(input)))
877
- result.push({
878
- kind: 'rule',
879
- selector,
880
- style: grouping(selector) ? global(child) : style(child),
881
- })
882
- } else if (call.kind === 'fontFace') {
883
- const declarations = record(input)
884
-
885
- for (const key of Object.keys(declarations))
886
- if (
887
- declarations[key] === undefined &&
888
- key !== 'fontFamily' &&
889
- key !== 'src'
890
- )
891
- delete declarations[key]
892
-
893
- const keys = [
894
- 'fontFeatureSettings',
895
- 'fontVariationSettings',
896
- 'fontFamily',
897
- 'src',
898
- 'fontDisplay',
899
- 'fontStyle',
900
- 'fontWeight',
901
- 'fontStretch',
902
- 'unicodeRange',
903
- 'sizeAdjust',
904
- 'ascentOverride',
905
- 'descentOverride',
906
- 'lineGapOverride',
907
- ]
908
- if (
909
- typeof declarations.fontFamily !== 'string' ||
910
- typeof declarations.src !== 'string' ||
911
- Object.entries(declarations).some(
912
- ([key, value]) =>
913
- !keys.includes(key) ||
914
- (typeof value !== 'string' && typeof value !== 'number'),
915
- )
916
- )
917
- throw new Error(
918
- 'Font faces require family/source and scalar supported descriptors.',
919
- )
920
-
921
- for (const [name, value] of Object.entries(declarations))
922
- Descriptors.check({ rule: 'font-face', name, value: String(value) })
923
- result.push({
924
- kind: 'font-face',
925
- declarations: declarations as Record<string, string | number>,
926
- })
927
- } else if (call.kind === 'property') {
928
- const options = record(input)
929
- const names =
930
- typeof options.name === 'string'
931
- ? Identifiers.list(options.name, 'space')
932
- : undefined
933
- if (
934
- Object.keys(options).some(
935
- (key) =>
936
- !['name', 'syntax', 'inherits', 'initialValue'].includes(key),
937
- ) ||
938
- names?.length !== 1 ||
939
- !names[0]!.startsWith('--') ||
940
- names[0]!.length <= 2 ||
941
- typeof options.syntax !== 'string' ||
942
- typeof options.inherits !== 'boolean' ||
943
- (options.initialValue !== undefined &&
944
- typeof options.initialValue !== 'string' &&
945
- typeof options.initialValue !== 'number')
946
- )
947
- throw new Error(
948
- 'Expected a native property registration with scalar descriptors.',
949
- )
950
- if (options.initialValue !== undefined)
951
- Properties.initial(String(options.initialValue))
952
- result.push({
953
- kind: 'property',
954
- name: options.name as `--${string}`,
955
- syntax: options.syntax,
956
- inherits: options.inherits,
957
- ...(options.initialValue !== undefined
958
- ? { initialValue: options.initialValue as string | number }
959
- : {}),
960
- })
961
- } else if (call.kind === 'page') {
962
- const options = record(input)
963
- if (
964
- Object.keys(options).some(
965
- (key) => !['descriptors', 'selector'].includes(key),
966
- ) ||
967
- (options.selector !== undefined &&
968
- typeof options.selector !== 'string')
969
- )
970
- throw new Error(
971
- 'Expected page descriptors and an optional selector.',
972
- )
973
- const margins = [
974
- 'top-left-corner',
975
- 'top-left',
976
- 'top-center',
977
- 'top-right',
978
- 'top-right-corner',
979
- 'bottom-left-corner',
980
- 'bottom-left',
981
- 'bottom-center',
982
- 'bottom-right',
983
- 'bottom-right-corner',
984
- 'left-top',
985
- 'left-middle',
986
- 'left-bottom',
987
- 'right-top',
988
- 'right-middle',
989
- 'right-bottom',
990
- ].map((name) => `@${name}`)
991
- const properties =
992
- /^(?!border(?:Collapse|Spacing)$)(?:background|border|font|margin|padding|outline)/
993
- const names = [
994
- 'color',
995
- 'counterIncrement',
996
- 'counterReset',
997
- 'direction',
998
- 'height',
999
- 'letterSpacing',
1000
- 'lineHeight',
1001
- 'maxHeight',
1002
- 'maxWidth',
1003
- 'minHeight',
1004
- 'minWidth',
1005
- 'quotes',
1006
- 'textAlign',
1007
- 'textDecoration',
1008
- 'textIndent',
1009
- 'textTransform',
1010
- 'visibility',
1011
- 'whiteSpace',
1012
- 'width',
1013
- 'wordSpacing',
1014
- ]
1015
- function body(
1016
- input: unknown,
1017
- margin = false,
1018
- ): readonly Block.Entry[] {
1019
- return Object.entries(record(input)).flatMap(
1020
- ([key, value]): Block.Entry[] => {
1021
- if (value === undefined) return []
1022
- if (!margin && margins.includes(key))
1023
- return [
1024
- { kind: 'block', header: key, entries: body(value, true) },
1025
- ]
1026
- if (
1027
- !margin &&
1028
- [
1029
- 'bleed',
1030
- 'marks',
1031
- 'pageMarginSafety',
1032
- 'pageOrientation',
1033
- 'size',
1034
- ].includes(key)
1035
- ) {
1036
- if (typeof value !== 'string' && typeof value !== 'number')
1037
- throw new Error('Expected a scalar page descriptor.')
1038
- Descriptors.check({
1039
- rule: 'page',
1040
- name: key,
1041
- value: String(value),
1042
- })
1043
- return [
1044
- {
1045
- kind: 'descriptor',
1046
- name: key.replace(
1047
- /[A-Z]/g,
1048
- (letter) => `-${letter.toLowerCase()}`,
1049
- ),
1050
- value,
1051
- },
1052
- ]
1053
- }
1054
- if (
1055
- !properties.test(key) &&
1056
- !names.includes(key) &&
1057
- !(
1058
- margin &&
1059
- [
1060
- 'content',
1061
- 'overflow',
1062
- 'unicodeBidi',
1063
- 'verticalAlign',
1064
- 'zIndex',
1065
- ].includes(key)
1066
- )
1067
- )
1068
- throw new Error(
1069
- 'Unsupported page or page-margin declaration.',
1070
- )
1071
- return [{ kind: 'style', style: style({ [key]: value }) }]
1072
- },
1073
- )
1074
- }
1075
- result.push({
1076
- kind: 'block',
1077
- header: Condition.normalize(
1078
- `@page${options.selector ? ` ${options.selector}` : ''}`,
1079
- ),
1080
- entries: body(options.descriptors),
1081
- })
1082
- } else if (call.kind === 'fontFeatureValues') {
1083
- const options = record(input)
1084
- if (
1085
- Object.keys(options).some(
1086
- (key) => !['families', 'features', 'fontDisplay'].includes(key),
1087
- )
1088
- )
1089
- throw new Error('Unknown font-feature-values option.')
1090
- const families = options.families
1091
- if (
1092
- typeof families !== 'string' &&
1093
- (!Array.isArray(families) ||
1094
- !families.length ||
1095
- families.some((value) => typeof value !== 'string'))
1096
- )
1097
- throw new Error('Expected a font family list.')
1098
- const familyList = Array.isArray(families)
1099
- ? families
1100
- .map(
1101
- (value) =>
1102
- '"' +
1103
- Array.from(value as string, (char) => {
1104
- const code = char.codePointAt(0)!
1105
- return code < 32 ||
1106
- code === 127 ||
1107
- char === '"' ||
1108
- char === '\\'
1109
- ? `\\${code.toString(16)} `
1110
- : char
1111
- }).join('') +
1112
- '"',
1113
- )
1114
- .join(',')
1115
- : families
1116
- // Validate the native prelude before shielding newer body descriptors.
1117
- Lightning.transform({
1118
- filename: 'font-feature-values.css',
1119
- code: new TextEncoder().encode(
1120
- `@font-feature-values ${familyList} {}`,
1121
- ),
1122
- })
1123
- const entries: Block.Entry[] = []
1124
- for (const key of Object.keys(options)) {
1125
- if (key === 'fontDisplay' && options.fontDisplay !== undefined) {
1126
- if (
1127
- typeof options.fontDisplay !== 'string' ||
1128
- !['auto', 'block', 'fallback', 'optional', 'swap'].includes(
1129
- Identifiers.list(options.fontDisplay, 'space')
1130
- ?.join(' ')
1131
- .replace(/[A-Z]/g, (letter) => letter.toLowerCase()) ?? '',
1132
- )
1133
- )
1134
- throw new Error('Invalid font display descriptor.')
1135
- entries.push({
1136
- kind: 'descriptor',
1137
- name: 'font-display',
1138
- value: options.fontDisplay,
1139
- })
1140
- }
1141
- if (key !== 'features') continue
1142
- for (const [header, input] of Object.entries(
1143
- record(options.features),
1144
- )) {
1145
- if (input === undefined) continue
1146
- if (
1147
- ![
1148
- '@annotation',
1149
- '@character-variant',
1150
- '@ornaments',
1151
- '@styleset',
1152
- '@stylistic',
1153
- '@swash',
1154
- ].includes(header)
1155
- )
1156
- throw new Error('Unknown font feature block.')
1157
- const declarations: Block.Entry[] = Object.entries(
1158
- record(input),
1159
- ).map(([name, value]) => {
1160
- const values = Array.isArray(value) ? value : [value]
1161
- const maximum =
1162
- header === '@styleset'
1163
- ? Infinity
1164
- : header === '@character-variant'
1165
- ? 2
1166
- : 1
1167
- if (
1168
- !identifier(name) ||
1169
- !values.length ||
1170
- values.length > maximum ||
1171
- values.some(
1172
- (value) =>
1173
- typeof value !== 'number' ||
1174
- !Number.isSafeInteger(value) ||
1175
- value < 0,
1176
- )
1177
- )
1178
- throw new Error(
1179
- 'Expected feature aliases with nonnegative integer indices.',
1180
- )
1181
- return { kind: 'descriptor', name, value: values.join(' ') }
1182
- })
1183
- entries.push({ kind: 'block', header, entries: declarations })
1184
- }
1185
- }
1186
- result.push({
1187
- kind: 'block',
1188
- header: `@font-feature-values ${familyList}`,
1189
- entries,
1190
- })
1191
- } else if (call.kind === 'viewTransition') {
1192
- const entries = Object.entries(record(input)).flatMap(
1193
- ([key, value]): Block.Entry[] => {
1194
- if (value === undefined) return []
1195
- if (
1196
- (key !== 'navigation' && key !== 'types') ||
1197
- typeof value !== 'string' ||
1198
- (key === 'navigation' &&
1199
- !['auto', 'none'].includes(
1200
- Identifiers.list(value, 'space')?.join(' ').toLowerCase() ??
1201
- '',
1202
- ))
1203
- )
1204
- throw new Error(
1205
- 'Expected navigation or types view-transition descriptors.',
1206
- )
1207
- if (key === 'types') {
1208
- const names = Identifiers.list(value, 'space')
1209
- if (
1210
- !names ||
1211
- names.some(
1212
- (name) =>
1213
- [
1214
- 'default',
1215
- 'inherit',
1216
- 'initial',
1217
- 'revert',
1218
- 'revert-layer',
1219
- 'unset',
1220
- ].includes(name.toLowerCase()) ||
1221
- (name.toLowerCase() === 'none' && names.length !== 1),
1222
- )
1223
- )
1224
- throw new Error(
1225
- 'Expected none or a list of view-transition custom identifiers.',
1226
- )
1227
- }
1228
- Functions.value('*', String(value))
1229
- return [{ kind: 'descriptor', name: key, value }]
1230
- },
1231
- )
1232
- result.push({ kind: 'block', header: '@view-transition', entries })
1233
- } else if (call.kind === 'positionTry') {
1234
- const declarations = record(input)
1235
- const keys = [
1236
- 'alignSelf',
1237
- 'blockSize',
1238
- 'bottom',
1239
- 'height',
1240
- 'inlineSize',
1241
- 'inset',
1242
- 'insetBlock',
1243
- 'insetBlockEnd',
1244
- 'insetBlockStart',
1245
- 'insetInline',
1246
- 'insetInlineEnd',
1247
- 'insetInlineStart',
1248
- 'justifySelf',
1249
- 'left',
1250
- 'margin',
1251
- 'marginBlock',
1252
- 'marginBlockEnd',
1253
- 'marginBlockStart',
1254
- 'marginBottom',
1255
- 'marginInline',
1256
- 'marginInlineEnd',
1257
- 'marginInlineStart',
1258
- 'marginLeft',
1259
- 'marginRight',
1260
- 'marginTop',
1261
- 'maxBlockSize',
1262
- 'maxHeight',
1263
- 'maxInlineSize',
1264
- 'maxWidth',
1265
- 'minBlockSize',
1266
- 'minHeight',
1267
- 'minInlineSize',
1268
- 'minWidth',
1269
- 'placeSelf',
1270
- 'positionAnchor',
1271
- 'positionArea',
1272
- 'right',
1273
- 'top',
1274
- 'width',
1275
- ]
1276
- if (Object.keys(declarations).some((key) => !keys.includes(key)))
1277
- throw new Error('Unsupported position-try declaration.')
1278
- const block = style(declarations)
1279
- if (
1280
- block.rules ||
1281
- block.declarations.some((value) => value.important)
1282
- )
1283
- throw new Error(
1284
- 'Position-try forbids nested rules and important declarations.',
1285
- )
1286
- result.push({
1287
- kind: 'rule',
1288
- selector: `@position-try ${call.name}`,
1289
- style: block,
1290
- })
1291
- } else if (
1292
- call.kind === 'colorProfile' ||
1293
- call.kind === 'counterStyle' ||
1294
- call.kind === 'fontPaletteValues'
1295
- ) {
1296
- const descriptors = {
1297
- colorProfile: {
1298
- rule: 'color-profile',
1299
- keys: ['components', 'renderingIntent', 'src'],
1300
- required: ['src'],
1301
- },
1302
- counterStyle: {
1303
- rule: 'counter-style',
1304
- keys: [
1305
- 'additiveSymbols',
1306
- 'fallback',
1307
- 'negative',
1308
- 'pad',
1309
- 'prefix',
1310
- 'range',
1311
- 'speakAs',
1312
- 'suffix',
1313
- 'symbols',
1314
- 'system',
1315
- ],
1316
- required: [],
1317
- },
1318
- fontPaletteValues: {
1319
- rule: 'font-palette-values',
1320
- keys: ['basePalette', 'fontFamily', 'overrideColors'],
1321
- required: ['fontFamily'],
1322
- },
1323
- } as const
1324
- const definition = descriptors[call.kind]
1325
- const declarations = record(input)
1326
- for (const key of Object.keys(declarations))
1327
- if (declarations[key] === undefined) delete declarations[key]
1328
- if (
1329
- definition.required.some(
1330
- (key) => typeof declarations[key] !== 'string',
1331
- ) ||
1332
- Object.entries(declarations).some(
1333
- ([key, value]) =>
1334
- !(definition.keys as readonly string[]).includes(key) ||
1335
- (typeof value !== 'string' &&
1336
- !(
1337
- key === 'basePalette' &&
1338
- typeof value === 'number' &&
1339
- Number.isSafeInteger(value) &&
1340
- value >= 0
1341
- )),
1342
- )
1343
- )
1344
- throw new Error(
1345
- 'Expected supported scalar descriptors and required fields.',
1346
- )
1347
- if (call.kind === 'colorProfile') {
1348
- if (!Identifiers.url(declarations.src as string))
1349
- throw new Error('Color-profile src requires one URL.')
1350
- if (typeof declarations.components === 'string') {
1351
- const names = Identifiers.list(declarations.components, 'comma')
1352
- if (!names || names.some((name) => name.toLowerCase() === 'none'))
1353
- throw new Error(
1354
- 'Color-profile components require comma-separated identifiers other than none.',
1355
- )
1356
- }
1357
- if (typeof declarations.renderingIntent === 'string') {
1358
- const names = Identifiers.list(
1359
- declarations.renderingIntent,
1360
- 'space',
1361
- )
1362
- if (
1363
- names?.length !== 1 ||
1364
- ![
1365
- 'absolute-colorimetric',
1366
- 'relative-colorimetric',
1367
- 'perceptual',
1368
- 'saturation',
1369
- ].includes(names[0]!.toLowerCase())
1370
- )
1371
- throw new Error('Invalid color-profile rendering intent.')
1372
- }
1373
- }
1374
- if (call.kind === 'counterStyle') {
1375
- const system =
1376
- typeof declarations.system === 'string'
1377
- ? Lexical.normalize(declarations.system)
1378
- .trim()
1379
- .replace(/[A-Z]/g, (letter) => letter.toLowerCase())
1380
- : 'symbolic'
1381
- try {
1382
- Descriptors.check({
1383
- rule: 'counter-style',
1384
- name: 'system',
1385
- value:
1386
- typeof declarations.system === 'string'
1387
- ? declarations.system
1388
- : 'symbolic',
1389
- })
1390
- } catch {
1391
- throw new Error(
1392
- 'Expected a supported counter system, with an integer after fixed.',
1393
- )
1394
- }
1395
- const required =
1396
- system === 'additive'
1397
- ? 'additiveSymbols'
1398
- : system.startsWith('extends ')
1399
- ? undefined
1400
- : 'symbols'
1401
- if (
1402
- required &&
1403
- (typeof declarations[required] !== 'string' ||
1404
- !(declarations[required] as string).trim())
1405
- )
1406
- throw new Error(
1407
- 'The counter system requires symbols or additiveSymbols.',
1408
- )
1409
- }
1410
- if (call.kind !== 'colorProfile')
1411
- for (const [name, value] of Object.entries(declarations))
1412
- Descriptors.check({
1413
- rule: definition.rule,
1414
- name,
1415
- value: String(value),
1416
- })
1417
- result.push({
1418
- kind: 'descriptor',
1419
- rule: definition.rule,
1420
- name: call.name!,
1421
- declarations: declarations as Record<string, string | number>,
1422
- })
1423
- } else {
1424
- const frames = Object.entries(record(input)).map(([stop, input]) => {
1425
- if (!Keyframes.accepts(stop))
1426
- throw new Error(
1427
- 'Keyframe stops require from, to, 0–100% offsets, or named timeline percentages.',
1428
- )
1429
-
1430
- const frame = style(input)
1431
- if (
1432
- frame.rules ||
1433
- frame.declarations.some((value) => value.important)
1434
- )
1435
- throw new Error(
1436
- 'Keyframes forbid nested rules and important declarations.',
1437
- )
1438
-
1439
- return { stop, style: frame }
1440
- })
1441
-
1442
- if (call.exported || scanned.used.has(call.name!))
1443
- result.push({ kind: 'keyframes', name: call.name!, frames })
1444
- }
1445
- for (let index = before; index < result.length; index++) {
1446
- if (headers.length)
1447
- result[index] = { ...result[index]!, within: headers }
1448
- starts?.push(call.start)
1449
- }
1450
- }
1451
- } catch (error) {
1452
- throw new Themes.InvalidError((error as Error).message, call)
1453
- }
1454
- }
1455
- for (const input of scanned.functionInputs)
1456
- if (
1457
- input.values.some((value, index) => {
1458
- const syntax = input.signature.parameters[index]?.syntax ?? '*'
1459
- if (value === undefined || !FunctionSyntax.integerOnly(syntax))
1460
- return false
1461
-
1462
- const text = value.trim()
1463
- // List contents retain their existing CSS-text contract; this diagnostic checks scalar tokens.
1464
- if (
1465
- (syntax.includes('+') && /\s/.test(text)) ||
1466
- (syntax.includes('#') && text.includes(','))
1467
- )
1468
- return false
1469
-
1470
- if (!/^[+-]?(?:\d|\.\d)/.test(text) || /^[+-]?\d+$/.test(text))
1471
- return false
1472
-
1473
- const suffix = /^[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?(.+)$/.exec(
1474
- text,
1475
- )?.[1]
1476
- const unit =
1477
- suffix === '%'
1478
- ? suffix
1479
- : suffix && Identifiers.read(suffix)?.toLowerCase()
1480
- const normalized = syntax.trim()
1481
- const body = normalized.startsWith('type(')
1482
- ? normalized.slice(5, -1)
1483
- : normalized
1484
- const alternatives = body.split('|').map((part) => part.trim())
1485
-
1486
- return (
1487
- !unit ||
1488
- !alternatives.some((alternative) => {
1489
- if (alternative === '<percentage>') return unit === '%'
1490
- if (alternative === '<length-percentage>' && unit === '%')
1491
- return true
1492
- if (
1493
- alternative === '<length>' ||
1494
- alternative === '<length-percentage>'
1495
- )
1496
- return (Literal.lengthUnits as readonly string[]).includes(unit)
1497
- if (alternative === '<angle>')
1498
- return ['deg', 'grad', 'rad', 'turn'].includes(unit)
1499
- if (alternative === '<time>') return ['s', 'ms'].includes(unit)
1500
- if (alternative === '<resolution>')
1501
- return ['dpi', 'dpcm', 'dppx', 'x'].includes(unit)
1502
- return false
1503
- })
1504
- )
1505
- })
1506
- )
1507
- throw new Themes.InvalidError(
1508
- 'CSS integer parameters require integer tokens.',
1509
- input.node,
1510
- )
1511
-
1512
- for (const input of scanned.functionInputs) {
1513
- const required =
1514
- input.signature.parameters.findLastIndex(
1515
- (parameter) => parameter.default === undefined,
1516
- ) + 1
1517
- if (
1518
- input.values.length < required ||
1519
- input.values.length > input.signature.parameters.length
1520
- )
1521
- throw new Themes.InvalidError(
1522
- 'CSS function arguments must match the declared parameter count.',
1523
- input.node,
1524
- )
1525
- for (const [index, value] of input.values.entries()) {
1526
- if (value === undefined) continue
1527
- try {
1528
- Functions.value(input.signature.parameters[index]?.syntax ?? '*', value)
1529
- } catch (error) {
1530
- throw new Themes.InvalidError((error as Error).message, input.node)
1531
- }
1532
- }
1533
- }
1534
- return result
1535
- }
1536
-
1537
- function dashed(value: string): boolean {
1538
- const name = Identifiers.read(value)
1539
- return name !== undefined && name.startsWith('--') && name.length > 2
1540
- }
1541
-
1542
- // CSS identifiers allow non-ASCII code points and escaped identifier characters.
1543
- function identifier(value: string): boolean {
1544
- const escape = String.raw`\\(?:[\da-fA-F]{1,6}(?:\r\n|[ \t\n\r\f])?|[^\n\r\f\da-fA-F])`
1545
- const start = `(?:[_a-zA-Z\\u0080-\\u{10FFFF}]|${escape})`
1546
- const rest = `(?:[_a-zA-Z0-9-\\u0080-\\u{10FFFF}]|${escape})`
1547
- if (!new RegExp(`^(?:--|-?${start})${rest}*$`, 'u').test(value)) return false
1548
- const decoded = value.replace(
1549
- /\\([\da-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?|\\([^\n\r\f])/g,
1550
- (_, hex: string | undefined, char: string) =>
1551
- hex ? String.fromCodePoint(Math.min(parseInt(hex, 16), 0x10ffff)) : char,
1552
- )
1553
- return ![
1554
- 'default',
1555
- 'inherit',
1556
- 'initial',
1557
- 'revert',
1558
- 'revert-layer',
1559
- 'unset',
1560
- ].includes(decoded.toLowerCase())
1561
- }