@velarscript/web 0.30.1 → 0.32.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 (385) hide show
  1. package/README.md +1 -1
  2. package/dist/analysis/calls/intrinsics/config.d.ts +10 -0
  3. package/dist/analysis/calls/intrinsics/config.d.ts.map +1 -0
  4. package/dist/analysis/calls/intrinsics/config.js +19 -0
  5. package/dist/analysis/calls/intrinsics/config.js.map +1 -0
  6. package/dist/analysis/calls/intrinsics/forms.d.ts +10 -0
  7. package/dist/analysis/calls/intrinsics/forms.d.ts.map +1 -0
  8. package/dist/analysis/calls/intrinsics/forms.js +38 -0
  9. package/dist/analysis/calls/intrinsics/forms.js.map +1 -0
  10. package/dist/analysis/calls/intrinsics/http.d.ts +10 -0
  11. package/dist/analysis/calls/intrinsics/http.d.ts.map +1 -0
  12. package/dist/analysis/calls/intrinsics/http.js +37 -0
  13. package/dist/analysis/calls/intrinsics/http.js.map +1 -0
  14. package/dist/analysis/calls/intrinsics/reactive.d.ts +10 -0
  15. package/dist/analysis/calls/intrinsics/reactive.d.ts.map +1 -0
  16. package/dist/analysis/calls/intrinsics/reactive.js +30 -0
  17. package/dist/analysis/calls/intrinsics/reactive.js.map +1 -0
  18. package/dist/analysis/calls/intrinsics/storage.d.ts +12 -0
  19. package/dist/analysis/calls/intrinsics/storage.d.ts.map +1 -0
  20. package/dist/analysis/calls/intrinsics/storage.js +86 -0
  21. package/dist/analysis/calls/intrinsics/storage.js.map +1 -0
  22. package/dist/analysis/calls/intrinsics/web.d.ts +10 -0
  23. package/dist/analysis/calls/intrinsics/web.d.ts.map +1 -0
  24. package/dist/analysis/calls/intrinsics/web.js +100 -0
  25. package/dist/analysis/calls/intrinsics/web.js.map +1 -0
  26. package/dist/analysis/calls/intrinsics.d.ts +13 -0
  27. package/dist/analysis/calls/intrinsics.d.ts.map +1 -0
  28. package/dist/analysis/calls/intrinsics.js +31 -0
  29. package/dist/analysis/calls/intrinsics.js.map +1 -0
  30. package/dist/analysis/components/contracts.d.ts +19 -0
  31. package/dist/analysis/components/contracts.d.ts.map +1 -0
  32. package/dist/analysis/components/contracts.js +83 -0
  33. package/dist/analysis/components/contracts.js.map +1 -0
  34. package/dist/analysis/components/declaration.d.ts +4 -0
  35. package/dist/analysis/components/declaration.d.ts.map +1 -0
  36. package/dist/analysis/components/declaration.js +163 -0
  37. package/dist/analysis/components/declaration.js.map +1 -0
  38. package/dist/analysis/components/host.d.ts +61 -0
  39. package/dist/analysis/components/host.d.ts.map +1 -0
  40. package/dist/analysis/components/host.js +22 -0
  41. package/dist/analysis/components/host.js.map +1 -0
  42. package/dist/analysis/components/lifecycle.d.ts +22 -0
  43. package/dist/analysis/components/lifecycle.d.ts.map +1 -0
  44. package/dist/analysis/components/lifecycle.js +33 -0
  45. package/dist/analysis/components/lifecycle.js.map +1 -0
  46. package/dist/analysis/components/ownership.d.ts +74 -0
  47. package/dist/analysis/components/ownership.d.ts.map +1 -0
  48. package/dist/analysis/components/ownership.js +106 -0
  49. package/dist/analysis/components/ownership.js.map +1 -0
  50. package/dist/analysis/declarations.d.ts +99 -0
  51. package/dist/analysis/declarations.d.ts.map +1 -0
  52. package/dist/analysis/declarations.js +176 -0
  53. package/dist/analysis/declarations.js.map +1 -0
  54. package/dist/analysis/inference.d.ts +67 -0
  55. package/dist/analysis/inference.d.ts.map +1 -0
  56. package/dist/analysis/inference.js +236 -0
  57. package/dist/analysis/inference.js.map +1 -0
  58. package/dist/analysis/jsx/attributes.d.ts +103 -0
  59. package/dist/analysis/jsx/attributes.d.ts.map +1 -0
  60. package/dist/analysis/jsx/attributes.js +601 -0
  61. package/dist/analysis/jsx/attributes.js.map +1 -0
  62. package/dist/analysis/jsx/elements.d.ts +17 -0
  63. package/dist/analysis/jsx/elements.d.ts.map +1 -0
  64. package/dist/analysis/jsx/elements.js +213 -0
  65. package/dist/analysis/jsx/elements.js.map +1 -0
  66. package/dist/analysis/jsx/host.d.ts +61 -0
  67. package/dist/analysis/jsx/host.d.ts.map +1 -0
  68. package/dist/analysis/jsx/host.js +15 -0
  69. package/dist/analysis/jsx/host.js.map +1 -0
  70. package/dist/analysis/jsx/keys.d.ts +15 -0
  71. package/dist/analysis/jsx/keys.d.ts.map +1 -0
  72. package/dist/analysis/jsx/keys.js +67 -0
  73. package/dist/analysis/jsx/keys.js.map +1 -0
  74. package/dist/analysis/jsx/security.d.ts +29 -0
  75. package/dist/analysis/jsx/security.d.ts.map +1 -0
  76. package/dist/analysis/jsx/security.js +61 -0
  77. package/dist/analysis/jsx/security.js.map +1 -0
  78. package/dist/analysis/jsx-detection.d.ts +49 -0
  79. package/dist/analysis/jsx-detection.d.ts.map +1 -0
  80. package/dist/analysis/jsx-detection.js +196 -0
  81. package/dist/analysis/jsx-detection.js.map +1 -0
  82. package/dist/analysis/keyed-rebuild.d.ts +148 -0
  83. package/dist/analysis/keyed-rebuild.d.ts.map +1 -0
  84. package/dist/analysis/keyed-rebuild.js +299 -0
  85. package/dist/analysis/keyed-rebuild.js.map +1 -0
  86. package/dist/analysis/keyframes-analysis.d.ts +6 -0
  87. package/dist/analysis/keyframes-analysis.d.ts.map +1 -0
  88. package/dist/analysis/keyframes-analysis.js +79 -0
  89. package/dist/analysis/keyframes-analysis.js.map +1 -0
  90. package/dist/analysis/look/builders.d.ts +29 -0
  91. package/dist/analysis/look/builders.d.ts.map +1 -0
  92. package/dist/analysis/look/builders.js +168 -0
  93. package/dist/analysis/look/builders.js.map +1 -0
  94. package/dist/analysis/look/conditions.d.ts +13 -0
  95. package/dist/analysis/look/conditions.d.ts.map +1 -0
  96. package/dist/analysis/look/conditions.js +84 -0
  97. package/dist/analysis/look/conditions.js.map +1 -0
  98. package/dist/analysis/look/edits.d.ts +25 -0
  99. package/dist/analysis/look/edits.d.ts.map +1 -0
  100. package/dist/analysis/look/edits.js +57 -0
  101. package/dist/analysis/look/edits.js.map +1 -0
  102. package/dist/analysis/look/entries.d.ts +56 -0
  103. package/dist/analysis/look/entries.d.ts.map +1 -0
  104. package/dist/analysis/look/entries.js +223 -0
  105. package/dist/analysis/look/entries.js.map +1 -0
  106. package/dist/analysis/look/host.d.ts +47 -0
  107. package/dist/analysis/look/host.d.ts.map +1 -0
  108. package/dist/analysis/look/host.js +16 -0
  109. package/dist/analysis/look/host.js.map +1 -0
  110. package/dist/analysis/look/tokens.d.ts +75 -0
  111. package/dist/analysis/look/tokens.d.ts.map +1 -0
  112. package/dist/analysis/look/tokens.js +151 -0
  113. package/dist/analysis/look/tokens.js.map +1 -0
  114. package/dist/analysis/look/values.d.ts +45 -0
  115. package/dist/analysis/look/values.d.ts.map +1 -0
  116. package/dist/analysis/look/values.js +148 -0
  117. package/dist/analysis/look/values.js.map +1 -0
  118. package/dist/analysis/look-conditions.d.ts +47 -0
  119. package/dist/analysis/look-conditions.d.ts.map +1 -0
  120. package/dist/analysis/look-conditions.js +157 -0
  121. package/dist/analysis/look-conditions.js.map +1 -0
  122. package/dist/analysis/look-sites.d.ts +54 -0
  123. package/dist/analysis/look-sites.d.ts.map +1 -0
  124. package/dist/analysis/look-sites.js +104 -0
  125. package/dist/analysis/look-sites.js.map +1 -0
  126. package/dist/analysis/look-vocabulary-guidance.d.ts +76 -0
  127. package/dist/analysis/look-vocabulary-guidance.d.ts.map +1 -0
  128. package/dist/analysis/look-vocabulary-guidance.js +338 -0
  129. package/dist/analysis/look-vocabulary-guidance.js.map +1 -0
  130. package/dist/analysis/media-conditions.d.ts +33 -0
  131. package/dist/analysis/media-conditions.d.ts.map +1 -0
  132. package/dist/analysis/media-conditions.js +92 -0
  133. package/dist/analysis/media-conditions.js.map +1 -0
  134. package/dist/analysis/program-passes.d.ts +106 -0
  135. package/dist/analysis/program-passes.d.ts.map +1 -0
  136. package/dist/analysis/program-passes.js +149 -0
  137. package/dist/analysis/program-passes.js.map +1 -0
  138. package/dist/analysis/reactivity/bindings.d.ts +37 -0
  139. package/dist/analysis/reactivity/bindings.d.ts.map +1 -0
  140. package/dist/analysis/reactivity/bindings.js +88 -0
  141. package/dist/analysis/reactivity/bindings.js.map +1 -0
  142. package/dist/analysis/reactivity/derived.d.ts +39 -0
  143. package/dist/analysis/reactivity/derived.d.ts.map +1 -0
  144. package/dist/analysis/reactivity/derived.js +80 -0
  145. package/dist/analysis/reactivity/derived.js.map +1 -0
  146. package/dist/analysis/reactivity/host.d.ts +38 -0
  147. package/dist/analysis/reactivity/host.d.ts.map +1 -0
  148. package/dist/analysis/reactivity/host.js +14 -0
  149. package/dist/analysis/reactivity/host.js.map +1 -0
  150. package/dist/analysis/reactivity/retired-accessors.d.ts +82 -0
  151. package/dist/analysis/reactivity/retired-accessors.d.ts.map +1 -0
  152. package/dist/analysis/reactivity/retired-accessors.js +118 -0
  153. package/dist/analysis/reactivity/retired-accessors.js.map +1 -0
  154. package/dist/analysis/renderable.d.ts +47 -0
  155. package/dist/analysis/renderable.d.ts.map +1 -0
  156. package/dist/analysis/renderable.js +105 -0
  157. package/dist/analysis/renderable.js.map +1 -0
  158. package/dist/analysis/routes.d.ts +64 -0
  159. package/dist/analysis/routes.d.ts.map +1 -0
  160. package/dist/analysis/routes.js +182 -0
  161. package/dist/analysis/routes.js.map +1 -0
  162. package/dist/analysis/url-attributes.d.ts +12 -0
  163. package/dist/analysis/url-attributes.d.ts.map +1 -0
  164. package/dist/analysis/url-attributes.js +48 -0
  165. package/dist/analysis/url-attributes.js.map +1 -0
  166. package/dist/analysis/watch-cycles.d.ts +102 -0
  167. package/dist/analysis/watch-cycles.d.ts.map +1 -1
  168. package/dist/analysis/watch-cycles.js +240 -1
  169. package/dist/analysis/watch-cycles.js.map +1 -1
  170. package/dist/analysis/watch-subject.d.ts +45 -0
  171. package/dist/analysis/watch-subject.d.ts.map +1 -0
  172. package/dist/analysis/watch-subject.js +272 -0
  173. package/dist/analysis/watch-subject.js.map +1 -0
  174. package/dist/analysis/web-types.d.ts +27 -0
  175. package/dist/analysis/web-types.d.ts.map +1 -0
  176. package/dist/analysis/web-types.js +170 -0
  177. package/dist/analysis/web-types.js.map +1 -0
  178. package/dist/analyzer.d.ts +18 -617
  179. package/dist/analyzer.d.ts.map +1 -1
  180. package/dist/analyzer.js +208 -4853
  181. package/dist/analyzer.js.map +1 -1
  182. package/dist/compiler.d.ts +20 -0
  183. package/dist/compiler.d.ts.map +1 -1
  184. package/dist/compiler.js +29 -864
  185. package/dist/compiler.js.map +1 -1
  186. package/dist/documentation.d.ts +28 -0
  187. package/dist/documentation.d.ts.map +1 -0
  188. package/dist/documentation.js +67 -0
  189. package/dist/documentation.js.map +1 -0
  190. package/dist/editor/completions.d.ts +12 -0
  191. package/dist/editor/completions.d.ts.map +1 -0
  192. package/dist/editor/completions.js +6 -0
  193. package/dist/editor/completions.js.map +1 -0
  194. package/dist/editor/jsx-completion.d.ts +12 -0
  195. package/dist/editor/jsx-completion.d.ts.map +1 -0
  196. package/dist/editor/jsx-completion.js +137 -0
  197. package/dist/editor/jsx-completion.js.map +1 -0
  198. package/dist/editor/look-completion.d.ts +29 -0
  199. package/dist/editor/look-completion.d.ts.map +1 -0
  200. package/dist/editor/look-completion.js +159 -0
  201. package/dist/editor/look-completion.js.map +1 -0
  202. package/dist/editor.d.ts.map +1 -1
  203. package/dist/editor.js +12 -288
  204. package/dist/editor.js.map +1 -1
  205. package/dist/emit/components.d.ts +33 -0
  206. package/dist/emit/components.d.ts.map +1 -0
  207. package/dist/emit/components.js +135 -0
  208. package/dist/emit/components.js.map +1 -0
  209. package/dist/emit/jsx.d.ts +57 -0
  210. package/dist/emit/jsx.d.ts.map +1 -0
  211. package/dist/emit/jsx.js +384 -0
  212. package/dist/emit/jsx.js.map +1 -0
  213. package/dist/emit/look-css.d.ts +44 -0
  214. package/dist/emit/look-css.d.ts.map +1 -0
  215. package/dist/emit/look-css.js +177 -0
  216. package/dist/emit/look-css.js.map +1 -0
  217. package/dist/emit/look.d.ts +39 -0
  218. package/dist/emit/look.d.ts.map +1 -0
  219. package/dist/emit/look.js +218 -0
  220. package/dist/emit/look.js.map +1 -0
  221. package/dist/emit/runtime-imports.d.ts +40 -0
  222. package/dist/emit/runtime-imports.d.ts.map +1 -0
  223. package/dist/emit/runtime-imports.js +184 -0
  224. package/dist/emit/runtime-imports.js.map +1 -0
  225. package/dist/emitter.d.ts +15 -68
  226. package/dist/emitter.d.ts.map +1 -1
  227. package/dist/emitter.js +79 -1008
  228. package/dist/emitter.js.map +1 -1
  229. package/dist/look/animation.d.ts +7 -0
  230. package/dist/look/animation.d.ts.map +1 -0
  231. package/dist/look/animation.js +13 -0
  232. package/dist/look/animation.js.map +1 -0
  233. package/dist/look/builders.d.ts +35 -0
  234. package/dist/look/builders.d.ts.map +1 -0
  235. package/dist/look/builders.js +74 -0
  236. package/dist/look/builders.js.map +1 -0
  237. package/dist/look/css-functions.d.ts +28 -0
  238. package/dist/look/css-functions.d.ts.map +1 -0
  239. package/dist/look/css-functions.js +54 -0
  240. package/dist/look/css-functions.js.map +1 -0
  241. package/dist/look/keyword-sets.d.ts +41 -0
  242. package/dist/look/keyword-sets.d.ts.map +1 -0
  243. package/dist/look/keyword-sets.js +117 -0
  244. package/dist/look/keyword-sets.js.map +1 -0
  245. package/dist/look/keywords.d.ts +55 -0
  246. package/dist/look/keywords.d.ts.map +1 -0
  247. package/dist/look/keywords.js +387 -0
  248. package/dist/look/keywords.js.map +1 -0
  249. package/dist/look/media.d.ts +19 -0
  250. package/dist/look/media.d.ts.map +1 -0
  251. package/dist/look/media.js +27 -0
  252. package/dist/look/media.js.map +1 -0
  253. package/dist/look/naming.d.ts +9 -0
  254. package/dist/look/naming.d.ts.map +1 -0
  255. package/dist/look/naming.js +45 -0
  256. package/dist/look/naming.js.map +1 -0
  257. package/dist/look/properties.d.ts +16 -0
  258. package/dist/look/properties.d.ts.map +1 -0
  259. package/dist/look/properties.js +52 -0
  260. package/dist/look/properties.js.map +1 -0
  261. package/dist/look/shorthands.d.ts +25 -0
  262. package/dist/look/shorthands.d.ts.map +1 -0
  263. package/dist/look/shorthands.js +130 -0
  264. package/dist/look/shorthands.js.map +1 -0
  265. package/dist/look/tokens.d.ts +38 -0
  266. package/dist/look/tokens.d.ts.map +1 -0
  267. package/dist/look/tokens.js +59 -0
  268. package/dist/look/tokens.js.map +1 -0
  269. package/dist/look/units.d.ts +24 -0
  270. package/dist/look/units.d.ts.map +1 -0
  271. package/dist/look/units.js +33 -0
  272. package/dist/look/units.js.map +1 -0
  273. package/dist/look.d.ts +32 -227
  274. package/dist/look.d.ts.map +1 -1
  275. package/dist/look.js +28 -933
  276. package/dist/look.js.map +1 -1
  277. package/dist/module-roster.d.ts +16 -0
  278. package/dist/module-roster.d.ts.map +1 -0
  279. package/dist/module-roster.js +28 -0
  280. package/dist/module-roster.js.map +1 -0
  281. package/dist/modules/app.d.ts +9 -0
  282. package/dist/modules/app.d.ts.map +1 -0
  283. package/dist/modules/app.js +20 -0
  284. package/dist/modules/app.js.map +1 -0
  285. package/dist/modules/browser-test.d.ts +9 -0
  286. package/dist/modules/browser-test.d.ts.map +1 -0
  287. package/dist/modules/browser-test.js +67 -0
  288. package/dist/modules/browser-test.js.map +1 -0
  289. package/dist/modules/browser.d.ts +10 -0
  290. package/dist/modules/browser.d.ts.map +1 -0
  291. package/dist/modules/browser.js +89 -0
  292. package/dist/modules/browser.js.map +1 -0
  293. package/dist/modules/config.d.ts +9 -0
  294. package/dist/modules/config.d.ts.map +1 -0
  295. package/dist/modules/config.js +14 -0
  296. package/dist/modules/config.js.map +1 -0
  297. package/dist/modules/files.d.ts +9 -0
  298. package/dist/modules/files.d.ts.map +1 -0
  299. package/dist/modules/files.js +16 -0
  300. package/dist/modules/files.js.map +1 -0
  301. package/dist/modules/forms.d.ts +9 -0
  302. package/dist/modules/forms.d.ts.map +1 -0
  303. package/dist/modules/forms.js +26 -0
  304. package/dist/modules/forms.js.map +1 -0
  305. package/dist/modules/http.d.ts +9 -0
  306. package/dist/modules/http.d.ts.map +1 -0
  307. package/dist/modules/http.js +127 -0
  308. package/dist/modules/http.js.map +1 -0
  309. package/dist/modules/look.d.ts +9 -0
  310. package/dist/modules/look.d.ts.map +1 -0
  311. package/dist/modules/look.js +58 -0
  312. package/dist/modules/look.js.map +1 -0
  313. package/dist/modules/realtime.d.ts +9 -0
  314. package/dist/modules/realtime.d.ts.map +1 -0
  315. package/dist/modules/realtime.js +163 -0
  316. package/dist/modules/realtime.js.map +1 -0
  317. package/dist/modules/storage.d.ts +9 -0
  318. package/dist/modules/storage.d.ts.map +1 -0
  319. package/dist/modules/storage.js +59 -0
  320. package/dist/modules/storage.js.map +1 -0
  321. package/dist/modules/types.d.ts +78 -0
  322. package/dist/modules/types.d.ts.map +1 -0
  323. package/dist/modules/types.js +154 -0
  324. package/dist/modules/types.js.map +1 -0
  325. package/dist/modules/web.d.ts +9 -0
  326. package/dist/modules/web.d.ts.map +1 -0
  327. package/dist/modules/web.js +63 -0
  328. package/dist/modules/web.js.map +1 -0
  329. package/dist/modules/websocket.d.ts +11 -0
  330. package/dist/modules/websocket.d.ts.map +1 -0
  331. package/dist/modules/websocket.js +75 -0
  332. package/dist/modules/websocket.js.map +1 -0
  333. package/dist/parser/expressions/jsx.d.ts +17 -0
  334. package/dist/parser/expressions/jsx.d.ts.map +1 -0
  335. package/dist/parser/expressions/jsx.js +43 -0
  336. package/dist/parser/expressions/jsx.js.map +1 -0
  337. package/dist/parser/expressions/keyframes.d.ts +15 -0
  338. package/dist/parser/expressions/keyframes.d.ts.map +1 -0
  339. package/dist/parser/expressions/keyframes.js +33 -0
  340. package/dist/parser/expressions/keyframes.js.map +1 -0
  341. package/dist/parser/expressions/look.d.ts +16 -0
  342. package/dist/parser/expressions/look.d.ts.map +1 -0
  343. package/dist/parser/expressions/look.js +37 -0
  344. package/dist/parser/expressions/look.js.map +1 -0
  345. package/dist/parser/expressions/visual-block.d.ts +17 -0
  346. package/dist/parser/expressions/visual-block.d.ts.map +1 -0
  347. package/dist/parser/expressions/visual-block.js +21 -0
  348. package/dist/parser/expressions/visual-block.js.map +1 -0
  349. package/dist/parser/keyframes-source.d.ts +24 -0
  350. package/dist/parser/keyframes-source.d.ts.map +1 -0
  351. package/dist/parser/keyframes-source.js +133 -0
  352. package/dist/parser/keyframes-source.js.map +1 -0
  353. package/dist/parser/look-source.d.ts +25 -0
  354. package/dist/parser/look-source.d.ts.map +1 -0
  355. package/dist/parser/look-source.js +189 -0
  356. package/dist/parser/look-source.js.map +1 -0
  357. package/dist/parser/spans.d.ts +24 -0
  358. package/dist/parser/spans.d.ts.map +1 -0
  359. package/dist/parser/spans.js +113 -0
  360. package/dist/parser/spans.js.map +1 -0
  361. package/dist/parser/statements/components.d.ts +37 -0
  362. package/dist/parser/statements/components.d.ts.map +1 -0
  363. package/dist/parser/statements/components.js +185 -0
  364. package/dist/parser/statements/components.js.map +1 -0
  365. package/dist/parser/statements/heads.d.ts +38 -0
  366. package/dist/parser/statements/heads.d.ts.map +1 -0
  367. package/dist/parser/statements/heads.js +55 -0
  368. package/dist/parser/statements/heads.js.map +1 -0
  369. package/dist/parser/statements/imports.d.ts +9 -0
  370. package/dist/parser/statements/imports.d.ts.map +1 -0
  371. package/dist/parser/statements/imports.js +31 -0
  372. package/dist/parser/statements/imports.js.map +1 -0
  373. package/dist/parser/statements/reactive.d.ts +31 -0
  374. package/dist/parser/statements/reactive.d.ts.map +1 -0
  375. package/dist/parser/statements/reactive.js +68 -0
  376. package/dist/parser/statements/reactive.js.map +1 -0
  377. package/dist/parser/statements/unsafe-css.d.ts +21 -0
  378. package/dist/parser/statements/unsafe-css.d.ts.map +1 -0
  379. package/dist/parser/statements/unsafe-css.js +42 -0
  380. package/dist/parser/statements/unsafe-css.js.map +1 -0
  381. package/dist/parser.d.ts +37 -50
  382. package/dist/parser.d.ts.map +1 -1
  383. package/dist/parser.js +98 -882
  384. package/dist/parser.js.map +1 -1
  385. package/package.json +2 -2
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Every attribute a JSX element can carry: the visual directives (`look`,
3
+ * `look:`, `style:`, `class`), the `bind:` family, the `on:` events and the
4
+ * `on`-prefixed names a native element reserves, `ref`, `key`, and the native
5
+ * attribute names and values the DOM and ARIA rosters answer for.
6
+ *
7
+ * D115 P4 R3b. `analyzeNativeJsxAttribute` stays the one guarded chain the
8
+ * order of these rules lives in — the name check sits at its tail so every
9
+ * directive above owns its own spelling — and the arms whose bodies are long
10
+ * enough to read on their own are named functions below it.
11
+ */
12
+ import { mechanicalFix } from "@velarscript/compiler";
13
+ import { anyType, boolType, describeType, isInvalidType, nonOptional, nullType, numberType, optionalOf, stringType, unknownType } from "@velarscript/compiler/extension";
14
+ import {} from "../../ast.js";
15
+ import { WEB_ARIA_ATTRIBUTES, WEB_ARIA_ENUMERATED_VALUES, WEB_ARIA_ROLE_SYNONYMS, WEB_ARIA_ROLES, WEB_BOOL_PRESENCE_HTML_ATTRIBUTES, WEB_HTML_ELEMENTS, WEB_MISSPELLED_ATTRIBUTES, WEB_NATIVE_ELEMENTS } from "../../elements.js";
16
+ import { nearestLookName } from "../../look.js";
17
+ import { lookContributions } from "../look-conditions.js";
18
+ import { lookLiteralZero, mentionsLookUnitType } from "../look-sites.js";
19
+ import { analyzeLookValue } from "../look/entries.js";
20
+ import { reportLookNumberWithoutUnit } from "../look/values.js";
21
+ import { bindTargetGuidance, diagnostic, htmlEventHandlerAttributes, LOOK_PROPERTY_TYPES, nativeDomEventNames, webEventType } from "../web-types.js";
22
+ import {} from "./host.js";
23
+ import { reportUrlAttributeScheme } from "./security.js";
24
+ export function analyzeNativeJsxAttribute(host, expression, attribute) {
25
+ const value = attribute.value;
26
+ reportUrlAttributeScheme(host, attribute);
27
+ const eventName = attribute.name.startsWith("on:") ? attribute.name.slice(3).split(".")[0] ?? "" : "";
28
+ const expectedEvent = eventName ? webEventType(eventName) : null;
29
+ // GRM-A4: the declared handler type returns null. `() => {}` after a fat
30
+ // arrow is an empty-record factory rather than an empty block, so a handler
31
+ // position that accepted any result silently accepted that record.
32
+ const eventHandlerType = expectedEvent ? { kind: "function", parameters: [expectedEvent], requiredParameters: 1, result: nullType } : null;
33
+ // An event arrow that assigns a state binding from an event field is the
34
+ // hand-rolled spelling of a two-way binding: it receives bind:value
35
+ // guidance and skips ordinary handler inference so the guidance is not
36
+ // buried under a cascade from the recovered assignment.
37
+ const boundState = (eventName || /^on[A-Z]/u.test(attribute.name)) ? eventAssignedStateBinding(host, value) : null;
38
+ if (boundState) {
39
+ host.diagnostics.push(diagnostic("VEL5019", `Use 'bind:value={${boundState}}'; VelarScript binds input state with the bind: directive instead of assigning state from event fields`, attribute.span));
40
+ }
41
+ // A `look=` value is inferred inside its own check so an inline Look block
42
+ // is reported once rather than analyzed as a snapshot as well.
43
+ const inferred = typeof value === "string" ? stringType
44
+ : boundState || attribute.name === "look" ? anyType
45
+ : value ? host.inferExpression(value, eventHandlerType ?? unknownType) : boolType;
46
+ if (attribute.name === "style") {
47
+ host.diagnostics.push(diagnostic("VEL5041", "Raw JSX style is not supported; use style:property for a checked high-priority inline override, or prefer Look for ordinary visuals", attribute.span));
48
+ }
49
+ else if (attribute.name.startsWith("style:")) {
50
+ analyzeInlineVisualAttribute(host, attribute, inferred, "style");
51
+ }
52
+ else if (attribute.name === "look") {
53
+ analyzeJsxLookAttribute(host, attribute);
54
+ }
55
+ else if (attribute.name.startsWith("look:")) {
56
+ analyzeInlineVisualAttribute(host, attribute, inferred, "look");
57
+ }
58
+ else if (attribute.name === "class") {
59
+ if (!host.isClassInput(inferred))
60
+ host.diagnostics.push(diagnostic("VEL5040", `JSX class requires string, string?, or a list of strings; received ${describeType(inferred)}`, attribute.span));
61
+ }
62
+ else if (attribute.name === "unsafe:html") {
63
+ if (!isInvalidType(inferred) && inferred.kind !== "any" && !host.isOptionalString(inferred)) {
64
+ host.diagnostics.push(diagnostic("VEL5047", `unsafe:html requires string or string?, received ${describeType(inferred)}`, attribute.span));
65
+ }
66
+ // D90 coherence-3, one step sideways: `unsafe:` is a closed prefix with
67
+ // exactly one member, so `unsafe:script=` is wrong by construction rather
68
+ // than merely unrecognised — and it used to be emitted verbatim as a dead
69
+ // attribute, which is the worst possible answer for a name that reads like
70
+ // an escape hatch the author believes they opened.
71
+ }
72
+ else if (attribute.name.startsWith("unsafe:")) {
73
+ host.diagnostics.push(diagnostic("VEL5015", `Unknown escape hatch '${attribute.name}'; 'unsafe:html' is the only one an element has, and it takes the HTML text as a string`, attribute.span));
74
+ }
75
+ else if (attribute.name === "bind:value") {
76
+ analyzeBindValue(host, expression, attribute, value, inferred);
77
+ }
78
+ else if (attribute.name === "bind:checked") {
79
+ analyzeBindChecked(host, expression, attribute, value, inferred);
80
+ }
81
+ else if (attribute.name === "bind:group") {
82
+ analyzeBindGroup(host, expression, attribute, value, inferred);
83
+ }
84
+ else if (attribute.name === "ref") {
85
+ analyzeRefAttribute(host, expression, attribute, value);
86
+ // The three branches above are the whole `bind:` family, so a fourth suffix
87
+ // names no binding — and it used to reach the attribute emitter and render
88
+ // a dead `bind:foo` attribute, silently binding nothing. Same shape as the
89
+ // React-spelling rule: a closed VelarScript vocabulary, so an outside name
90
+ // is wrong rather than unknown.
91
+ }
92
+ else if (attribute.name === "bind" || attribute.name.startsWith("bind:")) {
93
+ reportUnknownBinding(host, attribute);
94
+ // WEB-S1: the guard used to be anchored on an uppercase letter, which
95
+ // closed the React reflex `onClick=` and left the lowercase HTML spelling
96
+ // open. Both are the same attribute — an HTML attribute name is matched
97
+ // ASCII-case-insensitively, and `setAttribute` lowercases on an HTML
98
+ // element, so `onClick` and `ONCLICK` reach `onclick` too. That attribute
99
+ // is the one that matters: the browser compiles its value as script, so any
100
+ // string routed there is executable code in the application's origin, which
101
+ // the charter reserves for `unsafe:html`. So a native element reserves the
102
+ // whole `on` prefix by name rather than by a roster of handler names: a
103
+ // roster leaves the next handler spelling open, and every event is written
104
+ // with the `on:` directive anyway. The lookahead keeps that directive out.
105
+ }
106
+ else if (/^on(?!:)/iu.test(attribute.name)) {
107
+ reportReservedOnAttribute(host, attribute);
108
+ }
109
+ else if (attribute.name.startsWith("on:")) {
110
+ analyzeEventDirective(host, attribute, value, inferred, expectedEvent);
111
+ }
112
+ else if (attribute.name.startsWith("class:")) {
113
+ host.requireAssignable(inferred, boolType, attribute.span);
114
+ }
115
+ else if (attribute.name === "key" && !isInvalidType(inferred) && inferred.kind !== "string" && inferred.kind !== "number" && inferred.kind !== "enum" && inferred.kind !== "enumMember" && inferred.kind !== "any") {
116
+ host.diagnostics.push(diagnostic("VEL5022", "A JSX key must be a string, string-backed enum, or number", attribute.span));
117
+ // The name check sits at the tail of the chain so every directive above it
118
+ // — look:, class:, on:, bind:, ref, key — keeps owning its own spelling and
119
+ // is never read as an HTML attribute name. It reports at most once per
120
+ // attribute and short-circuits the value-shape message, so a React spelling
121
+ // is answered with its successor rather than with two half-answers.
122
+ }
123
+ else if (!reportNativeAttributeSpelling(host, expression, attribute)
124
+ && !isInvalidType(inferred) && !host.isJsxAttributeValue(inferred)) {
125
+ host.diagnostics.push(diagnostic("VEL5047", `Native JSX attributes require text, finite numbers, bool, enums, or null; received ${describeType(inferred)}`, attribute.span));
126
+ }
127
+ if (!isInvalidType(inferred))
128
+ adviseNativeBooleanTextConversion(host, expression.tag, attribute);
129
+ if (attribute.name.startsWith("on:click") && !["button", "a", "input", "select", "textarea", "summary"].includes(expression.tag)
130
+ && !expression.attributes.some((item) => item.name === "role"))
131
+ host.diagnostics.push(diagnostic("VEL5023", `Clickable <${expression.tag}> requires an explicit role`, expression.span));
132
+ }
133
+ /**
134
+ * The `look=` attribute on either host kind. A Look block written inline is
135
+ * reported without inferring its entries, so the one directive-level message
136
+ * stands alone; an empty list names the accepted family rather than rendering
137
+ * `List<unknown>` (LOK-I3, LOK-I6).
138
+ */
139
+ export function analyzeJsxLookAttribute(host, attribute) {
140
+ const value = attribute.value;
141
+ if (!value) {
142
+ host.diagnostics.push(diagnostic("VEL5040", "JSX look requires an expression value", attribute.span));
143
+ return;
144
+ }
145
+ if (typeof value === "string") {
146
+ host.diagnostics.push(diagnostic("VEL5040", "JSX look requires an expression value such as look={cardLook}", attribute.span));
147
+ return;
148
+ }
149
+ if (value.kind === "ExtensionExpression:web:look") {
150
+ host.diagnostics.push(diagnostic("VEL5053", "An inline Look block is not supported; use look:property directives for simple overrides or extract a const Look for conditions and targets", attribute.span));
151
+ return;
152
+ }
153
+ if (value.kind === "ListExpression" && value.elements.length === 0) {
154
+ host.diagnostics.push(diagnostic("VEL5040", "JSX look accepts a Look, a Look?, or a list of Look values; an empty list composes nothing — remove the attribute", attribute.span));
155
+ return;
156
+ }
157
+ const actual = host.inferExpression(value);
158
+ if (!host.isLookInput(actual))
159
+ host.diagnostics.push(diagnostic("VEL5040", `JSX look requires Look, Look?, or a list of Look values; received ${describeType(actual)}`, attribute.span));
160
+ if (value.kind === "ListExpression")
161
+ reportIndependentLookCollisions(host, value, attribute);
162
+ }
163
+ /**
164
+ * Composition is how a Look overrides another one: `...baseLook` puts the two
165
+ * in an order the reader can see, and everything after it wins. Two looks
166
+ * placed side by side on one element state no order at all, so a property both
167
+ * of them set has no answer the source gives — the winner used to fall out of
168
+ * whichever rule the stylesheet happened to carry last. The shape is refused
169
+ * rather than ordered by fiat, and the refusal names the spelling that states
170
+ * the order the author meant.
171
+ */
172
+ export function reportIndependentLookCollisions(host, value, attribute) {
173
+ const entries = [];
174
+ for (const element of value.elements) {
175
+ if (element.kind !== "IdentifierExpression")
176
+ continue;
177
+ const look = host.lookDeclarations.get(element.name);
178
+ if (look)
179
+ entries.push({ name: element.name, look });
180
+ }
181
+ for (const [index, first] of entries.entries()) {
182
+ for (const second of entries.slice(index + 1)) {
183
+ if (first.name === second.name)
184
+ continue;
185
+ const left = lookContributions(first.look, host.lookDeclarations);
186
+ const right = lookContributions(second.look, host.lookDeclarations);
187
+ // A look that composes the other is already ordered against it: the
188
+ // spread says which one is the base, so nothing here is ambiguous.
189
+ if (left.composed.has(second.name) || right.composed.has(first.name))
190
+ continue;
191
+ const collision = [...left.properties].find((property) => right.properties.has(property));
192
+ if (collision === undefined)
193
+ continue;
194
+ const property = collision.slice(collision.indexOf(":") + 1);
195
+ const target = collision.slice(0, collision.indexOf(":"));
196
+ host.diagnostics.push(diagnostic("VEL5068", `Look '${first.name}' and Look '${second.name}' both set '${property}'${target ? ` on '@${target}'` : ""}, and placing them side by side states no order between them; write one Look that starts with '...${first.name}' and overrides '${property}' from there, then pass that one`, attribute.span));
197
+ return;
198
+ }
199
+ }
200
+ }
201
+ export function analyzeInlineVisualAttribute(host, attribute, actual, directive) {
202
+ const property = attribute.name.slice(`${directive}:`.length);
203
+ if (!property) {
204
+ host.diagnostics.push(diagnostic("VEL5038", `A ${directive}: directive requires a camelCase Look property name`, attribute.span));
205
+ return;
206
+ }
207
+ if (attribute.value === null) {
208
+ host.diagnostics.push(diagnostic("VEL5040", `JSX ${directive}:${property} requires a string or expression value`, attribute.span));
209
+ return;
210
+ }
211
+ const expression = typeof attribute.value === "string"
212
+ ? { kind: "LiteralExpression", value: attribute.value, raw: JSON.stringify(attribute.value), span: attribute.span }
213
+ : attribute.value;
214
+ if (!analyzeLookValue(host, property, expression, attribute.span, directive))
215
+ return;
216
+ const expected = LOOK_PROPERTY_TYPES.get(property) ?? stringType;
217
+ if (mentionsLookUnitType(expected) && lookLiteralZero(expression))
218
+ return;
219
+ if (reportLookNumberWithoutUnit(host, property, actual, expected, expression.span))
220
+ return;
221
+ if (expected.kind !== "unknown")
222
+ host.requireAssignable(actual, optionalOf(expected), expression.span);
223
+ }
224
+ /**
225
+ * A14: `condition ? "true" : "false"` is the expanded spelling of the Core
226
+ * text conversion `str(condition)`. Keep that conversion explicit at a DOM
227
+ * text boundary instead of teaching every attribute its own coercion rule.
228
+ *
229
+ * The rule does not reach component props or HTML bool-presence attributes.
230
+ * A string "false" keeps such an attribute present; accepting that spelling
231
+ * as canonical would hide a likely presence bug whose direct expression is
232
+ * the bool itself.
233
+ */
234
+ export function adviseNativeBooleanTextConversion(host, tag, attribute) {
235
+ if (!WEB_NATIVE_ELEMENTS.has(tag))
236
+ return;
237
+ const value = attribute.value;
238
+ if (!value || typeof value === "string" || !plainNativeTextAttribute(tag, attribute.name))
239
+ return;
240
+ const written = host.webSourceText.slice(value.span.start, value.span.end);
241
+ if (written.length === 0)
242
+ return;
243
+ if (value.kind === "ConditionalExpression"
244
+ && value.thenValue.kind === "LiteralExpression" && value.thenValue.value === "true"
245
+ && value.elseValue.kind === "LiteralExpression" && value.elseValue.value === "false") {
246
+ if (host.expandAliases(host.inferredExpressionType(value.condition)).kind !== "bool")
247
+ return;
248
+ const replacement = host.webSourceText.slice(value.condition.span.start, value.condition.span.end);
249
+ if (!replacement)
250
+ return;
251
+ // The condition is copied verbatim into str(...), so comments inside it
252
+ // survive. Only comments in discarded punctuation/branches make the
253
+ // mechanical rewrite unsafe.
254
+ const omitted = `${host.webSourceText.slice(value.span.start, value.condition.span.start)}${host.webSourceText.slice(value.condition.span.end, value.span.end)}`;
255
+ host.advise("A14", `Native attribute '${attribute.name}' spells this bool as text; write the equivalent str(condition) conversion directly`, value.span, omitted.includes("//") || omitted.includes("/*")
256
+ ? undefined
257
+ : mechanicalFix(value.span, `str(${replacement})`, `Convert the bool with str() for '${attribute.name}'`));
258
+ }
259
+ }
260
+ /** Directives and compiler-owned control attributes do not use DOM text conversion. */
261
+ function plainNativeTextAttribute(tag, name) {
262
+ if (WEB_MISSPELLED_ATTRIBUTES.has(name) || /^aria[A-Z]/u.test(name))
263
+ return false;
264
+ if (name.startsWith("aria-") && !WEB_ARIA_ATTRIBUTES.has(name))
265
+ return false;
266
+ if (WEB_HTML_ELEMENTS.has(tag) && WEB_BOOL_PRESENCE_HTML_ATTRIBUTES.has(name.toLowerCase()))
267
+ return false;
268
+ return name !== "class" && name !== "host" && name !== "key" && name !== "look" && name !== "ref"
269
+ && name !== "style" && !/^(?:bind|class|look|on|style|unsafe):/u.test(name) && !/^on/iu.test(name);
270
+ }
271
+ /**
272
+ * D90 coherence-3: DOM attribute names and ARIA are documented as checked
273
+ * surfaces, and nothing checked them — `className="panel"` compiled clean and
274
+ * emitted a dead attribute, while the sibling React reflex `onClick=` on the
275
+ * same element was already refused. So a model got a clean bill of health on
276
+ * exactly the half of its React habit that silently breaks the page.
277
+ *
278
+ * The rule diagnoses names that are KNOWN wrong, never names that are merely
279
+ * unknown. HTML lets a document carry attributes no roster can enumerate, so
280
+ * `foo="bar"`, `data-*`, and a framework's own prefixes stay legal; a false
281
+ * positive there would block a correct program, which is worse than the
282
+ * silence it replaces. Three closed rosters answer three closed questions:
283
+ * the React / JavaScript-property spellings, the ARIA attribute names (ARIA,
284
+ * unlike HTML, admits no custom names), and the ARIA and role vocabularies.
285
+ *
286
+ * D61 bounds the value half. Every native attribute uses the same presence
287
+ * rule: false/null remove it, true writes an empty value, and strings carry
288
+ * literal text. A token vocabulary is therefore read only against a string
289
+ * literal; expression values are runtime questions. A14 separately shortens
290
+ * an explicit bool-to-text conditional to `str(bool)` when that rewrite is
291
+ * provably equivalent.
292
+ *
293
+ * Returns true when it reported, so the caller does not stack a value-shape
294
+ * message on an attribute whose name is already answered.
295
+ */
296
+ export function reportNativeAttributeSpelling(host, expression, attribute) {
297
+ // A custom element owns its own attribute vocabulary, and an unknown tag was
298
+ // already reported as a tag; neither is a surface these rosters describe.
299
+ if (!WEB_NATIVE_ELEMENTS.has(expression.tag))
300
+ return false;
301
+ const name = attribute.name;
302
+ // The attribute span opens on the name, so the name occupies its first
303
+ // `name.length` characters — the range a rename rewrites, leaving the value
304
+ // exactly as written.
305
+ const nameSpan = { start: attribute.span.start, end: attribute.span.start + name.length };
306
+ const rename = (write) => ({ fix: { title: `Use '${write}'`, edits: [{ span: nameSpan, text: write }] } });
307
+ const spelling = WEB_MISSPELLED_ATTRIBUTES.get(name);
308
+ if (spelling) {
309
+ host.diagnostics.push({
310
+ code: "VEL5070",
311
+ message: `'${name}' is not a native attribute name; write '${spelling.write}'${spelling.note ? ` ${spelling.note}` : ""}`,
312
+ span: attribute.span,
313
+ ...(spelling.note === undefined ? rename(spelling.write) : {}),
314
+ });
315
+ return true;
316
+ }
317
+ if (/^aria[A-Z]/u.test(name)) {
318
+ // Every ARIA attribute is `aria-` followed by one lowercase word, so
319
+ // lowercasing the remainder recovers the spelling whenever one exists.
320
+ const hyphenated = `aria-${name.slice(4).toLowerCase()}`;
321
+ const known = WEB_ARIA_ATTRIBUTES.has(hyphenated);
322
+ host.diagnostics.push({
323
+ code: "VEL5070",
324
+ message: known
325
+ ? `'${name}' is not a native attribute name; ARIA attribute names are hyphenated — write '${hyphenated}'`
326
+ : `'${name}' is not a native attribute name; ARIA attribute names are hyphenated and lowercase, such as 'aria-label'`,
327
+ span: attribute.span,
328
+ ...(known ? rename(hyphenated) : {}),
329
+ });
330
+ return true;
331
+ }
332
+ if (name.startsWith("aria-") && !WEB_ARIA_ATTRIBUTES.has(name)) {
333
+ const nearest = nearestLookName(name, WEB_ARIA_ATTRIBUTES);
334
+ host.diagnostics.push({
335
+ code: "VEL5070",
336
+ message: nearest
337
+ ? `Unknown ARIA attribute '${name}'; did you mean '${nearest}'?`
338
+ : `Unknown ARIA attribute '${name}'; ARIA defines a closed set of aria-* names, so a state or property it does not define belongs on a data-* attribute`,
339
+ span: attribute.span,
340
+ ...(nearest ? rename(nearest) : {}),
341
+ });
342
+ return true;
343
+ }
344
+ // Only a literal value is read. A fix is not registered for a value: the
345
+ // attribute span opens on the name, so the value's own range is not known
346
+ // here, and the message names the token instead.
347
+ const literal = attribute.value;
348
+ // The empty string is not an out-of-vocabulary token: ARIA reads it as the
349
+ // attribute's own default, so `aria-hidden=""` is an uncommon but correct
350
+ // spelling of "not hidden" and refusing it would block a working document.
351
+ // `role=""` is already silent for the same reason — its loop skips empty
352
+ // tokens — so this keeps the two halves of the value check agreeing.
353
+ if (typeof literal !== "string" || literal === "")
354
+ return false;
355
+ const vocabulary = WEB_ARIA_ENUMERATED_VALUES.get(name);
356
+ if (vocabulary && !vocabulary.has(literal)) {
357
+ const nearest = nearestLookName(literal, vocabulary);
358
+ host.diagnostics.push(diagnostic("VEL5070", `'${name}' takes one of ${[...vocabulary].join(", ")}; '${literal}' is not one of them${nearest ? ` — did you mean '${nearest}'?` : ""}`, attribute.span));
359
+ return true;
360
+ }
361
+ if (name === "role") {
362
+ // `role` takes a space-separated fallback list, so each token is its own
363
+ // question and only the unknown ones are named.
364
+ let reported = false;
365
+ for (const token of literal.split(/\s+/u)) {
366
+ if (!token || WEB_ARIA_ROLES.has(token))
367
+ continue;
368
+ // A published second spelling is answered as a synonym rather than as
369
+ // an unknown name. `image` is ARIA's own later spelling of `img`, and
370
+ // "Unknown ARIA role 'image'" would assert something false about it.
371
+ const synonym = WEB_ARIA_ROLE_SYNONYMS.get(token);
372
+ const nearest = synonym ? null : nearestLookName(token, WEB_ARIA_ROLES);
373
+ host.diagnostics.push(diagnostic("VEL5070", synonym
374
+ ? `ARIA publishes '${token}' and '${synonym}' as one role; VelarScript writes '${synonym}'`
375
+ : nearest
376
+ ? `Unknown ARIA role '${token}'; did you mean '${nearest}'?`
377
+ : `Unknown ARIA role '${token}'; role takes an ARIA role name such as 'button', 'dialog', or 'status'`, attribute.span));
378
+ reported = true;
379
+ }
380
+ return reported;
381
+ }
382
+ return false;
383
+ }
384
+ /**
385
+ * GRM-A4: an event handler runs for effect and returns null. The hole this
386
+ * closes is `on:click={() => {}}`: after a fat arrow, braces build a record,
387
+ * never a block, so the empty-record factory used to be accepted as a handler
388
+ * and silently did nothing on every click.
389
+ */
390
+ export function checkEventHandlerResult(host, event, value, result, attributeSpan) {
391
+ const resolved = host.expandAliases(result);
392
+ // An asynchronous handler stays legal whatever it resolves to: attaching an
393
+ // action directly or through a wrapper is a decided spelling, and an action
394
+ // already owns its pending state, its errors, and its result.
395
+ if (resolved.kind === "promise")
396
+ return;
397
+ if (resolved.kind === "null" || resolved.kind === "any" || isInvalidType(resolved) || resolved.kind === "unknown")
398
+ return;
399
+ const emptyRecordBody = value && typeof value !== "string" && value.kind === "ArrowFunctionExpression"
400
+ && value.body.kind === "ObjectExpression" && value.body.properties.length === 0;
401
+ host.diagnostics.push(diagnostic("VEL5021", emptyRecordBody
402
+ ? `Event '${event}' handlers return null, and '{}' after '=>' builds an empty record rather than an empty block; write '() => null' for a handler that does nothing, or name a 'def' that performs the work`
403
+ : `Event '${event}' handlers return null; this handler returns ${describeType(result)} — the result is discarded, so call it inside a 'def' that returns null`, attributeSpan));
404
+ }
405
+ /**
406
+ * D47 rule 84(A): a bind target is a writable reactive location — a state
407
+ * name, or a record-field / List-index / Map-key path rooted in one. A
408
+ * computed accessor, a const, and a function result stay rejected: nothing
409
+ * would receive the write.
410
+ */
411
+ export function isWritableBindTarget(host, value) {
412
+ if (!value || typeof value === "string")
413
+ return false;
414
+ if (value.kind === "IdentifierExpression")
415
+ return host.writableStateName(value.name);
416
+ return bindPathRoot(host, value) !== null;
417
+ }
418
+ /**
419
+ * Walks a member/index path inward to its root state binding, checking every
420
+ * segment is a writable location: a declared record field, a List element, or
421
+ * a Map value. Returns the root state name, or null when the path is not a
422
+ * writable reactive location.
423
+ */
424
+ export function bindPathRoot(host, value) {
425
+ const segments = [];
426
+ let node = value;
427
+ while (node.kind === "MemberExpression" || node.kind === "IndexExpression") {
428
+ if (node.kind === "MemberExpression" && node.optional)
429
+ return null;
430
+ segments.push(node);
431
+ node = node.object;
432
+ }
433
+ if (node.kind !== "IdentifierExpression" || !host.writableStateName(node.name))
434
+ return null;
435
+ let current = host.expandAliases(host.lookup(node.name).type);
436
+ for (const segment of segments.reverse()) {
437
+ if (segment.kind === "MemberExpression") {
438
+ const fields = current.kind === "object" ? current.fields
439
+ : current.kind === "named" ? host.fieldsOf(current.identity ?? current.name) : null;
440
+ const field = fields?.get(segment.property);
441
+ if (!field)
442
+ return null;
443
+ current = host.expandAliases(field);
444
+ }
445
+ else if (current.kind === "list")
446
+ current = host.expandAliases(current.element);
447
+ else if (current.kind === "map" || current.kind === "record")
448
+ current = host.expandAliases(current.value);
449
+ else
450
+ return null;
451
+ }
452
+ return node.name;
453
+ }
454
+ /**
455
+ * D47 rule 84: `bind:group` binds a set of inputs that share one decision.
456
+ * A radio group holds the selected input's `value`; a checkbox group holds the
457
+ * checked values as a List<string>, so checking and unchecking are membership
458
+ * changes.
459
+ */
460
+ export function analyzeBindGroup(host, expression, attribute, value, inferred) {
461
+ const inputType = expression.attributes.find((item) => item.name === "type")?.value;
462
+ const kind = expression.tag === "input" && typeof inputType === "string" && (inputType === "radio" || inputType === "checkbox") ? inputType : null;
463
+ if (!kind) {
464
+ host.diagnostics.push(diagnostic("VEL5019", `bind:group binds a group of choices and requires <input type="radio"> or <input type="checkbox">; use bind:value for a single field and bind:checked for a single flag`, attribute.span));
465
+ return;
466
+ }
467
+ if (!expression.attributes.some((item) => item.name === "value")) {
468
+ host.diagnostics.push(diagnostic("VEL5019", `bind:group identifies each choice by its value attribute; add value="..." to this <input type="${kind}">`, attribute.span));
469
+ return;
470
+ }
471
+ if (!isWritableBindTarget(host, value)) {
472
+ host.diagnostics.push(diagnostic("VEL5019", bindTargetGuidance("bind:group"), attribute.span));
473
+ return;
474
+ }
475
+ const expected = kind === "radio" ? stringType : { kind: "list", element: stringType };
476
+ if (kind === "radio" && inferred.kind === "enum") {
477
+ host.enumValueBindings.set(attribute.span.start, inferred.name);
478
+ return;
479
+ }
480
+ host.requireAssignable(inferred, expected, attribute.span);
481
+ }
482
+ // Matches 'event => stateName = event.field' (any member depth) where the
483
+ // assignment target is a writable state binding, and returns that binding's
484
+ // name; anything else returns null.
485
+ export function eventAssignedStateBinding(host, value) {
486
+ if (!value || typeof value === "string")
487
+ return null;
488
+ if (value.kind !== "ArrowFunctionExpression" || value.asynchronous || value.parameters.length !== 1)
489
+ return null;
490
+ const body = value.body;
491
+ if (body.kind !== "AssignmentExpression" || body.target.kind !== "IdentifierExpression")
492
+ return null;
493
+ const state = body.target.name;
494
+ if (!host.writableStateName(state))
495
+ return null;
496
+ let source = body.value;
497
+ while (source.kind === "MemberExpression")
498
+ source = source.object;
499
+ return source.kind === "IdentifierExpression" && source.name === value.parameters[0].name && body.value.kind === "MemberExpression"
500
+ ? state
501
+ : null;
502
+ }
503
+ /** D47 rule 84: `bind:value` holds one field, on the three elements that have one. */
504
+ function analyzeBindValue(host, expression, attribute, value, inferred) {
505
+ if (!isWritableBindTarget(host, value)) {
506
+ host.diagnostics.push(diagnostic("VEL5019", bindTargetGuidance("bind:value"), attribute.span));
507
+ }
508
+ else {
509
+ if (!["input", "textarea", "select"].includes(expression.tag))
510
+ host.diagnostics.push(diagnostic("VEL5019", `bind:value is not valid on <${expression.tag}>`, attribute.span));
511
+ const numeric = expression.tag === "input" && expression.attributes.some((item) => item.name === "type" && item.value === "number");
512
+ host.requireAssignable(inferred, numeric ? numberType : stringType, attribute.span);
513
+ if (!numeric && inferred.kind === "enum")
514
+ host.enumValueBindings.set(attribute.span.start, inferred.name);
515
+ }
516
+ }
517
+ /** D47 rule 84: `bind:checked` holds one flag, and only a checkbox or radio has one. */
518
+ function analyzeBindChecked(host, expression, attribute, value, inferred) {
519
+ if (!isWritableBindTarget(host, value)) {
520
+ host.diagnostics.push(diagnostic("VEL5019", bindTargetGuidance("bind:checked"), attribute.span));
521
+ }
522
+ else {
523
+ if (expression.tag !== "input")
524
+ host.diagnostics.push(diagnostic("VEL5019", `bind:checked is not valid on <${expression.tag}>`, attribute.span));
525
+ host.requireAssignable(inferred, boolType, attribute.span);
526
+ }
527
+ }
528
+ /** `ref` on a native element: the binding it writes has to be optional, so cleanup can restore null, and wide enough for the element the tag names. */
529
+ function analyzeRefAttribute(host, expression, attribute, value) {
530
+ if (!value || typeof value === "string" || value.kind !== "IdentifierExpression" || !host.lookup(value.name)?.mutable) {
531
+ host.diagnostics.push(diagnostic("VEL5020", "ref requires a mutable let binding", attribute.span));
532
+ }
533
+ else {
534
+ const bindingType = host.lookup(value.name).type;
535
+ const target = nonOptional(bindingType);
536
+ const expected = expression.tag === "canvas" ? "CanvasElement" : expression.tag === "dialog" ? "DialogElement" : expression.tag === "textarea" ? "TextAreaElement" : ["input", "select"].includes(expression.tag) ? "InputElement" : "Element";
537
+ const accepted = expected === "TextAreaElement" ? new Set(["TextAreaElement", "InputElement", "Element"]) : new Set([expected, "Element"]);
538
+ if (bindingType.kind !== "any" && bindingType.kind !== "optional")
539
+ host.diagnostics.push(diagnostic("VEL5024", `A <${expression.tag}> ref requires ${expected}? or a parent element type so cleanup can restore null`, attribute.span));
540
+ else if (target.kind !== "any" && (target.kind !== "named" || !accepted.has(target.name)))
541
+ host.diagnostics.push(diagnostic("VEL5024", `A <${expression.tag}> ref requires ${expected}? or a parent element type`, attribute.span));
542
+ }
543
+ }
544
+ /** A fourth `bind:` suffix names no binding, so it is answered with the three that exist rather than emitted as a dead attribute. */
545
+ function reportUnknownBinding(host, attribute) {
546
+ const property = attribute.name.slice("bind:".length);
547
+ const nearest = property ? nearestLookName(property, ["value", "checked", "group"]) : null;
548
+ host.diagnostics.push(diagnostic("VEL5019", property
549
+ ? `Unknown binding 'bind:${property}'${nearest ? `; did you mean 'bind:${nearest}'?` : "; an element binds bind:value for a field, bind:checked for a flag, and bind:group for a set of choices"}`
550
+ : "Use 'bind:value={name}'; the bind directive names the bound property, such as bind:value or bind:checked", attribute.span));
551
+ }
552
+ /** WEB-S1: the `on` prefix a native element reserves — the message names the `on:` spelling, and says so about the browser only for a name the browser really compiles as script. */
553
+ function reportReservedOnAttribute(host, attribute) {
554
+ const camel = attribute.name.slice(2);
555
+ const event = camel === "DoubleClick" || camel === "DblClick" ? "dblclick" : camel.toLowerCase();
556
+ // Only a name the browser really compiles as script earns the executable
557
+ // clause; a name that is no event at all is refused by the prefix rule,
558
+ // not by the browser, and a message that claimed otherwise would state a
559
+ // rule the author did not hit. The roster answering that question is
560
+ // HTML's handler attributes, read case-insensitively as the browser reads
561
+ // them — the `on:` directive vocabulary is a different question and
562
+ // answered it wrongly in both directions.
563
+ const executable = htmlEventHandlerAttributes.has(attribute.name.toLowerCase());
564
+ // A concrete `on:` replacement is worth naming only when the remainder is
565
+ // a real event; `onward` would otherwise be answered with `on:ward`.
566
+ const named = executable || nativeDomEventNames.has(event);
567
+ const script = executable
568
+ ? ` — an '${attribute.name}' attribute is compiled as script by the browser, so any value written there runs`
569
+ : "";
570
+ if (attribute.name === "onEnter") {
571
+ host.diagnostics.push(diagnostic("VEL5025", "Use 'on:keydown' with a handler that checks 'event.key == \"Enter\"'; VelarScript has no dedicated enter-key event", attribute.span));
572
+ }
573
+ else {
574
+ host.diagnostics.push(diagnostic("VEL5025", named
575
+ ? `Use 'on:${event}'; VelarScript event attributes use the on: directive, written 'on:${event}={handler}'${script}`
576
+ : "Use an 'on:event' directive with a native DOM event name, such as 'on:click={handler}' or 'on:keydown={handler}'; an element reserves every attribute name beginning with 'on' other than the 'on:' directive itself, because the handler spellings among them — 'onclick', 'onerror', 'onload' — are executable script in the browser", attribute.span));
577
+ }
578
+ }
579
+ /** The `on:event.modifier` directive: the modifier vocabulary, and the handler shape the event provides for. */
580
+ function analyzeEventDirective(host, attribute, value, inferred, expectedEvent) {
581
+ const [event, ...modifiers] = attribute.name.slice(3).split(".");
582
+ const supported = new Set(["prevent", "stop", "once", "capture", "self"]);
583
+ if (!event)
584
+ host.diagnostics.push(diagnostic("VEL5025", "An event directive requires an event name", attribute.span));
585
+ for (const modifier of modifiers)
586
+ if (!supported.has(modifier))
587
+ host.diagnostics.push(diagnostic("VEL5025", `Unknown event modifier '${modifier}'`, attribute.span));
588
+ if (new Set(modifiers).size !== modifiers.length)
589
+ host.diagnostics.push(diagnostic("VEL5025", "Event modifiers cannot be repeated", attribute.span));
590
+ if (!isInvalidType(inferred) && inferred.kind !== "function" && inferred.kind !== "action" && inferred.kind !== "intrinsic" && inferred.kind !== "any") {
591
+ host.diagnostics.push(diagnostic("VEL5021", `Event '${event}' requires a function`, attribute.span));
592
+ }
593
+ else if (inferred.kind === "function" || inferred.kind === "action" || inferred.kind === "intrinsic") {
594
+ if (inferred.rest || inferred.parameters.length > 1)
595
+ host.diagnostics.push(diagnostic("VEL5021", `Event '${event}' handlers accept zero parameters or one ${describeType(expectedEvent ?? { kind: "named", name: "Event" })} parameter`, attribute.span));
596
+ else if (inferred.parameters.length === 1 && expectedEvent && !host.isAssignableHere(expectedEvent, inferred.parameters[0]))
597
+ host.diagnostics.push(diagnostic("VEL5021", `Event '${event}' provides ${describeType(expectedEvent)}, not ${describeType(inferred.parameters[0])}`, attribute.span));
598
+ checkEventHandlerResult(host, event ?? "", value, inferred.result, attribute.span);
599
+ }
600
+ }
601
+ //# sourceMappingURL=attributes.js.map