@alloy-js/core 0.23.0-dev.1 → 0.23.0-dev.11

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 (337) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +80 -0
  3. package/dist/src/binder.d.ts +2 -0
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +60 -12
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/components/AccessExpression.d.ts +78 -0
  8. package/dist/src/components/AccessExpression.d.ts.map +1 -0
  9. package/dist/src/components/AccessExpression.js +218 -0
  10. package/dist/src/components/AccessExpression.js.map +1 -0
  11. package/dist/src/components/AccessExpression.test.d.ts +2 -0
  12. package/dist/src/components/AccessExpression.test.d.ts.map +1 -0
  13. package/dist/src/components/AccessExpression.test.js +137 -0
  14. package/dist/src/components/AccessExpression.test.js.map +1 -0
  15. package/dist/src/components/AppendFile.d.ts.map +1 -1
  16. package/dist/src/components/AppendFile.js +14 -3
  17. package/dist/src/components/AppendFile.js.map +1 -1
  18. package/dist/src/components/Block.js +1 -1
  19. package/dist/src/components/Block.js.map +1 -1
  20. package/dist/src/components/Declaration.d.ts.map +1 -1
  21. package/dist/src/components/Declaration.js +2 -1
  22. package/dist/src/components/Declaration.js.map +1 -1
  23. package/dist/src/components/For.d.ts.map +1 -1
  24. package/dist/src/components/For.js +1 -1
  25. package/dist/src/components/For.js.map +1 -1
  26. package/dist/src/components/List.d.ts.map +1 -1
  27. package/dist/src/components/List.js +1 -1
  28. package/dist/src/components/List.js.map +1 -1
  29. package/dist/src/components/Prose.js +2 -2
  30. package/dist/src/components/Prose.js.map +1 -1
  31. package/dist/src/components/Scope.d.ts.map +1 -1
  32. package/dist/src/components/Scope.js +6 -1
  33. package/dist/src/components/Scope.js.map +1 -1
  34. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  35. package/dist/src/components/SourceDirectory.js +1 -2
  36. package/dist/src/components/SourceDirectory.js.map +1 -1
  37. package/dist/src/components/Switch.d.ts.map +1 -1
  38. package/dist/src/components/Switch.js +1 -1
  39. package/dist/src/components/Switch.js.map +1 -1
  40. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  41. package/dist/src/components/TemplateFile.js +18 -3
  42. package/dist/src/components/TemplateFile.js.map +1 -1
  43. package/dist/src/components/index.d.ts +1 -0
  44. package/dist/src/components/index.d.ts.map +1 -1
  45. package/dist/src/components/index.js +1 -0
  46. package/dist/src/components/index.js.map +1 -1
  47. package/dist/src/content-slot.d.ts.map +1 -1
  48. package/dist/src/content-slot.js +7 -6
  49. package/dist/src/content-slot.js.map +1 -1
  50. package/dist/src/context.d.ts.map +1 -1
  51. package/dist/src/context.js +10 -3
  52. package/dist/src/context.js.map +1 -1
  53. package/dist/src/debug/cli.d.ts +6 -0
  54. package/dist/src/debug/cli.d.ts.map +1 -0
  55. package/dist/src/{debug.js → debug/cli.js} +79 -82
  56. package/dist/src/debug/cli.js.map +1 -0
  57. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  58. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  59. package/dist/src/debug/diagnostics.test.js +46 -0
  60. package/dist/src/debug/diagnostics.test.js.map +1 -0
  61. package/dist/src/debug/effects.d.ts +81 -0
  62. package/dist/src/debug/effects.d.ts.map +1 -0
  63. package/dist/src/debug/effects.js +358 -0
  64. package/dist/src/debug/effects.js.map +1 -0
  65. package/dist/src/debug/effects.test.d.ts +2 -0
  66. package/dist/src/debug/effects.test.d.ts.map +1 -0
  67. package/dist/src/debug/effects.test.js +256 -0
  68. package/dist/src/debug/effects.test.js.map +1 -0
  69. package/dist/src/debug/files.d.ts +14 -0
  70. package/dist/src/debug/files.d.ts.map +1 -0
  71. package/dist/src/debug/files.js +29 -0
  72. package/dist/src/debug/files.js.map +1 -0
  73. package/dist/src/debug/files.test.d.ts +2 -0
  74. package/dist/src/debug/files.test.d.ts.map +1 -0
  75. package/dist/src/debug/files.test.js +66 -0
  76. package/dist/src/debug/files.test.js.map +1 -0
  77. package/dist/src/debug/index.d.ts +63 -0
  78. package/dist/src/debug/index.d.ts.map +1 -0
  79. package/dist/src/debug/index.js +71 -0
  80. package/dist/src/debug/index.js.map +1 -0
  81. package/dist/src/debug/message-format.test.d.ts +2 -0
  82. package/dist/src/debug/message-format.test.d.ts.map +1 -0
  83. package/dist/src/debug/message-format.test.js +700 -0
  84. package/dist/src/debug/message-format.test.js.map +1 -0
  85. package/dist/src/debug/render-tree-orphans.test.d.ts +2 -0
  86. package/dist/src/debug/render-tree-orphans.test.d.ts.map +1 -0
  87. package/dist/src/debug/render-tree-orphans.test.js +297 -0
  88. package/dist/src/debug/render-tree-orphans.test.js.map +1 -0
  89. package/dist/src/debug/render.d.ts +57 -0
  90. package/dist/src/debug/render.d.ts.map +1 -0
  91. package/dist/src/debug/render.js +472 -0
  92. package/dist/src/debug/render.js.map +1 -0
  93. package/dist/src/debug/render.test.d.ts +2 -0
  94. package/dist/src/debug/render.test.d.ts.map +1 -0
  95. package/dist/src/debug/render.test.js +291 -0
  96. package/dist/src/debug/render.test.js.map +1 -0
  97. package/dist/src/debug/serialize.d.ts +9 -0
  98. package/dist/src/debug/serialize.d.ts.map +1 -0
  99. package/dist/src/debug/serialize.js +70 -0
  100. package/dist/src/debug/serialize.js.map +1 -0
  101. package/dist/src/debug/symbols.d.ts +16 -0
  102. package/dist/src/debug/symbols.d.ts.map +1 -0
  103. package/dist/src/debug/symbols.js +196 -0
  104. package/dist/src/debug/symbols.js.map +1 -0
  105. package/dist/src/debug/symbols.test.d.ts +2 -0
  106. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  107. package/dist/src/debug/symbols.test.js +93 -0
  108. package/dist/src/debug/symbols.test.js.map +1 -0
  109. package/dist/src/debug/trace-writer.d.ts +55 -0
  110. package/dist/src/debug/trace-writer.d.ts.map +1 -0
  111. package/dist/src/debug/trace-writer.js +658 -0
  112. package/dist/src/debug/trace-writer.js.map +1 -0
  113. package/dist/src/debug/trace.d.ts +342 -0
  114. package/dist/src/debug/trace.d.ts.map +1 -0
  115. package/dist/src/debug/trace.js +446 -0
  116. package/dist/src/debug/trace.js.map +1 -0
  117. package/dist/src/devtools/devtools-protocol.d.ts +389 -0
  118. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  119. package/dist/src/devtools/devtools-protocol.js +2 -0
  120. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  121. package/dist/src/devtools/devtools-server.browser.d.ts +23 -0
  122. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  123. package/dist/src/devtools/devtools-server.browser.js +33 -0
  124. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  125. package/dist/src/devtools/devtools-server.d.ts +66 -0
  126. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  127. package/dist/src/devtools/devtools-server.js +444 -0
  128. package/dist/src/devtools/devtools-server.js.map +1 -0
  129. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  130. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  131. package/dist/src/devtools/devtools-transport.js +114 -0
  132. package/dist/src/devtools/devtools-transport.js.map +1 -0
  133. package/dist/src/devtools-entry.browser.d.ts +4 -0
  134. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  135. package/dist/src/devtools-entry.browser.js +2 -0
  136. package/dist/src/devtools-entry.browser.js.map +1 -0
  137. package/dist/src/devtools-entry.d.ts +4 -0
  138. package/dist/src/devtools-entry.d.ts.map +1 -0
  139. package/dist/src/devtools-entry.js +2 -0
  140. package/dist/src/devtools-entry.js.map +1 -0
  141. package/dist/src/diagnostics.d.ts +34 -0
  142. package/dist/src/diagnostics.d.ts.map +1 -0
  143. package/dist/src/diagnostics.js +89 -0
  144. package/dist/src/diagnostics.js.map +1 -0
  145. package/dist/src/index.d.ts +3 -2
  146. package/dist/src/index.d.ts.map +1 -1
  147. package/dist/src/index.js +3 -2
  148. package/dist/src/index.js.map +1 -1
  149. package/dist/src/print-hook.d.ts +14 -0
  150. package/dist/src/print-hook.d.ts.map +1 -0
  151. package/dist/src/print-hook.js +10 -0
  152. package/dist/src/print-hook.js.map +1 -0
  153. package/dist/src/reactive-union-set.d.ts.map +1 -1
  154. package/dist/src/reactive-union-set.js +28 -3
  155. package/dist/src/reactive-union-set.js.map +1 -1
  156. package/dist/src/reactivity.d.ts +60 -7
  157. package/dist/src/reactivity.d.ts.map +1 -1
  158. package/dist/src/reactivity.js +308 -39
  159. package/dist/src/reactivity.js.map +1 -1
  160. package/dist/src/render-stack.d.ts +18 -1
  161. package/dist/src/render-stack.d.ts.map +1 -1
  162. package/dist/src/render-stack.js +61 -1
  163. package/dist/src/render-stack.js.map +1 -1
  164. package/dist/src/render.d.ts +8 -15
  165. package/dist/src/render.d.ts.map +1 -1
  166. package/dist/src/render.js +424 -109
  167. package/dist/src/render.js.map +1 -1
  168. package/dist/src/resource.d.ts.map +1 -1
  169. package/dist/src/resource.js +5 -0
  170. package/dist/src/resource.js.map +1 -1
  171. package/dist/src/scheduler.d.ts +13 -0
  172. package/dist/src/scheduler.d.ts.map +1 -1
  173. package/dist/src/scheduler.js +150 -13
  174. package/dist/src/scheduler.js.map +1 -1
  175. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  176. package/dist/src/symbols/basic-symbol.js +6 -1
  177. package/dist/src/symbols/basic-symbol.js.map +1 -1
  178. package/dist/src/symbols/decl.d.ts.map +1 -1
  179. package/dist/src/symbols/decl.js +5 -1
  180. package/dist/src/symbols/decl.js.map +1 -1
  181. package/dist/src/symbols/output-scope.d.ts +2 -1
  182. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  183. package/dist/src/symbols/output-scope.js +13 -8
  184. package/dist/src/symbols/output-scope.js.map +1 -1
  185. package/dist/src/symbols/output-symbol.d.ts +1 -0
  186. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  187. package/dist/src/symbols/output-symbol.js +25 -8
  188. package/dist/src/symbols/output-symbol.js.map +1 -1
  189. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  190. package/dist/src/symbols/symbol-flow.js +24 -8
  191. package/dist/src/symbols/symbol-flow.js.map +1 -1
  192. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  193. package/dist/src/symbols/symbol-slot.js +15 -0
  194. package/dist/src/symbols/symbol-slot.js.map +1 -1
  195. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  196. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  197. package/dist/src/symbols/symbol-slot.test.js +35 -0
  198. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  199. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  200. package/dist/src/symbols/symbol-table.js +6 -5
  201. package/dist/src/symbols/symbol-table.js.map +1 -1
  202. package/dist/src/trace.d.ts +2 -0
  203. package/dist/src/trace.d.ts.map +1 -0
  204. package/dist/src/trace.js +2 -0
  205. package/dist/src/trace.js.map +1 -0
  206. package/dist/src/tracer.d.ts +2 -228
  207. package/dist/src/tracer.d.ts.map +1 -1
  208. package/dist/src/tracer.js +5 -298
  209. package/dist/src/tracer.js.map +1 -1
  210. package/dist/src/utils.d.ts.map +1 -1
  211. package/dist/src/utils.js +17 -9
  212. package/dist/src/utils.js.map +1 -1
  213. package/dist/test/components/append-file.test.d.ts.map +1 -1
  214. package/dist/test/components/append-file.test.js +18 -10
  215. package/dist/test/components/append-file.test.js.map +1 -1
  216. package/dist/test/components/template-file.test.d.ts.map +1 -1
  217. package/dist/test/components/template-file.test.js +6 -4
  218. package/dist/test/components/template-file.test.js.map +1 -1
  219. package/dist/test/lazy-isempty.test.d.ts +2 -0
  220. package/dist/test/lazy-isempty.test.d.ts.map +1 -0
  221. package/dist/test/lazy-isempty.test.js +89 -0
  222. package/dist/test/lazy-isempty.test.js.map +1 -0
  223. package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
  224. package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
  225. package/dist/test/reactive-union-set-disposers.test.js +98 -0
  226. package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
  227. package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
  228. package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
  229. package/dist/test/reactivity/shallow-reactive.test.js +52 -0
  230. package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
  231. package/dist/test/rendering/basic.test.js +3 -0
  232. package/dist/test/rendering/basic.test.js.map +1 -1
  233. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -1
  234. package/dist/test/rendering/print-render-stack.test.js +91 -98
  235. package/dist/test/rendering/print-render-stack.test.js.map +1 -1
  236. package/dist/test/scheduler-extended.test.d.ts +2 -0
  237. package/dist/test/scheduler-extended.test.d.ts.map +1 -0
  238. package/dist/test/scheduler-extended.test.js +96 -0
  239. package/dist/test/scheduler-extended.test.js.map +1 -0
  240. package/dist/test/scheduler.test.d.ts +2 -0
  241. package/dist/test/scheduler.test.d.ts.map +1 -0
  242. package/dist/test/scheduler.test.js +46 -0
  243. package/dist/test/scheduler.test.js.map +1 -0
  244. package/dist/testing/create-test-wrapper.d.ts +1 -1
  245. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  246. package/dist/testing/create-test-wrapper.js +1 -1
  247. package/dist/testing/create-test-wrapper.js.map +1 -1
  248. package/dist/testing/devtools-utils.d.ts +35 -0
  249. package/dist/testing/devtools-utils.d.ts.map +1 -0
  250. package/dist/testing/devtools-utils.js +162 -0
  251. package/dist/testing/devtools-utils.js.map +1 -0
  252. package/dist/testing/extend-expect.d.ts.map +1 -1
  253. package/dist/testing/extend-expect.js +63 -1
  254. package/dist/testing/extend-expect.js.map +1 -1
  255. package/dist/testing/render.d.ts +2 -2
  256. package/dist/testing/render.d.ts.map +1 -1
  257. package/dist/testing/render.js +2 -2
  258. package/dist/testing/render.js.map +1 -1
  259. package/dist/tsconfig.tsbuildinfo +1 -1
  260. package/package.json +21 -7
  261. package/scripts/copy-devtools-ui.mjs +26 -0
  262. package/src/binder.ts +117 -53
  263. package/src/components/AccessExpression.test.tsx +132 -0
  264. package/src/components/AccessExpression.tsx +344 -0
  265. package/src/components/AppendFile.tsx +14 -9
  266. package/src/components/Block.tsx +1 -1
  267. package/src/components/Declaration.tsx +2 -1
  268. package/src/components/For.tsx +14 -10
  269. package/src/components/List.tsx +7 -4
  270. package/src/components/Prose.tsx +1 -1
  271. package/src/components/Scope.tsx +6 -1
  272. package/src/components/SourceDirectory.tsx +1 -2
  273. package/src/components/Switch.tsx +11 -7
  274. package/src/components/TemplateFile.tsx +18 -9
  275. package/src/components/index.tsx +1 -0
  276. package/src/content-slot.tsx +7 -7
  277. package/src/context.ts +17 -6
  278. package/src/{debug.ts → debug/cli.ts} +114 -125
  279. package/src/debug/diagnostics.test.tsx +56 -0
  280. package/src/debug/effects.test.tsx +301 -0
  281. package/src/debug/effects.ts +531 -0
  282. package/src/debug/files.test.tsx +76 -0
  283. package/src/debug/files.ts +40 -0
  284. package/src/debug/index.ts +132 -0
  285. package/src/debug/message-format.test.tsx +759 -0
  286. package/src/debug/render-tree-orphans.test.tsx +344 -0
  287. package/src/debug/render.test.tsx +357 -0
  288. package/src/debug/render.ts +698 -0
  289. package/src/debug/serialize.ts +85 -0
  290. package/src/debug/symbols.test.tsx +105 -0
  291. package/src/debug/symbols.ts +322 -0
  292. package/src/debug/trace-writer.ts +969 -0
  293. package/src/debug/trace.ts +309 -0
  294. package/src/devtools/devtools-protocol.ts +497 -0
  295. package/src/devtools/devtools-server.browser.ts +62 -0
  296. package/src/devtools/devtools-server.ts +468 -0
  297. package/src/devtools/devtools-transport.ts +154 -0
  298. package/src/devtools-entry.browser.ts +48 -0
  299. package/src/devtools-entry.ts +48 -0
  300. package/src/diagnostics.ts +150 -0
  301. package/src/index.ts +2 -7
  302. package/src/print-hook.ts +22 -0
  303. package/src/reactive-union-set.ts +85 -44
  304. package/src/reactivity.ts +396 -58
  305. package/src/render-stack.ts +73 -1
  306. package/src/render.ts +544 -161
  307. package/src/resource.ts +28 -19
  308. package/src/scheduler.ts +209 -14
  309. package/src/symbols/basic-symbol.ts +6 -1
  310. package/src/symbols/decl.ts +5 -1
  311. package/src/symbols/output-scope.ts +21 -13
  312. package/src/symbols/output-symbol.ts +34 -14
  313. package/src/symbols/symbol-flow.ts +76 -39
  314. package/src/symbols/symbol-slot.test.tsx +41 -0
  315. package/src/symbols/symbol-slot.tsx +47 -20
  316. package/src/symbols/symbol-table.ts +6 -10
  317. package/src/trace.ts +1 -0
  318. package/src/tracer.ts +13 -242
  319. package/src/utils.tsx +31 -21
  320. package/temp/api.json +5700 -3015
  321. package/test/components/append-file.test.tsx +36 -29
  322. package/test/components/template-file.test.tsx +11 -11
  323. package/test/lazy-isempty.test.tsx +106 -0
  324. package/test/reactive-union-set-disposers.test.tsx +112 -0
  325. package/test/reactivity/shallow-reactive.test.tsx +56 -0
  326. package/test/rendering/basic.test.tsx +4 -0
  327. package/test/rendering/print-render-stack.test.tsx +52 -43
  328. package/test/scheduler-extended.test.tsx +122 -0
  329. package/test/scheduler.test.tsx +50 -0
  330. package/testing/create-test-wrapper.tsx +1 -1
  331. package/testing/devtools-utils.ts +245 -0
  332. package/testing/extend-expect.ts +89 -0
  333. package/testing/render.ts +2 -2
  334. package/testing/vitest.d.ts +9 -0
  335. package/dist/src/debug.d.ts +0 -14
  336. package/dist/src/debug.d.ts.map +0 -1
  337. package/dist/src/debug.js.map +0 -1
