@llui/compiler 0.3.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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accessor-resolver.d.ts +58 -0
  3. package/dist/accessor-resolver.d.ts.map +1 -0
  4. package/dist/accessor-resolver.js +119 -0
  5. package/dist/accessor-resolver.js.map +1 -0
  6. package/dist/binding-descriptors.d.ts +105 -0
  7. package/dist/binding-descriptors.d.ts.map +1 -0
  8. package/dist/binding-descriptors.js +340 -0
  9. package/dist/binding-descriptors.js.map +1 -0
  10. package/dist/collect-deps.d.ts +49 -0
  11. package/dist/collect-deps.d.ts.map +1 -0
  12. package/dist/collect-deps.js +444 -0
  13. package/dist/collect-deps.js.map +1 -0
  14. package/dist/compiler-cache.d.ts +20 -0
  15. package/dist/compiler-cache.d.ts.map +1 -0
  16. package/dist/compiler-cache.js +20 -0
  17. package/dist/compiler-cache.js.map +1 -0
  18. package/dist/cross-file-resolver.d.ts +109 -0
  19. package/dist/cross-file-resolver.d.ts.map +1 -0
  20. package/dist/cross-file-resolver.js +530 -0
  21. package/dist/cross-file-resolver.js.map +1 -0
  22. package/dist/cross-file-walker.d.ts +63 -0
  23. package/dist/cross-file-walker.d.ts.map +1 -0
  24. package/dist/cross-file-walker.js +516 -0
  25. package/dist/cross-file-walker.js.map +1 -0
  26. package/dist/diagnostic.d.ts +76 -0
  27. package/dist/diagnostic.d.ts.map +1 -0
  28. package/dist/diagnostic.js +59 -0
  29. package/dist/diagnostic.js.map +1 -0
  30. package/dist/index.d.ts +27 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +37 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/introspection-factory.d.ts +54 -0
  35. package/dist/introspection-factory.d.ts.map +1 -0
  36. package/dist/introspection-factory.js +46 -0
  37. package/dist/introspection-factory.js.map +1 -0
  38. package/dist/manifest.d.ts +144 -0
  39. package/dist/manifest.d.ts.map +1 -0
  40. package/dist/manifest.js +209 -0
  41. package/dist/manifest.js.map +1 -0
  42. package/dist/module.d.ts +222 -0
  43. package/dist/module.d.ts.map +1 -0
  44. package/dist/module.js +256 -0
  45. package/dist/module.js.map +1 -0
  46. package/dist/modules/_element-helpers.d.ts +4 -0
  47. package/dist/modules/_element-helpers.d.ts.map +1 -0
  48. package/dist/modules/_element-helpers.js +138 -0
  49. package/dist/modules/_element-helpers.js.map +1 -0
  50. package/dist/modules/_msg-variants.d.ts +10 -0
  51. package/dist/modules/_msg-variants.d.ts.map +1 -0
  52. package/dist/modules/_msg-variants.js +97 -0
  53. package/dist/modules/_msg-variants.js.map +1 -0
  54. package/dist/modules/_shared.d.ts +16 -0
  55. package/dist/modules/_shared.d.ts.map +1 -0
  56. package/dist/modules/_shared.js +30 -0
  57. package/dist/modules/_shared.js.map +1 -0
  58. package/dist/modules/accessibility.d.ts +3 -0
  59. package/dist/modules/accessibility.d.ts.map +1 -0
  60. package/dist/modules/accessibility.js +82 -0
  61. package/dist/modules/accessibility.js.map +1 -0
  62. package/dist/modules/accessor-side-effect.d.ts +3 -0
  63. package/dist/modules/accessor-side-effect.d.ts.map +1 -0
  64. package/dist/modules/accessor-side-effect.js +113 -0
  65. package/dist/modules/accessor-side-effect.js.map +1 -0
  66. package/dist/modules/agent-emits-drift.d.ts +3 -0
  67. package/dist/modules/agent-emits-drift.d.ts.map +1 -0
  68. package/dist/modules/agent-emits-drift.js +158 -0
  69. package/dist/modules/agent-emits-drift.js.map +1 -0
  70. package/dist/modules/agent-example-on-payload.d.ts +3 -0
  71. package/dist/modules/agent-example-on-payload.d.ts.map +1 -0
  72. package/dist/modules/agent-example-on-payload.js +53 -0
  73. package/dist/modules/agent-example-on-payload.js.map +1 -0
  74. package/dist/modules/agent-exclusive-annotations.d.ts +3 -0
  75. package/dist/modules/agent-exclusive-annotations.d.ts.map +1 -0
  76. package/dist/modules/agent-exclusive-annotations.js +68 -0
  77. package/dist/modules/agent-exclusive-annotations.js.map +1 -0
  78. package/dist/modules/agent-missing-intent.d.ts +3 -0
  79. package/dist/modules/agent-missing-intent.d.ts.map +1 -0
  80. package/dist/modules/agent-missing-intent.js +47 -0
  81. package/dist/modules/agent-missing-intent.js.map +1 -0
  82. package/dist/modules/agent-msg-resolvable.d.ts +3 -0
  83. package/dist/modules/agent-msg-resolvable.d.ts.map +1 -0
  84. package/dist/modules/agent-msg-resolvable.js +161 -0
  85. package/dist/modules/agent-msg-resolvable.js.map +1 -0
  86. package/dist/modules/agent-nonextractable-handler.d.ts +3 -0
  87. package/dist/modules/agent-nonextractable-handler.d.ts.map +1 -0
  88. package/dist/modules/agent-nonextractable-handler.js +127 -0
  89. package/dist/modules/agent-nonextractable-handler.js.map +1 -0
  90. package/dist/modules/agent-optional-field-undocumented.d.ts +3 -0
  91. package/dist/modules/agent-optional-field-undocumented.d.ts.map +1 -0
  92. package/dist/modules/agent-optional-field-undocumented.js +67 -0
  93. package/dist/modules/agent-optional-field-undocumented.js.map +1 -0
  94. package/dist/modules/agent-tagsend-translator-missing.d.ts +3 -0
  95. package/dist/modules/agent-tagsend-translator-missing.d.ts.map +1 -0
  96. package/dist/modules/agent-tagsend-translator-missing.js +58 -0
  97. package/dist/modules/agent-tagsend-translator-missing.js.map +1 -0
  98. package/dist/modules/agent-warning-on-confirm.d.ts +3 -0
  99. package/dist/modules/agent-warning-on-confirm.d.ts.map +1 -0
  100. package/dist/modules/agent-warning-on-confirm.js +46 -0
  101. package/dist/modules/agent-warning-on-confirm.js.map +1 -0
  102. package/dist/modules/async-update.d.ts +3 -0
  103. package/dist/modules/async-update.d.ts.map +1 -0
  104. package/dist/modules/async-update.js +86 -0
  105. package/dist/modules/async-update.js.map +1 -0
  106. package/dist/modules/binding-descriptors.d.ts +4 -0
  107. package/dist/modules/binding-descriptors.d.ts.map +1 -0
  108. package/dist/modules/binding-descriptors.js +48 -0
  109. package/dist/modules/binding-descriptors.js.map +1 -0
  110. package/dist/modules/bitmask-overflow.d.ts +3 -0
  111. package/dist/modules/bitmask-overflow.d.ts.map +1 -0
  112. package/dist/modules/bitmask-overflow.js +152 -0
  113. package/dist/modules/bitmask-overflow.js.map +1 -0
  114. package/dist/modules/compiler-stamp.d.ts +3 -0
  115. package/dist/modules/compiler-stamp.d.ts.map +1 -0
  116. package/dist/modules/compiler-stamp.js +44 -0
  117. package/dist/modules/compiler-stamp.js.map +1 -0
  118. package/dist/modules/component-meta.d.ts +3 -0
  119. package/dist/modules/component-meta.d.ts.map +1 -0
  120. package/dist/modules/component-meta.js +44 -0
  121. package/dist/modules/component-meta.js.map +1 -0
  122. package/dist/modules/controlled-input.d.ts +3 -0
  123. package/dist/modules/controlled-input.d.ts.map +1 -0
  124. package/dist/modules/controlled-input.js +68 -0
  125. package/dist/modules/controlled-input.js.map +1 -0
  126. package/dist/modules/core-synthesis.d.ts +18 -0
  127. package/dist/modules/core-synthesis.d.ts.map +1 -0
  128. package/dist/modules/core-synthesis.js +748 -0
  129. package/dist/modules/core-synthesis.js.map +1 -0
  130. package/dist/modules/direct-state-in-view.d.ts +3 -0
  131. package/dist/modules/direct-state-in-view.d.ts.map +1 -0
  132. package/dist/modules/direct-state-in-view.js +103 -0
  133. package/dist/modules/direct-state-in-view.js.map +1 -0
  134. package/dist/modules/each-closure-violation.d.ts +3 -0
  135. package/dist/modules/each-closure-violation.d.ts.map +1 -0
  136. package/dist/modules/each-closure-violation.js +255 -0
  137. package/dist/modules/each-closure-violation.js.map +1 -0
  138. package/dist/modules/each-memo.d.ts +15 -0
  139. package/dist/modules/each-memo.d.ts.map +1 -0
  140. package/dist/modules/each-memo.js +115 -0
  141. package/dist/modules/each-memo.js.map +1 -0
  142. package/dist/modules/effect-without-handler.d.ts +3 -0
  143. package/dist/modules/effect-without-handler.d.ts.map +1 -0
  144. package/dist/modules/effect-without-handler.js +92 -0
  145. package/dist/modules/effect-without-handler.js.map +1 -0
  146. package/dist/modules/element-rewrite.d.ts +22 -0
  147. package/dist/modules/element-rewrite.d.ts.map +1 -0
  148. package/dist/modules/element-rewrite.js +1017 -0
  149. package/dist/modules/element-rewrite.js.map +1 -0
  150. package/dist/modules/empty-props.d.ts +3 -0
  151. package/dist/modules/empty-props.d.ts.map +1 -0
  152. package/dist/modules/empty-props.js +50 -0
  153. package/dist/modules/empty-props.js.map +1 -0
  154. package/dist/modules/exhaustive-effect-handling.d.ts +3 -0
  155. package/dist/modules/exhaustive-effect-handling.d.ts.map +1 -0
  156. package/dist/modules/exhaustive-effect-handling.js +61 -0
  157. package/dist/modules/exhaustive-effect-handling.js.map +1 -0
  158. package/dist/modules/exhaustive-update.d.ts +3 -0
  159. package/dist/modules/exhaustive-update.d.ts.map +1 -0
  160. package/dist/modules/exhaustive-update.js +146 -0
  161. package/dist/modules/exhaustive-update.js.map +1 -0
  162. package/dist/modules/forgotten-spread.d.ts +3 -0
  163. package/dist/modules/forgotten-spread.d.ts.map +1 -0
  164. package/dist/modules/forgotten-spread.js +51 -0
  165. package/dist/modules/forgotten-spread.js.map +1 -0
  166. package/dist/modules/form-boilerplate.d.ts +3 -0
  167. package/dist/modules/form-boilerplate.d.ts.map +1 -0
  168. package/dist/modules/form-boilerplate.js +101 -0
  169. package/dist/modules/form-boilerplate.js.map +1 -0
  170. package/dist/modules/imperative-dom-in-view.d.ts +3 -0
  171. package/dist/modules/imperative-dom-in-view.d.ts.map +1 -0
  172. package/dist/modules/imperative-dom-in-view.js +123 -0
  173. package/dist/modules/imperative-dom-in-view.js.map +1 -0
  174. package/dist/modules/item-dedup.d.ts +7 -0
  175. package/dist/modules/item-dedup.d.ts.map +1 -0
  176. package/dist/modules/item-dedup.js +204 -0
  177. package/dist/modules/item-dedup.js.map +1 -0
  178. package/dist/modules/map-on-state-array.d.ts +3 -0
  179. package/dist/modules/map-on-state-array.d.ts.map +1 -0
  180. package/dist/modules/map-on-state-array.js +84 -0
  181. package/dist/modules/map-on-state-array.js.map +1 -0
  182. package/dist/modules/mask-legend.d.ts +10 -0
  183. package/dist/modules/mask-legend.d.ts.map +1 -0
  184. package/dist/modules/mask-legend.js +50 -0
  185. package/dist/modules/mask-legend.js.map +1 -0
  186. package/dist/modules/missing-memo.d.ts +3 -0
  187. package/dist/modules/missing-memo.d.ts.map +1 -0
  188. package/dist/modules/missing-memo.js +114 -0
  189. package/dist/modules/missing-memo.js.map +1 -0
  190. package/dist/modules/msg-annotations.d.ts +9 -0
  191. package/dist/modules/msg-annotations.d.ts.map +1 -0
  192. package/dist/modules/msg-annotations.js +54 -0
  193. package/dist/modules/msg-annotations.js.map +1 -0
  194. package/dist/modules/msg-schema.d.ts +10 -0
  195. package/dist/modules/msg-schema.d.ts.map +1 -0
  196. package/dist/modules/msg-schema.js +70 -0
  197. package/dist/modules/msg-schema.js.map +1 -0
  198. package/dist/modules/namespace-import.d.ts +3 -0
  199. package/dist/modules/namespace-import.d.ts.map +1 -0
  200. package/dist/modules/namespace-import.js +80 -0
  201. package/dist/modules/namespace-import.js.map +1 -0
  202. package/dist/modules/nested-send-in-update.d.ts +3 -0
  203. package/dist/modules/nested-send-in-update.d.ts.map +1 -0
  204. package/dist/modules/nested-send-in-update.js +77 -0
  205. package/dist/modules/nested-send-in-update.js.map +1 -0
  206. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts +3 -0
  207. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts.map +1 -0
  208. package/dist/modules/no-barrel-import-when-subpath-exists.js +100 -0
  209. package/dist/modules/no-barrel-import-when-subpath-exists.js.map +1 -0
  210. package/dist/modules/no-eager-item-accessor.d.ts +3 -0
  211. package/dist/modules/no-eager-item-accessor.d.ts.map +1 -0
  212. package/dist/modules/no-eager-item-accessor.js +74 -0
  213. package/dist/modules/no-eager-item-accessor.js.map +1 -0
  214. package/dist/modules/no-let-reactive-accessor.d.ts +3 -0
  215. package/dist/modules/no-let-reactive-accessor.d.ts.map +1 -0
  216. package/dist/modules/no-let-reactive-accessor.js +227 -0
  217. package/dist/modules/no-let-reactive-accessor.js.map +1 -0
  218. package/dist/modules/no-list-render-in-sample.d.ts +3 -0
  219. package/dist/modules/no-list-render-in-sample.d.ts.map +1 -0
  220. package/dist/modules/no-list-render-in-sample.js +89 -0
  221. package/dist/modules/no-list-render-in-sample.js.map +1 -0
  222. package/dist/modules/no-sample-in-accessor.d.ts +3 -0
  223. package/dist/modules/no-sample-in-accessor.d.ts.map +1 -0
  224. package/dist/modules/no-sample-in-accessor.js +141 -0
  225. package/dist/modules/no-sample-in-accessor.js.map +1 -0
  226. package/dist/modules/no-sample-in-reactive-position.d.ts +3 -0
  227. package/dist/modules/no-sample-in-reactive-position.d.ts.map +1 -0
  228. package/dist/modules/no-sample-in-reactive-position.js +72 -0
  229. package/dist/modules/no-sample-in-reactive-position.js.map +1 -0
  230. package/dist/modules/pure-update-function.d.ts +3 -0
  231. package/dist/modules/pure-update-function.d.ts.map +1 -0
  232. package/dist/modules/pure-update-function.js +127 -0
  233. package/dist/modules/pure-update-function.js.map +1 -0
  234. package/dist/modules/reactive-paths.d.ts +3 -0
  235. package/dist/modules/reactive-paths.d.ts.map +1 -0
  236. package/dist/modules/reactive-paths.js +77 -0
  237. package/dist/modules/reactive-paths.js.map +1 -0
  238. package/dist/modules/row-factory.d.ts +12 -0
  239. package/dist/modules/row-factory.d.ts.map +1 -0
  240. package/dist/modules/row-factory.js +385 -0
  241. package/dist/modules/row-factory.js.map +1 -0
  242. package/dist/modules/schema-hash.d.ts +15 -0
  243. package/dist/modules/schema-hash.d.ts.map +1 -0
  244. package/dist/modules/schema-hash.js +70 -0
  245. package/dist/modules/schema-hash.js.map +1 -0
  246. package/dist/modules/spread-in-children.d.ts +3 -0
  247. package/dist/modules/spread-in-children.d.ts.map +1 -0
  248. package/dist/modules/spread-in-children.js +144 -0
  249. package/dist/modules/spread-in-children.js.map +1 -0
  250. package/dist/modules/state-mutation.d.ts +3 -0
  251. package/dist/modules/state-mutation.d.ts.map +1 -0
  252. package/dist/modules/state-mutation.js +138 -0
  253. package/dist/modules/state-mutation.js.map +1 -0
  254. package/dist/modules/state-schema.d.ts +8 -0
  255. package/dist/modules/state-schema.d.ts.map +1 -0
  256. package/dist/modules/state-schema.js +55 -0
  257. package/dist/modules/state-schema.js.map +1 -0
  258. package/dist/modules/static-items.d.ts +3 -0
  259. package/dist/modules/static-items.d.ts.map +1 -0
  260. package/dist/modules/static-items.js +125 -0
  261. package/dist/modules/static-items.js.map +1 -0
  262. package/dist/modules/static-on.d.ts +3 -0
  263. package/dist/modules/static-on.d.ts.map +1 -0
  264. package/dist/modules/static-on.js +100 -0
  265. package/dist/modules/static-on.js.map +1 -0
  266. package/dist/modules/string-effect-callback.d.ts +3 -0
  267. package/dist/modules/string-effect-callback.d.ts.map +1 -0
  268. package/dist/modules/string-effect-callback.js +50 -0
  269. package/dist/modules/string-effect-callback.js.map +1 -0
  270. package/dist/modules/structural-mask.d.ts +8 -0
  271. package/dist/modules/structural-mask.d.ts.map +1 -0
  272. package/dist/modules/structural-mask.js +76 -0
  273. package/dist/modules/structural-mask.js.map +1 -0
  274. package/dist/modules/subapp-requires-reason.d.ts +3 -0
  275. package/dist/modules/subapp-requires-reason.d.ts.map +1 -0
  276. package/dist/modules/subapp-requires-reason.js +129 -0
  277. package/dist/modules/subapp-requires-reason.js.map +1 -0
  278. package/dist/modules/text-mask.d.ts +12 -0
  279. package/dist/modules/text-mask.d.ts.map +1 -0
  280. package/dist/modules/text-mask.js +63 -0
  281. package/dist/modules/text-mask.js.map +1 -0
  282. package/dist/modules/view-bag-import.d.ts +3 -0
  283. package/dist/modules/view-bag-import.d.ts.map +1 -0
  284. package/dist/modules/view-bag-import.js +80 -0
  285. package/dist/modules/view-bag-import.js.map +1 -0
  286. package/dist/msg-annotations.d.ts +104 -0
  287. package/dist/msg-annotations.d.ts.map +1 -0
  288. package/dist/msg-annotations.js +242 -0
  289. package/dist/msg-annotations.js.map +1 -0
  290. package/dist/msg-schema.d.ts +130 -0
  291. package/dist/msg-schema.d.ts.map +1 -0
  292. package/dist/msg-schema.js +770 -0
  293. package/dist/msg-schema.js.map +1 -0
  294. package/dist/schema-hash.d.ts +16 -0
  295. package/dist/schema-hash.d.ts.map +1 -0
  296. package/dist/schema-hash.js +31 -0
  297. package/dist/schema-hash.js.map +1 -0
  298. package/dist/state-schema.d.ts +41 -0
  299. package/dist/state-schema.d.ts.map +1 -0
  300. package/dist/state-schema.js +156 -0
  301. package/dist/state-schema.js.map +1 -0
  302. package/dist/transform.d.ts +109 -0
  303. package/dist/transform.d.ts.map +1 -0
  304. package/dist/transform.js +1390 -0
  305. package/dist/transform.js.map +1 -0
  306. package/dist/version.d.ts +11 -0
  307. package/dist/version.d.ts.map +1 -0
  308. package/dist/version.js +11 -0
  309. package/dist/version.js.map +1 -0
  310. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Franco Ponticelli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,58 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Helpers for resolving identifier references at reactive-accessor
4
+ * positions. Shared by `transform.ts` (compile-time prop classification
5
+ * + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning
6
+ * for `__dirty` / `__maskLegend`).
7
+ *
8
+ * The compiler must distinguish the legitimate accessor shapes:
9
+ *
10
+ * - Inline arrow / function expression at the call site
11
+ * - Inline `memo(arrow)` at the call site
12
+ * - Identifier referencing a const-bound arrow / function expression
13
+ * - Identifier referencing a hoisted function declaration
14
+ * - Identifier referencing `const x = memo(arrow)`
15
+ *
16
+ * …from values we can't classify (imports, parameters, opaque calls), so
17
+ * those can be bailed-to-runtime instead of silently miscompiled. See the
18
+ * `disabled` binding bug, where a function reference at a reactive prop
19
+ * position was statically assigned (`__e.disabled = isGated`) — writing
20
+ * the function object onto the boolean DOM property and never re-evaluating.
21
+ */
22
+ /**
23
+ * Walk parent chains to find a `const X = ...` declaration matching
24
+ * `use.text`, or a hoisted `function X(...)` declaration. Returns the
25
+ * resolved declaration or `null` for unresolvable references (imports,
26
+ * parameters, this-bindings, etc.).
27
+ *
28
+ * Limitations:
29
+ * - Only `const`. `let` resolution is unsafe — we can't track later
30
+ * reassignments without a type checker.
31
+ * - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).
32
+ * - The declaration must dominate the use (lexical scope).
33
+ */
34
+ export declare function resolveLocalConstInitializer(use: ts.Identifier): ts.Expression | ts.FunctionDeclaration | null;
35
+ /**
36
+ * Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can
37
+ * be analyzed for state-path masking. The runtime `memo()` returns a
38
+ * cached accessor — its body's reads determine when it re-evaluates,
39
+ * not the call site.
40
+ */
41
+ export declare function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.CallExpression & {
42
+ arguments: readonly [ts.ArrowFunction | ts.FunctionExpression, ...ts.Expression[]];
43
+ };
44
+ /**
45
+ * Resolve a value at a reactive-accessor position down to the callable
46
+ * AST node we can mask-analyze. Returns `null` when the value isn't a
47
+ * recognized accessor shape — caller leaves the call unchanged (runtime
48
+ * falls back to FULL_MASK, which is correct just slower).
49
+ *
50
+ * Recognized shapes:
51
+ * - `(s) => …` (ArrowFunction)
52
+ * - `function (s) { … }` (FunctionExpression)
53
+ * - `memo((s) => …)` — returns the inner arrow
54
+ * - `someIdentifier` resolving to any of the above (or to a hoisted
55
+ * `function X(s) { … }` declaration)
56
+ */
57
+ export declare function resolveAccessorBody(value: ts.Expression): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null;
58
+ //# sourceMappingURL=accessor-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessor-resolver.d.ts","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,EAAE,CAAC,UAAU,GACjB,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CA8B/C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,GAAG;IACvF,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAA;CACnF,CAQA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,EAAE,CAAC,UAAU,GACnB,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,kBAAkB,GAAG,EAAE,CAAC,mBAAmB,GAAG,IAAI,CAoB1E"}
@@ -0,0 +1,119 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Helpers for resolving identifier references at reactive-accessor
4
+ * positions. Shared by `transform.ts` (compile-time prop classification
5
+ * + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning
6
+ * for `__dirty` / `__maskLegend`).
7
+ *
8
+ * The compiler must distinguish the legitimate accessor shapes:
9
+ *
10
+ * - Inline arrow / function expression at the call site
11
+ * - Inline `memo(arrow)` at the call site
12
+ * - Identifier referencing a const-bound arrow / function expression
13
+ * - Identifier referencing a hoisted function declaration
14
+ * - Identifier referencing `const x = memo(arrow)`
15
+ *
16
+ * …from values we can't classify (imports, parameters, opaque calls), so
17
+ * those can be bailed-to-runtime instead of silently miscompiled. See the
18
+ * `disabled` binding bug, where a function reference at a reactive prop
19
+ * position was statically assigned (`__e.disabled = isGated`) — writing
20
+ * the function object onto the boolean DOM property and never re-evaluating.
21
+ */
22
+ /**
23
+ * Walk parent chains to find a `const X = ...` declaration matching
24
+ * `use.text`, or a hoisted `function X(...)` declaration. Returns the
25
+ * resolved declaration or `null` for unresolvable references (imports,
26
+ * parameters, this-bindings, etc.).
27
+ *
28
+ * Limitations:
29
+ * - Only `const`. `let` resolution is unsafe — we can't track later
30
+ * reassignments without a type checker.
31
+ * - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).
32
+ * - The declaration must dominate the use (lexical scope).
33
+ */
34
+ export function resolveLocalConstInitializer(use) {
35
+ const name = use.text;
36
+ let node = use;
37
+ while (node.parent) {
38
+ const parent = node.parent;
39
+ let statements = null;
40
+ if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {
41
+ statements = parent.statements;
42
+ }
43
+ else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {
44
+ statements = parent.statements;
45
+ }
46
+ if (statements) {
47
+ for (const stmt of statements) {
48
+ if (ts.isFunctionDeclaration(stmt)) {
49
+ if (stmt.name && stmt.name.text === name)
50
+ return stmt;
51
+ continue;
52
+ }
53
+ if (!ts.isVariableStatement(stmt))
54
+ continue;
55
+ const flags = stmt.declarationList.flags;
56
+ if (!(flags & ts.NodeFlags.Const))
57
+ continue;
58
+ if (stmt.declarationList.declarations.length !== 1)
59
+ continue;
60
+ const decl = stmt.declarationList.declarations[0];
61
+ if (!ts.isIdentifier(decl.name) || decl.name.text !== name)
62
+ continue;
63
+ if (!decl.initializer)
64
+ continue;
65
+ return decl.initializer;
66
+ }
67
+ }
68
+ node = parent;
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can
74
+ * be analyzed for state-path masking. The runtime `memo()` returns a
75
+ * cached accessor — its body's reads determine when it re-evaluates,
76
+ * not the call site.
77
+ */
78
+ export function isMemoCallWithArrowArg(expr) {
79
+ return (ts.isCallExpression(expr) &&
80
+ ts.isIdentifier(expr.expression) &&
81
+ expr.expression.text === 'memo' &&
82
+ expr.arguments.length >= 1 &&
83
+ (ts.isArrowFunction(expr.arguments[0]) || ts.isFunctionExpression(expr.arguments[0])));
84
+ }
85
+ /**
86
+ * Resolve a value at a reactive-accessor position down to the callable
87
+ * AST node we can mask-analyze. Returns `null` when the value isn't a
88
+ * recognized accessor shape — caller leaves the call unchanged (runtime
89
+ * falls back to FULL_MASK, which is correct just slower).
90
+ *
91
+ * Recognized shapes:
92
+ * - `(s) => …` (ArrowFunction)
93
+ * - `function (s) { … }` (FunctionExpression)
94
+ * - `memo((s) => …)` — returns the inner arrow
95
+ * - `someIdentifier` resolving to any of the above (or to a hoisted
96
+ * `function X(s) { … }` declaration)
97
+ */
98
+ export function resolveAccessorBody(value) {
99
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value))
100
+ return value;
101
+ if (isMemoCallWithArrowArg(value)) {
102
+ return value.arguments[0];
103
+ }
104
+ if (ts.isIdentifier(value)) {
105
+ const resolved = resolveLocalConstInitializer(value);
106
+ if (!resolved)
107
+ return null;
108
+ if (ts.isArrowFunction(resolved) ||
109
+ ts.isFunctionExpression(resolved) ||
110
+ ts.isFunctionDeclaration(resolved)) {
111
+ return resolved;
112
+ }
113
+ if (isMemoCallWithArrowArg(resolved)) {
114
+ return resolved.arguments[0];
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ //# sourceMappingURL=accessor-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessor-resolver.js","sourceRoot":"","sources":["../src/accessor-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * Helpers for resolving identifier references at reactive-accessor\n * positions. Shared by `transform.ts` (compile-time prop classification\n * + Pass 2 mask injection) and `collect-deps.ts` (state-path scanning\n * for `__dirty` / `__maskLegend`).\n *\n * The compiler must distinguish the legitimate accessor shapes:\n *\n * - Inline arrow / function expression at the call site\n * - Inline `memo(arrow)` at the call site\n * - Identifier referencing a const-bound arrow / function expression\n * - Identifier referencing a hoisted function declaration\n * - Identifier referencing `const x = memo(arrow)`\n *\n * …from values we can't classify (imports, parameters, opaque calls), so\n * those can be bailed-to-runtime instead of silently miscompiled. See the\n * `disabled` binding bug, where a function reference at a reactive prop\n * position was statically assigned (`__e.disabled = isGated`) — writing\n * the function object onto the boolean DOM property and never re-evaluating.\n */\n\n/**\n * Walk parent chains to find a `const X = ...` declaration matching\n * `use.text`, or a hoisted `function X(...)` declaration. Returns the\n * resolved declaration or `null` for unresolvable references (imports,\n * parameters, this-bindings, etc.).\n *\n * Limitations:\n * - Only `const`. `let` resolution is unsafe — we can't track later\n * reassignments without a type checker.\n * - Only single-binding declarations (`const a = …`, not `const a = …, b = …`).\n * - The declaration must dominate the use (lexical scope).\n */\nexport function resolveLocalConstInitializer(\n use: ts.Identifier,\n): ts.Expression | ts.FunctionDeclaration | null {\n const name = use.text\n let node: ts.Node = use\n while (node.parent) {\n const parent = node.parent\n let statements: readonly ts.Statement[] | null = null\n if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {\n statements = parent.statements\n } else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {\n statements = parent.statements\n }\n if (statements) {\n for (const stmt of statements) {\n if (ts.isFunctionDeclaration(stmt)) {\n if (stmt.name && stmt.name.text === name) return stmt\n continue\n }\n if (!ts.isVariableStatement(stmt)) continue\n const flags = stmt.declarationList.flags\n if (!(flags & ts.NodeFlags.Const)) continue\n if (stmt.declarationList.declarations.length !== 1) continue\n const decl = stmt.declarationList.declarations[0]!\n if (!ts.isIdentifier(decl.name) || decl.name.text !== name) continue\n if (!decl.initializer) continue\n return decl.initializer\n }\n }\n node = parent\n }\n return null\n}\n\n/**\n * Recognize `memo(arrow)` / `memo(fn)` calls so the inner accessor can\n * be analyzed for state-path masking. The runtime `memo()` returns a\n * cached accessor — its body's reads determine when it re-evaluates,\n * not the call site.\n */\nexport function isMemoCallWithArrowArg(expr: ts.Expression): expr is ts.CallExpression & {\n arguments: readonly [ts.ArrowFunction | ts.FunctionExpression, ...ts.Expression[]]\n} {\n return (\n ts.isCallExpression(expr) &&\n ts.isIdentifier(expr.expression) &&\n expr.expression.text === 'memo' &&\n expr.arguments.length >= 1 &&\n (ts.isArrowFunction(expr.arguments[0]!) || ts.isFunctionExpression(expr.arguments[0]!))\n )\n}\n\n/**\n * Resolve a value at a reactive-accessor position down to the callable\n * AST node we can mask-analyze. Returns `null` when the value isn't a\n * recognized accessor shape — caller leaves the call unchanged (runtime\n * falls back to FULL_MASK, which is correct just slower).\n *\n * Recognized shapes:\n * - `(s) => …` (ArrowFunction)\n * - `function (s) { … }` (FunctionExpression)\n * - `memo((s) => …)` — returns the inner arrow\n * - `someIdentifier` resolving to any of the above (or to a hoisted\n * `function X(s) { … }` declaration)\n */\nexport function resolveAccessorBody(\n value: ts.Expression,\n): ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | null {\n if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) return value\n if (isMemoCallWithArrowArg(value)) {\n return value.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n if (ts.isIdentifier(value)) {\n const resolved = resolveLocalConstInitializer(value)\n if (!resolved) return null\n if (\n ts.isArrowFunction(resolved) ||\n ts.isFunctionExpression(resolved) ||\n ts.isFunctionDeclaration(resolved)\n ) {\n return resolved\n }\n if (isMemoCallWithArrowArg(resolved)) {\n return resolved.arguments[0] as ts.ArrowFunction | ts.FunctionExpression\n }\n }\n return null\n}\n"]}
@@ -0,0 +1,105 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Compiler passes that surface the Msg variants currently dispatchable
4
+ * from rendered UI to the agent layer's `list_actions`. Two passes
5
+ * cover two distinct dispatch shapes:
6
+ *
7
+ * 1. `tagEventHandlerSends` — tags event-handler arrow functions
8
+ * (`onClick`, `onInput`, …) whose body contains literal
9
+ * `<id>({type: 'X', …})` call sites. Wraps with
10
+ * `Object.assign(arrow, { __lluiVariants: ['X', …] })`. The
11
+ * runtime in `@llui/dom` `elements.ts` / `el-split.ts` reads the
12
+ * metadata at bind time and registers the variants on the active
13
+ * component instance for the lifetime of the binding's scope.
14
+ *
15
+ * 2. `injectScopeVariantRegistrations` — handles the
16
+ * dispatch-translation case that pass (1) can't follow:
17
+ * `<bag>.connect(get, sendFn, …)` where `sendFn` is a user-
18
+ * defined function that translates library Msgs into app Msgs via
19
+ * `dispatch({type: 'X', …})`. Static analysis of the library's
20
+ * internal onClick can't see across this hop. The pass detects the
21
+ * `*.connect(get, sendFn, …)` syntactic pattern, follows `sendFn`
22
+ * to its declaration, scans the body for literal dispatches, and
23
+ * inserts a runtime `__registerScopeVariants(['X', …])` call
24
+ * immediately before the connect call. Lifetime semantics fall
25
+ * out of the render context: the call's active scope is whatever
26
+ * `each(...)` / `branch(...)` / root scope happens to be live when
27
+ * the view evaluates that statement.
28
+ *
29
+ * Both passes are gated on `devMode || emitAgentMetadata` in
30
+ * `transform.ts`. Production bundles without agent integration get
31
+ * neither the per-handler `Object.assign` cost nor the registration
32
+ * statements.
33
+ *
34
+ * False positives are deliberate. The alternative — proving the
35
+ * callee resolves to the destructured `send` from a `View` bag, or
36
+ * tracing function calls across files — would require full scope
37
+ * tracking the compiler doesn't do. In practice the patterns
38
+ * `<id>({type:'X',…})` and `*.connect(get, fn, …)` are reliable
39
+ * shape-level signals; an extra entry in the live descriptor
40
+ * registry just means the agent sees one more "affordable variant"
41
+ * than necessary — never a wrong dispatch, never a runtime error.
42
+ *
43
+ * False negatives stay where they were: non-literal `type` values
44
+ * (`send({ type: nextStep })`), and dispatch translators bound via
45
+ * patterns other than `*.connect(get, fn, …)`. Apps that hit those
46
+ * cases declare `agentAffordances` on the component def — the
47
+ * documented escape hatch.
48
+ *
49
+ * @see agent spec §5.2, §12.2
50
+ * @see @llui/dom binding-descriptors.ts (runtime registry + helper)
51
+ */
52
+ /**
53
+ * Walks every `ArrowFunction` and `FunctionExpression` in the source
54
+ * and wraps any whose body contains literal `<id>({type:'X', …})`
55
+ * dispatches with `Object.assign(fn, {__lluiVariants: ['X', …]})`.
56
+ *
57
+ * The runtime (in `@llui/dom` `elements.ts` / `el-split.ts`) reads
58
+ * `__lluiVariants` from event-handler bindings only — so tags placed
59
+ * on functions in non-handler positions (a const declared but never
60
+ * bound, an arrow passed to `Array.filter`, a view function whose
61
+ * body has nested handlers with dispatches) are runtime-inert. The
62
+ * compiler tags generously; the runtime registers selectively.
63
+ *
64
+ * Universal scope means three concrete patterns all surface their
65
+ * variants without the app author having to think about it:
66
+ *
67
+ * 1. **Inline event-handler arrows** —
68
+ * `onClick: () => send({type:'X'})` (the original Pass 1 case).
69
+ * 2. **Const-bound translator functions** —
70
+ * `const sendMenu = (m) => dispatch({type:'Y'})` paired with
71
+ * `*.connect(get, sendMenu, …)` (the original Pass 3 case). The
72
+ * tag travels with the function reference; library connect impls
73
+ * use `tagSend(send, libVariants, fn)` to propagate it onto
74
+ * returned handlers.
75
+ * 3. **Positional-arg handlers** —
76
+ * `helper(label, () => send({type:'Z'}))` where `helper` is an
77
+ * app-defined wrapper like `navButton(label, onClick)` that
78
+ * eventually binds the function as an event listener. The arrow
79
+ * is still tagged at its declaration site, and the runtime reads
80
+ * the tag when the wrapper binds it.
81
+ *
82
+ * False positives are deliberate. The alternative — proving that a
83
+ * tagged arrow actually reaches an event-handler binding — would
84
+ * require cross-function, cross-file flow analysis the compiler
85
+ * doesn't do. In practice the cost of an over-tagged arrow is bytes,
86
+ * not behavior: the runtime never reads the tag from non-handler
87
+ * bindings.
88
+ *
89
+ * Pass 2's `collectLocalFns` resolves identifiers to their original
90
+ * arrow/function initializers; this pass replaces those initializers
91
+ * with `Object.assign(arrow, {…})` wrappers. Run Pass 2 BEFORE Pass 1
92
+ * so the resolver still sees raw arrows.
93
+ *
94
+ * Already-wrapped functions (CallExpressions, including user-applied
95
+ * `tagSend(...)` or this pass's own prior output) are skipped — the
96
+ * pass only fires on bare arrow/function expressions.
97
+ */
98
+ export declare function tagDispatchHandlers(node: ts.SourceFile, f: ts.NodeFactory): ts.SourceFile;
99
+ export interface InjectResult {
100
+ sf: ts.SourceFile;
101
+ /** True when at least one `__registerScopeVariants(...)` call was inserted. */
102
+ injected: boolean;
103
+ }
104
+ export declare function injectScopeVariantRegistrations(node: ts.SourceFile, f: ts.NodeFactory): InjectResult;
105
+ //# sourceMappingURL=binding-descriptors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-descriptors.d.ts","sourceRoot":"","sources":["../src/binding-descriptors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAIH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,UAAU,CA0BzF;AA8BD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,EAAE,CAAC,UAAU,CAAA;IACjB,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,EAAE,CAAC,UAAU,EACnB,CAAC,EAAE,EAAE,CAAC,WAAW,GAChB,YAAY,CA0Fd"}
@@ -0,0 +1,340 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Compiler passes that surface the Msg variants currently dispatchable
4
+ * from rendered UI to the agent layer's `list_actions`. Two passes
5
+ * cover two distinct dispatch shapes:
6
+ *
7
+ * 1. `tagEventHandlerSends` — tags event-handler arrow functions
8
+ * (`onClick`, `onInput`, …) whose body contains literal
9
+ * `<id>({type: 'X', …})` call sites. Wraps with
10
+ * `Object.assign(arrow, { __lluiVariants: ['X', …] })`. The
11
+ * runtime in `@llui/dom` `elements.ts` / `el-split.ts` reads the
12
+ * metadata at bind time and registers the variants on the active
13
+ * component instance for the lifetime of the binding's scope.
14
+ *
15
+ * 2. `injectScopeVariantRegistrations` — handles the
16
+ * dispatch-translation case that pass (1) can't follow:
17
+ * `<bag>.connect(get, sendFn, …)` where `sendFn` is a user-
18
+ * defined function that translates library Msgs into app Msgs via
19
+ * `dispatch({type: 'X', …})`. Static analysis of the library's
20
+ * internal onClick can't see across this hop. The pass detects the
21
+ * `*.connect(get, sendFn, …)` syntactic pattern, follows `sendFn`
22
+ * to its declaration, scans the body for literal dispatches, and
23
+ * inserts a runtime `__registerScopeVariants(['X', …])` call
24
+ * immediately before the connect call. Lifetime semantics fall
25
+ * out of the render context: the call's active scope is whatever
26
+ * `each(...)` / `branch(...)` / root scope happens to be live when
27
+ * the view evaluates that statement.
28
+ *
29
+ * Both passes are gated on `devMode || emitAgentMetadata` in
30
+ * `transform.ts`. Production bundles without agent integration get
31
+ * neither the per-handler `Object.assign` cost nor the registration
32
+ * statements.
33
+ *
34
+ * False positives are deliberate. The alternative — proving the
35
+ * callee resolves to the destructured `send` from a `View` bag, or
36
+ * tracing function calls across files — would require full scope
37
+ * tracking the compiler doesn't do. In practice the patterns
38
+ * `<id>({type:'X',…})` and `*.connect(get, fn, …)` are reliable
39
+ * shape-level signals; an extra entry in the live descriptor
40
+ * registry just means the agent sees one more "affordable variant"
41
+ * than necessary — never a wrong dispatch, never a runtime error.
42
+ *
43
+ * False negatives stay where they were: non-literal `type` values
44
+ * (`send({ type: nextStep })`), and dispatch translators bound via
45
+ * patterns other than `*.connect(get, fn, …)`. Apps that hit those
46
+ * cases declare `agentAffordances` on the component def — the
47
+ * documented escape hatch.
48
+ *
49
+ * @see agent spec §5.2, §12.2
50
+ * @see @llui/dom binding-descriptors.ts (runtime registry + helper)
51
+ */
52
+ // ── Pass 1: dispatch-handler tagger (universal) ─────────────────────
53
+ /**
54
+ * Walks every `ArrowFunction` and `FunctionExpression` in the source
55
+ * and wraps any whose body contains literal `<id>({type:'X', …})`
56
+ * dispatches with `Object.assign(fn, {__lluiVariants: ['X', …]})`.
57
+ *
58
+ * The runtime (in `@llui/dom` `elements.ts` / `el-split.ts`) reads
59
+ * `__lluiVariants` from event-handler bindings only — so tags placed
60
+ * on functions in non-handler positions (a const declared but never
61
+ * bound, an arrow passed to `Array.filter`, a view function whose
62
+ * body has nested handlers with dispatches) are runtime-inert. The
63
+ * compiler tags generously; the runtime registers selectively.
64
+ *
65
+ * Universal scope means three concrete patterns all surface their
66
+ * variants without the app author having to think about it:
67
+ *
68
+ * 1. **Inline event-handler arrows** —
69
+ * `onClick: () => send({type:'X'})` (the original Pass 1 case).
70
+ * 2. **Const-bound translator functions** —
71
+ * `const sendMenu = (m) => dispatch({type:'Y'})` paired with
72
+ * `*.connect(get, sendMenu, …)` (the original Pass 3 case). The
73
+ * tag travels with the function reference; library connect impls
74
+ * use `tagSend(send, libVariants, fn)` to propagate it onto
75
+ * returned handlers.
76
+ * 3. **Positional-arg handlers** —
77
+ * `helper(label, () => send({type:'Z'}))` where `helper` is an
78
+ * app-defined wrapper like `navButton(label, onClick)` that
79
+ * eventually binds the function as an event listener. The arrow
80
+ * is still tagged at its declaration site, and the runtime reads
81
+ * the tag when the wrapper binds it.
82
+ *
83
+ * False positives are deliberate. The alternative — proving that a
84
+ * tagged arrow actually reaches an event-handler binding — would
85
+ * require cross-function, cross-file flow analysis the compiler
86
+ * doesn't do. In practice the cost of an over-tagged arrow is bytes,
87
+ * not behavior: the runtime never reads the tag from non-handler
88
+ * bindings.
89
+ *
90
+ * Pass 2's `collectLocalFns` resolves identifiers to their original
91
+ * arrow/function initializers; this pass replaces those initializers
92
+ * with `Object.assign(arrow, {…})` wrappers. Run Pass 2 BEFORE Pass 1
93
+ * so the resolver still sees raw arrows.
94
+ *
95
+ * Already-wrapped functions (CallExpressions, including user-applied
96
+ * `tagSend(...)` or this pass's own prior output) are skipped — the
97
+ * pass only fires on bare arrow/function expressions.
98
+ */
99
+ export function tagDispatchHandlers(node, f) {
100
+ const transformer = (ctx) => {
101
+ const visit = (n) => {
102
+ if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) {
103
+ // Visit children first so nested arrows are tagged before
104
+ // their parents see them. The parent's
105
+ // `collectLiteralSendVariants` walks the original (unwrapped)
106
+ // body — the recursive collect doesn't descend into nested
107
+ // function bodies (that would cause an outer arrow to inherit
108
+ // every dispatch from every closure within it, which is too
109
+ // permissive).
110
+ const inner = ts.visitEachChild(n, visit, ctx);
111
+ const variants = collectLiteralSendVariants(inner.body);
112
+ if (variants.length > 0) {
113
+ return wrapWithVariants(inner, variants, f);
114
+ }
115
+ return inner;
116
+ }
117
+ return ts.visitEachChild(n, visit, ctx);
118
+ };
119
+ return (sf) => ts.visitEachChild(sf, visit, ctx);
120
+ };
121
+ const result = ts.transform(node, [transformer]);
122
+ const out = result.transformed[0];
123
+ result.dispose();
124
+ return out;
125
+ }
126
+ function wrapWithVariants(arrow, variants, f) {
127
+ return f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('Object'), 'assign'), undefined, [
128
+ arrow,
129
+ f.createObjectLiteralExpression([
130
+ f.createPropertyAssignment('__lluiVariants', f.createArrayLiteralExpression(variants.map((v) => f.createStringLiteral(v)), false)),
131
+ ], false),
132
+ ]);
133
+ }
134
+ export function injectScopeVariantRegistrations(node, f) {
135
+ let injected = false;
136
+ const transformer = (ctx) => {
137
+ /**
138
+ * Tracks whether we're inside a function body. Top-level
139
+ * (module-scope) connect calls are skipped — there's no render
140
+ * context active when module code runs, so the registration
141
+ * would silently no-op anyway, and emitting it adds noise. Apps
142
+ * with module-scope translators (a single shared `parts` re-used
143
+ * across views) declare `agentAffordances` instead.
144
+ */
145
+ let inFunction = 0;
146
+ function rewriteBlock(block) {
147
+ const stmts = block.statements;
148
+ const localFns = collectLocalFns(stmts);
149
+ const out = [];
150
+ for (const stmt of stmts) {
151
+ const visited = ts.visitNode(stmt, (n) => visitNode(n, localFns));
152
+ out.push(visited);
153
+ }
154
+ if (ts.isSourceFile(block)) {
155
+ return f.updateSourceFile(block, out);
156
+ }
157
+ return f.updateBlock(block, out);
158
+ }
159
+ function visitNode(n, localFns) {
160
+ if (ts.isBlock(n)) {
161
+ return rewriteBlock(n);
162
+ }
163
+ // Track function-body nesting so we know whether the current
164
+ // call site can plausibly run within a render context. Arrow
165
+ // functions and function expressions/declarations both qualify.
166
+ if (ts.isArrowFunction(n) ||
167
+ ts.isFunctionExpression(n) ||
168
+ ts.isFunctionDeclaration(n) ||
169
+ ts.isMethodDeclaration(n)) {
170
+ inFunction++;
171
+ const r = ts.visitEachChild(n, (c) => visitNode(c, localFns), ctx);
172
+ inFunction--;
173
+ return r;
174
+ }
175
+ if (ts.isCallExpression(n) && inFunction > 0 && isConnectCallShape(n)) {
176
+ const sendArg = n.arguments[1];
177
+ const sendFn = resolveSendFn(sendArg, localFns);
178
+ if (sendFn) {
179
+ const variants = collectLiteralSendVariants(sendFn.body);
180
+ if (variants.length > 0) {
181
+ // Replace the call expression with a comma expression:
182
+ // (__registerScopeVariants([...]), originalCall)
183
+ // The comma keeps the call's value position intact (so
184
+ // `const parts = popover.connect(...)` still binds the
185
+ // original return), and ensures the registration fires
186
+ // *before* the call returns. This positions correctly
187
+ // regardless of the surrounding function context: view
188
+ // body, render callback inside `each(...)`, or any
189
+ // nested helper called from within a view.
190
+ injected = true;
191
+ const inner = ts.visitEachChild(n, (c) => visitNode(c, localFns), ctx);
192
+ return f.createParenthesizedExpression(f.createBinaryExpression(emitRegisterCall(variants, f), f.createToken(ts.SyntaxKind.CommaToken), inner));
193
+ }
194
+ }
195
+ }
196
+ return ts.visitEachChild(n, (c) => visitNode(c, localFns), ctx);
197
+ }
198
+ return (sf) => rewriteBlock(sf);
199
+ };
200
+ const result = ts.transform(node, [transformer]);
201
+ const out = result.transformed[0];
202
+ result.dispose();
203
+ return { sf: out, injected };
204
+ }
205
+ /**
206
+ * Collect `const fn = (m) => { … }` / `const fn = function(m){ … }`
207
+ * declarations in `stmts` so an identifier passed to a connect call
208
+ * later in the same scope can resolve to its body. Conservative —
209
+ * only direct function-valued initializers count; aliasing
210
+ * (`const a = b`) is not followed.
211
+ */
212
+ function collectLocalFns(stmts) {
213
+ const out = new Map();
214
+ for (const stmt of stmts) {
215
+ if (!ts.isVariableStatement(stmt))
216
+ continue;
217
+ for (const decl of stmt.declarationList.declarations) {
218
+ if (!ts.isIdentifier(decl.name))
219
+ continue;
220
+ const init = decl.initializer;
221
+ if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
222
+ out.set(decl.name.text, init);
223
+ }
224
+ }
225
+ }
226
+ return out;
227
+ }
228
+ function isConnectCallShape(node) {
229
+ if (!ts.isPropertyAccessExpression(node.expression))
230
+ return false;
231
+ if (node.expression.name.text !== 'connect')
232
+ return false;
233
+ return node.arguments.length >= 2;
234
+ }
235
+ function resolveSendFn(arg, localFns) {
236
+ if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg))
237
+ return arg;
238
+ if (ts.isIdentifier(arg))
239
+ return localFns.get(arg.text) ?? null;
240
+ return null;
241
+ }
242
+ function emitRegisterCall(variants, f) {
243
+ return f.createCallExpression(f.createIdentifier('__registerScopeVariants'), undefined, [
244
+ f.createArrayLiteralExpression(variants.map((v) => f.createStringLiteral(v)), false),
245
+ ]);
246
+ }
247
+ // ── Shared: literal-send variant collection ──────────────────────────
248
+ /**
249
+ * Walk `node`, collecting every literal type string from
250
+ * `<id>({ type: 'literal', … })` call sites. De-dupes while preserving
251
+ * first-seen order so the emitted array reads naturally for anyone
252
+ * inspecting the compiled output.
253
+ *
254
+ * The walk stops at nested function/arrow boundaries — every function
255
+ * "owns" its own dispatches, and the universal tagger visits each
256
+ * function separately. Without this guard, an outer arrow whose body
257
+ * includes a nested onClick arrow would be tagged with the inner
258
+ * arrow's variants too: a parent view tagged with every dispatch in
259
+ * every child handler. The runtime only reads the tag from event-
260
+ * handler bindings, so functionally that's harmless — but it bloats
261
+ * the wrapped output and obscures intent.
262
+ */
263
+ function collectLiteralSendVariants(body) {
264
+ const seen = new Set();
265
+ const out = [];
266
+ function visit(n) {
267
+ // Stop at every nested function/arrow boundary — those functions
268
+ // own their own variants and are tagged independently. An outer
269
+ // arrow whose body contains `setTimeout(() => send(...), 100)`
270
+ // doesn't dispatch directly when invoked; the inner arrow does
271
+ // when the timer fires. Counting the inner's variants on the
272
+ // outer would be too permissive.
273
+ if (ts.isArrowFunction(n) ||
274
+ ts.isFunctionExpression(n) ||
275
+ ts.isFunctionDeclaration(n) ||
276
+ ts.isMethodDeclaration(n)) {
277
+ return;
278
+ }
279
+ if (ts.isCallExpression(n)) {
280
+ const callee = n.expression;
281
+ const first = n.arguments[0];
282
+ if (callee &&
283
+ ts.isIdentifier(callee) &&
284
+ isDispatcherName(callee.text) &&
285
+ first &&
286
+ ts.isObjectLiteralExpression(first)) {
287
+ const variant = readTypeLiteral(first);
288
+ if (variant !== null && !seen.has(variant)) {
289
+ seen.add(variant);
290
+ out.push(variant);
291
+ }
292
+ }
293
+ }
294
+ ts.forEachChild(n, visit);
295
+ }
296
+ visit(body);
297
+ return out;
298
+ }
299
+ /**
300
+ * Recognize a callee as a dispatcher by name. The convention in LLui
301
+ * apps is `send` (the framework-provided dispatch) or `dispatch` (a
302
+ * common app-level alias), plus `send*` / `dispatch*` prefixes for
303
+ * named translators (`sendMenu`, `dispatchMenu`).
304
+ *
305
+ * Without this filter, the tagger would treat element-helper calls
306
+ * like `button({type: 'button'})` and `input({type: 'email'})` as
307
+ * dispatch sites — they syntactically match `<Identifier>({type:
308
+ * literal})`, but they construct DOM, not Msgs. Phantom variants
309
+ * like `button` and `email` would then leak into the binding-
310
+ * descriptor registry and surface as agent affordances. Filtering
311
+ * by name keeps the convention narrow enough that false positives
312
+ * are nearly zero while leaving room for translator naming.
313
+ *
314
+ * Apps that use unconventional dispatcher names (`forward`,
315
+ * `tellMenu`) need to either rename to the convention or wrap their
316
+ * handlers with the runtime `tagSend`/`tagVariants` helpers
317
+ * explicitly — the same escape hatch library code already uses.
318
+ */
319
+ function isDispatcherName(name) {
320
+ return /^(send|dispatch)/i.test(name);
321
+ }
322
+ function readTypeLiteral(obj) {
323
+ for (const prop of obj.properties) {
324
+ if (!ts.isPropertyAssignment(prop))
325
+ continue;
326
+ if (!prop.name)
327
+ continue;
328
+ const nameOk = (ts.isIdentifier(prop.name) && prop.name.text === 'type') ||
329
+ (ts.isStringLiteral(prop.name) && prop.name.text === 'type');
330
+ if (!nameOk)
331
+ continue;
332
+ const init = prop.initializer;
333
+ if (ts.isStringLiteral(init))
334
+ return init.text;
335
+ if (ts.isNoSubstitutionTemplateLiteral(init))
336
+ return init.text;
337
+ }
338
+ return null;
339
+ }
340
+ //# sourceMappingURL=binding-descriptors.js.map