@@ -0,0 +1,344 @@
1
+ import {
2
+ Children,
3
+ childrenArray,
4
+ ComponentCreator,
5
+ ComponentDefinition,
6
+ computed,
7
+ isComponentCreator,
8
+ OutputSymbol,
9
+ Refkeyable,
10
+ symbolForRefkey,
11
+ takeSymbols,
12
+ } from "../index.js";
13
+
14
+ /**
15
+ * Base props that all language-specific Part components must include.
16
+ * Language packages extend this with additional props.
17
+ */
18
+ export interface BasePartProps {
19
+ refkey?: Refkeyable;
20
+ symbol?: OutputSymbol;
21
+ }
22
+
23
+ /**
24
+ * Configuration for creating a language-specific access expression component.
25
+ *
26
+ * @typeParam TPartProps - The Part component's props type (extends BasePartProps)
27
+ * @typeParam TPart - The descriptor type that formatPart receives
28
+ */
29
+ export interface AccessExpressionConfig<
30
+ TPartProps extends BasePartProps,
31
+ TPart,
32
+ > {
33
+ /**
34
+ * Convert Part props + resolved symbol into a plain descriptor object.
35
+ * Called once per Part during children processing. The returned descriptor
36
+ * is wrapped in a computed + getter delegation for reactive optimization.
37
+ */
38
+ createDescriptor(
39
+ props: TPartProps,
40
+ symbol: OutputSymbol | undefined,
41
+ first: boolean,
42
+ ): TPart;
43
+
44
+ /**
45
+ * Extract the base content from the first part (the leftmost identifier).
46
+ */
47
+ getBase(part: TPart): Children;
48
+
49
+ /**
50
+ * Format a non-first part given its descriptor and the previous part.
51
+ * Returns JSX children for that segment (e.g., `.foo`, `?.bar`, `[idx]`, `(args)`).
52
+ * `inCallChain` is true when rendering inside a chunked call chain.
53
+ */
54
+ formatPart(part: TPart, prevPart: TPart, inCallChain: boolean): Children;
55
+
56
+ /**
57
+ * Identify which parts are function calls, for call chain detection.
58
+ * When provided, the factory uses the chunked call chain algorithm
59
+ * (line breaks after each call group) when more than one call is detected.
60
+ * When omitted, the expression is always formatted linearly.
61
+ */
62
+ isCallPart?(part: TPart): boolean;
63
+
64
+ /**
65
+ * Additional check for whether call chain formatting should be used.
66
+ * Called only when `isCallPart` is provided and more than one call is detected.
67
+ * Return false to force linear formatting (e.g., TypeScript disables call
68
+ * chains when any part has `await`).
69
+ * Defaults to `() => true`.
70
+ */
71
+ canUseCallChains?(parts: TPart[]): boolean;
72
+
73
+ /**
74
+ * Post-process the accumulated expression after each part in linear
75
+ * (non-call-chain) mode. Use this for language-specific wrapping like
76
+ * TypeScript's `await` which wraps the entire expression so far.
77
+ * Defaults to identity (returns expression unchanged).
78
+ */
79
+ wrapPartResult?(
80
+ expression: Children,
81
+ part: TPart,
82
+ index: number,
83
+ isLast: boolean,
84
+ ): Children;
85
+ }
86
+
87
+ /**
88
+ * Create a language-specific access/member expression component pair.
89
+ *
90
+ * Returns `{ Expression, Part }` where:
91
+ * - `Expression` is the main component that collects Part children and renders the chain
92
+ * - `Part` is a no-op component whose props are consumed by Expression
93
+ *
94
+ * The factory handles:
95
+ * - Children collection and Part filtering
96
+ * - Symbol resolution (refkey → symbol via binder, single computed per part)
97
+ * - Reactive optimization (getter delegation over single computed per part)
98
+ * - Flattening nested Expression instances
99
+ * - `takeSymbols()` to prevent symbol leakage
100
+ * - Call chain detection and chunked formatting algorithm
101
+ */
102
+ export function createAccessExpression<
103
+ TPartProps extends BasePartProps,
104
+ TPart extends Record<string, unknown>,
105
+ >(config: AccessExpressionConfig<TPartProps, TPart>) {
106
+ // Additional component references to match during flattening.
107
+ // Used when the wrapper function (e.g. MemberExpression) differs
108
+ // from the inner Expression function.
109
+ const outerComponents: ComponentDefinition<any>[] = [];
110
+
111
+ function Expression(props: { children: Children }): Children {
112
+ const children = flattenExpression(childrenArray(() => props.children));
113
+ const parts = collectParts(children);
114
+ takeSymbols();
115
+
116
+ if (parts.length === 0) {
117
+ return <></>;
118
+ }
119
+
120
+ const { isCallPart, canUseCallChains } = config;
121
+
122
+ if (!isCallPart) {
123
+ return formatLinear(config, parts);
124
+ }
125
+
126
+ const isCallChain = computed(() => {
127
+ if (canUseCallChains && !canUseCallChains(parts)) {
128
+ return false;
129
+ }
130
+ let callCount = 0;
131
+ for (const part of parts) {
132
+ if (isCallPart(part)) callCount++;
133
+ }
134
+ return callCount > 1;
135
+ });
136
+
137
+ return () => {
138
+ return isCallChain.value ?
139
+ formatCallChain(config, parts)
140
+ : formatLinear(config, parts);
141
+ };
142
+ }
143
+
144
+ function Part(_props: TPartProps) {
145
+ // No-op — props are consumed by the parent Expression component.
146
+ }
147
+
148
+ function isExpressionComponent(child: unknown): boolean {
149
+ if (isComponentCreator(child, Expression)) return true;
150
+ for (const comp of outerComponents) {
151
+ if (isComponentCreator(child, comp as any)) return true;
152
+ }
153
+ return false;
154
+ }
155
+
156
+ function flattenExpression(children: Children[]): Children[] {
157
+ const flattened: Children[] = [];
158
+ for (const child of children) {
159
+ if (isExpressionComponent(child)) {
160
+ flattened.push(
161
+ ...flattenExpression(
162
+ childrenArray(() => (child as ComponentCreator).props.children),
163
+ ),
164
+ );
165
+ } else {
166
+ flattened.push(child);
167
+ }
168
+ }
169
+ return flattened;
170
+ }
171
+
172
+ function collectParts(children: Children[]): TPart[] {
173
+ const parts: TPart[] = [];
174
+ for (const child of children) {
175
+ if (!isComponentCreator(child, Part)) continue;
176
+ const partProps = child.props as TPartProps;
177
+ const first = parts.length === 0;
178
+
179
+ const symbolSource = computed(() => {
180
+ if (partProps.refkey) {
181
+ return symbolForRefkey(partProps.refkey).value;
182
+ } else if (partProps.symbol) {
183
+ return partProps.symbol;
184
+ }
185
+ return undefined;
186
+ });
187
+
188
+ const desc = computed(() =>
189
+ config.createDescriptor(partProps, symbolSource.value, first),
190
+ );
191
+
192
+ // Create getter-delegation object for reactive optimization.
193
+ // Property access on this object tracks the single `desc` computed.
194
+ const keys = Object.keys(
195
+ config.createDescriptor(partProps, undefined, first),
196
+ );
197
+ const proxy: Record<string, unknown> = {};
198
+ for (const key of keys) {
199
+ Object.defineProperty(proxy, key, {
200
+ get() {
201
+ return (desc.value as Record<string, unknown>)[key];
202
+ },
203
+ enumerable: true,
204
+ });
205
+ }
206
+
207
+ parts.push(proxy as TPart);
208
+ }
209
+ return parts;
210
+ }
211
+
212
+ /**
213
+ * Register an outer wrapper component for flattening support.
214
+ * Call this after creating the wrapper function:
215
+ * `registerOuterComponent(MemberExpression);`
216
+ */
217
+ function registerOuterComponent(component: ComponentDefinition<any>) {
218
+ outerComponents.push(component);
219
+ }
220
+
221
+ return { Expression, Part, registerOuterComponent };
222
+ }
223
+
224
+ /**
225
+ * Format parts linearly (no call chain grouping).
226
+ */
227
+ function formatLinear<TPartProps extends BasePartProps, TPart>(
228
+ config: AccessExpressionConfig<TPartProps, TPart>,
229
+ parts: TPart[],
230
+ ): Children {
231
+ return computed(() => {
232
+ let expression: Children = [];
233
+
234
+ for (let i = 0; i < parts.length; i++) {
235
+ const part = parts[i];
236
+
237
+ if (i === 0) {
238
+ (expression as Children[]).push(config.getBase(part));
239
+ } else {
240
+ const prevPart = parts[i - 1];
241
+ const partExpr = config.formatPart(part, prevPart, false);
242
+
243
+ if (Array.isArray(expression)) {
244
+ expression.push(partExpr);
245
+ } else {
246
+ expression = (
247
+ <>
248
+ {expression}
249
+ {partExpr}
250
+ </>
251
+ );
252
+ }
253
+ }
254
+
255
+ if (config.wrapPartResult) {
256
+ expression = config.wrapPartResult(
257
+ expression,
258
+ part,
259
+ i,
260
+ i === parts.length - 1,
261
+ );
262
+ }
263
+ }
264
+
265
+ return expression;
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Format parts as a call chain with indented line breaks at call boundaries.
271
+ */
272
+ function formatCallChain<TPartProps extends BasePartProps, TPart>(
273
+ config: AccessExpressionConfig<TPartProps, TPart>,
274
+ parts: TPart[],
275
+ ): Children {
276
+ return computed(() => {
277
+ const expression: Children[] = [];
278
+ const chunks: TPart[][] = [];
279
+ let pi = 0;
280
+
281
+ function pushPart() {
282
+ chunks.at(-1)!.push(parts[pi]);
283
+ pi++;
284
+ }
285
+
286
+ function pushChunk() {
287
+ chunks.push([]);
288
+ }
289
+
290
+ // First chunk: take parts up to and including the first call
291
+ pushChunk();
292
+ while (
293
+ pi < parts.length &&
294
+ (pi === parts.length - 1 ||
295
+ chunks.at(-1)!.length === 0 ||
296
+ !config.isCallPart!(parts[pi + 1]))
297
+ ) {
298
+ pushPart();
299
+ if (config.isCallPart!(chunks.at(-1)!.at(-1)!)) {
300
+ break;
301
+ }
302
+ }
303
+
304
+ // Remaining chunks: collect non-call parts then call parts
305
+ while (pi < parts.length) {
306
+ pushChunk();
307
+ while (pi < parts.length && !config.isCallPart!(parts[pi])) {
308
+ pushPart();
309
+ }
310
+ while (pi < parts.length && config.isCallPart!(parts[pi])) {
311
+ pushPart();
312
+ }
313
+ }
314
+
315
+ for (let ci = 0; ci < chunks.length; ci++) {
316
+ const chunk = chunks[ci];
317
+ const chunkExpr = [];
318
+ for (let cpi = 0; cpi < chunk.length; cpi++) {
319
+ if (ci === 0 && cpi === 0) {
320
+ chunkExpr.push(config.getBase(chunk[0]));
321
+ continue;
322
+ }
323
+ const part = chunk[cpi];
324
+ const prevPart = cpi === 0 ? chunks[ci - 1].at(-1)! : chunk[cpi - 1];
325
+ chunkExpr.push(config.formatPart(part, prevPart, true));
326
+ }
327
+
328
+ expression.push(
329
+ ci === 0 ? chunkExpr : (
330
+ <>
331
+ <sbr />
332
+ {chunkExpr}
333
+ </>
334
+ ),
335
+ );
336
+ }
337
+
338
+ return (
339
+ <group>
340
+ <indent>{expression}</indent>
341
+ </group>
342
+ );
343
+ });
344
+ }
@@ -2,6 +2,7 @@ import { computed } from "@vue/reactivity";
2
2
  import { join } from "pathe";
3
3
  import { useContext } from "../context.js";
4
4
  import { SourceDirectoryContext } from "../context/source-directory.js";
5
+ import { emitDiagnostic } from "../diagnostics.js";
5
6
  import { createFileResource } from "../resource.js";
6
7
  import { Children, isComponentCreator } from "../runtime/component.js";
7
8
  import { childrenArray } from "../utils.jsx";
@@ -98,9 +99,11 @@ export function AppendFile(props: AppendFileProps): Children {
98
99
  } else if ("content" in regionProps) {
99
100
  content = regionProps.content;
100
101
  } else {
101
- throw new Error(
102
- `AppendRegion "${regionProps.id}" must have either children or content`,
103
- );
102
+ emitDiagnostic({
103
+ message: `AppendRegion "${regionProps.id}" must have either children or content`,
104
+ severity: "error",
105
+ });
106
+ // Still register the region to avoid duplicate "region not found" diagnostic
104
107
  }
105
108
 
106
109
  appendRegions[regionProps.id] = content;
@@ -115,9 +118,10 @@ export function AppendFile(props: AppendFileProps): Children {
115
118
  // Validate that all requested regions have corresponding AppendRegion children
116
119
  for (const regionId of regions) {
117
120
  if (!(regionId in appendRegions)) {
118
- throw new Error(
119
- `Region "${regionId}" specified but no corresponding AppendRegion child found`,
120
- );
121
+ emitDiagnostic({
122
+ message: `Region "${regionId}" specified but no corresponding AppendRegion child found`,
123
+ severity: "error",
124
+ });
121
125
  }
122
126
  }
123
127
 
@@ -181,9 +185,10 @@ export function AppendFile(props: AppendFileProps): Children {
181
185
  for (const regionId of regions) {
182
186
  const info = sigilInfo[regionId];
183
187
  if (info && info.start !== null && info.end === null) {
184
- throw new Error(
185
- `Region "${regionId}" has start sigil but no corresponding end sigil`,
186
- );
188
+ emitDiagnostic({
189
+ message: `Region "${regionId}" has start sigil but no corresponding end sigil`,
190
+ severity: "error",
191
+ });
187
192
  }
188
193
  }
189
194
 
@@ -1,5 +1,5 @@
1
- import { computed } from "@vue/reactivity";
2
1
  import { createContentSlot } from "../content-slot.jsx";
2
+ import { computed } from "../reactivity.js";
3
3
  import type { Children } from "../runtime/component.js";
4
4
  import { Indent } from "./Indent.jsx";
5
5
 
@@ -1,3 +1,4 @@
1
+ import { createSymbol } from "../binder.js";
1
2
  import { useContext } from "../context.js";
2
3
  import { BinderContext } from "../context/binder.js";
3
4
  import { DeclarationContext } from "../context/declaration.js";
@@ -83,7 +84,7 @@ export function Declaration(props: DeclarationProps) {
83
84
  );
84
85
  }
85
86
 
86
- declaration = new BasicSymbol(props.name, scope.symbols, {
87
+ declaration = createSymbol(BasicSymbol, props.name, scope.symbols, {
87
88
  binder,
88
89
  refkeys: [props.refkey ?? []].flat(),
89
90
  metadata: props.metadata,
@@ -80,15 +80,19 @@ export function For<
80
80
  const cb = props.children;
81
81
  const options = baseListPropsToMapJoinArgs(props);
82
82
  options.skipFalsy = props.skipFalsy;
83
- return memo(() => {
84
- const maybeRef = props.each;
83
+ return memo(
84
+ () => {
85
+ const maybeRef = props.each;
85
86
 
86
- return (mapJoin as any)(
87
- typeof maybeRef === "function" ? maybeRef : (
88
- () => (isRef(maybeRef) ? maybeRef.value : maybeRef)
89
- ),
90
- cb,
91
- options,
92
- );
93
- });
87
+ return (mapJoin as any)(
88
+ typeof maybeRef === "function" ? maybeRef : (
89
+ () => (isRef(maybeRef) ? maybeRef.value : maybeRef)
90
+ ),
91
+ cb,
92
+ options,
93
+ );
94
+ },
95
+ undefined,
96
+ "For",
97
+ );
94
98
  }
@@ -57,10 +57,13 @@ export interface ListProps extends BaseListProps {
57
57
  */
58
58
  export function List(props: ListProps) {
59
59
  const [rest, forProps] = splitProps(props, ["children"]);
60
- const resolvedChildren = memo(() =>
61
- childrenArray(() => rest.children, {
62
- preserveFragments: true,
63
- }),
60
+ const resolvedChildren = memo(
61
+ () =>
62
+ childrenArray(() => rest.children, {
63
+ preserveFragments: true,
64
+ }),
65
+ undefined,
66
+ "List:children",
64
67
  );
65
68
  return (
66
69
  <For each={resolvedChildren} {...forProps} skipFalsy>
@@ -22,7 +22,7 @@ export function Prose(props: Prose) {
22
22
  .map((word, index, array) => (
23
23
  <>
24
24
  {word}
25
- {index < array.length - 1 && <br />}
25
+ {/*@once*/ index < array.length - 1 && <br />}
26
26
  </>
27
27
  ));
28
28
  }
@@ -1,4 +1,6 @@
1
+ import { createScope } from "../binder.js";
1
2
  import { ScopeContext, useScope } from "../context/scope.js";
3
+ import { debug } from "../debug/index.js";
2
4
  import type { Children } from "../runtime/component.js";
3
5
  import { BasicScope } from "../symbols/basic-scope.js";
4
6
  import { OutputScope } from "../symbols/output-scope.js";
@@ -51,6 +53,7 @@ export function Scope(props: ScopeProps) {
51
53
  let scope: OutputScope;
52
54
  if ("value" in props) {
53
55
  scope = props.value;
56
+ debug.symbols.relocateScope(scope);
54
57
  } else {
55
58
  const parentScope = useScope();
56
59
  if (parentScope && !(parentScope instanceof BasicScope)) {
@@ -58,7 +61,9 @@ export function Scope(props: ScopeProps) {
58
61
  "Scope component can only make scopes within a BasicScope",
59
62
  );
60
63
  }
61
- scope = new BasicScope(props.name ?? "", parentScope, {
64
+ const binder = parentScope?.binder;
65
+ scope = createScope(BasicScope, props.name ?? "", parentScope, {
66
+ binder,
62
67
  metadata: props.metadata,
63
68
  ownerSymbol: props.ownerSymbol,
64
69
  });
@@ -1,8 +1,7 @@
1
- import { shallowReactive } from "@vue/reactivity";
2
1
  import { join } from "pathe";
3
2
  import { useContext } from "../context.js";
4
3
  import { SourceDirectoryContext } from "../context/source-directory.js";
5
- import { getContext } from "../reactivity.js";
4
+ import { getContext, shallowReactive } from "../reactivity.js";
6
5
  import type { Children } from "../runtime/component.js";
7
6
 
8
7
  export interface SourceDirectoryProps {
@@ -27,15 +27,19 @@ export function Switch(props: SwitchProps) {
27
27
  const children = childrenArray(() => props.children);
28
28
  const matches = findKeyedChildren(children, matchTag);
29
29
 
30
- return memo(() => {
31
- for (const match of matches) {
32
- if (match.props.when || match.props.else) {
33
- return match.props.children;
30
+ return memo(
31
+ () => {
32
+ for (const match of matches) {
33
+ if (match.props.when || match.props.else) {
34
+ return match.props.children;
35
+ }
34
36
  }
35
- }
36
37
 
37
- return undefined;
38
- });
38
+ return undefined;
39
+ },
40
+ undefined,
41
+ "Switch",
42
+ );
39
43
  }
40
44
 
41
45
  export interface MatchProps {
@@ -1,4 +1,5 @@
1
1
  import { computed } from "@vue/reactivity";
2
+ import { emitDiagnostic } from "../diagnostics.js";
2
3
  import { createFileResource } from "../resource.js";
3
4
  import { Children, isComponentCreator } from "../runtime/component.js";
4
5
  import { childrenArray } from "../utils.jsx";
@@ -85,9 +86,12 @@ export function TemplateFile(props: TemplateFileProps): Children {
85
86
  } else if ("value" in variableProps) {
86
87
  value = variableProps.value;
87
88
  } else {
88
- throw new Error(
89
- `TemplateVariable "${variableProps.name}" must have either children or value`,
90
- );
89
+ emitDiagnostic({
90
+ message: `TemplateVariable "${variableProps.name}" must have either children or value`,
91
+ severity: "error",
92
+ });
93
+ // Still register the variable to avoid duplicate diagnostics
94
+ value = "";
91
95
  }
92
96
 
93
97
  templateVariables[variableProps.name] = value;
@@ -100,9 +104,11 @@ export function TemplateFile(props: TemplateFileProps): Children {
100
104
  }
101
105
 
102
106
  if (templateResource.error) {
103
- throw new Error(
104
- `Failed to read template file "${props.src}": ${templateResource.error}`,
105
- );
107
+ emitDiagnostic({
108
+ message: `Failed to read template file "${props.src}": ${templateResource.error}`,
109
+ severity: "error",
110
+ });
111
+ return "";
106
112
  }
107
113
 
108
114
  const templateContent = templateResource.data!;
@@ -131,9 +137,12 @@ export function TemplateFile(props: TemplateFileProps): Children {
131
137
  if (variableName in templateVariables) {
132
138
  result.push(templateVariables[variableName]);
133
139
  } else {
134
- throw new Error(
135
- `Template variable "${variableName}" not found in TemplateVariable children`,
136
- );
140
+ emitDiagnostic({
141
+ message: `Template variable "${variableName}" not found in TemplateVariable children`,
142
+ severity: "error",
143
+ });
144
+ // Keep the placeholder in the output to make the error visible
145
+ result.push(`{{ ${variableName} }}`);
137
146
  }
138
147
 
139
148
  lastIndex = matchStart + fullMatch.length;
@@ -1,3 +1,4 @@
1
+ export * from "./AccessExpression.jsx";
1
2
  export * from "./AppendFile.jsx";
2
3
  export * from "./Block.js";
3
4
  export * from "./CopyFile.jsx";
@@ -1,6 +1,6 @@
1
- import { effect, Ref, shallowRef } from "@vue/reactivity";
1
+ import { computed, Ref, shallowRef } from "@vue/reactivity";
2
2
  import { Show } from "./components/Show.jsx";
3
- import { getContext } from "./reactivity.js";
3
+ import { ensureIsEmpty, getContext } from "./reactivity.js";
4
4
  import { Children, Component } from "./runtime/component.js";
5
5
 
6
6
  export interface ContentSlot {
@@ -54,14 +54,14 @@ export interface ContentSlot {
54
54
  * ```
55
55
  */
56
56
  export function createContentSlot(): ContentSlot {
57
- const isEmpty = shallowRef<boolean>(false);
57
+ // Holds a reference to the rendering context's isEmpty ref once ContentSlot
58
+ // renders. Before that, reads fall through to a default of "not empty".
59
+ const isEmptySource = shallowRef<Ref<boolean>>();
60
+ const isEmpty = computed(() => isEmptySource.value?.value ?? false);
58
61
 
59
62
  function ContentSlot(props: { children: Children }) {
60
63
  const context = getContext()!;
61
- effect(() => {
62
- isEmpty.value = context.isEmpty!.value;
63
- });
64
-
64
+ isEmptySource.value = ensureIsEmpty(context);
65
65
  return props.children;
66
66
  }
67
67
  ContentSlot.ref = isEmpty;
package/src/context.ts CHANGED
@@ -20,8 +20,8 @@ export function useContext<T>(context: ComponentContext<T>): T | undefined {
20
20
  // context must come from a parent
21
21
  let current = getContext();
22
22
  while (current) {
23
- if (Object.hasOwn(current.context!, context.id)) {
24
- return current.context![context.id] as T | undefined;
23
+ if (current.context && Object.hasOwn(current.context, context.id)) {
24
+ return current.context[context.id] as T | undefined;
25
25
  }
26
26
  current = current.owner;
27
27
  }
@@ -38,12 +38,22 @@ export function createContext<T = unknown>(
38
38
  const id = Symbol(name ?? "context");
39
39
  function Provider(props: ContextProviderProps<T>) {
40
40
  const context = getContext();
41
+ const contextName = name ?? "anonymous";
41
42
 
42
43
  const rendered = shallowRef();
43
- effect(() => {
44
- context!.context![id] = props.value;
45
- rendered.value = () => props.children;
46
- }, undefined);
44
+ effect(
45
+ () => {
46
+ (context!.context ??= {})[id] = props.value;
47
+ rendered.value = () => props.children;
48
+ },
49
+ undefined,
50
+ {
51
+ debug: {
52
+ name: `context:provider:${contextName}`,
53
+ type: "context",
54
+ },
55
+ },
56
+ );
47
57
 
48
58
  return rendered.value;
49
59
  }
@@ -54,6 +64,7 @@ export function createContext<T = unknown>(
54
64
  Provider,
55
65
  ProviderStc: stc(Provider),
56
66
  };
67
+ (Provider as any).contextName = name;
57
68
  contextsByKey.set(id, ctx);
58
69
  return ctx;
59
70
  }