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

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/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +68 -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 +55 -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/Prose.js +2 -2
  24. package/dist/src/components/Prose.js.map +1 -1
  25. package/dist/src/components/Scope.d.ts.map +1 -1
  26. package/dist/src/components/Scope.js +6 -1
  27. package/dist/src/components/Scope.js.map +1 -1
  28. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  29. package/dist/src/components/SourceDirectory.js +1 -2
  30. package/dist/src/components/SourceDirectory.js.map +1 -1
  31. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  32. package/dist/src/components/TemplateFile.js +18 -3
  33. package/dist/src/components/TemplateFile.js.map +1 -1
  34. package/dist/src/components/index.d.ts +1 -0
  35. package/dist/src/components/index.d.ts.map +1 -1
  36. package/dist/src/components/index.js +1 -0
  37. package/dist/src/components/index.js.map +1 -1
  38. package/dist/src/content-slot.d.ts.map +1 -1
  39. package/dist/src/content-slot.js +7 -6
  40. package/dist/src/content-slot.js.map +1 -1
  41. package/dist/src/context.d.ts.map +1 -1
  42. package/dist/src/context.js +10 -3
  43. package/dist/src/context.js.map +1 -1
  44. package/dist/src/debug/cli.d.ts +6 -0
  45. package/dist/src/debug/cli.d.ts.map +1 -0
  46. package/dist/src/{debug.js → debug/cli.js} +79 -82
  47. package/dist/src/debug/cli.js.map +1 -0
  48. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  49. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  50. package/dist/src/debug/diagnostics.test.js +45 -0
  51. package/dist/src/debug/diagnostics.test.js.map +1 -0
  52. package/dist/src/debug/effects.d.ts +73 -0
  53. package/dist/src/debug/effects.d.ts.map +1 -0
  54. package/dist/src/debug/effects.js +228 -0
  55. package/dist/src/debug/effects.js.map +1 -0
  56. package/dist/src/debug/effects.test.d.ts +2 -0
  57. package/dist/src/debug/effects.test.d.ts.map +1 -0
  58. package/dist/src/debug/effects.test.js +84 -0
  59. package/dist/src/debug/effects.test.js.map +1 -0
  60. package/dist/src/debug/files.d.ts +14 -0
  61. package/dist/src/debug/files.d.ts.map +1 -0
  62. package/dist/src/debug/files.js +40 -0
  63. package/dist/src/debug/files.js.map +1 -0
  64. package/dist/src/debug/files.test.d.ts +2 -0
  65. package/dist/src/debug/files.test.d.ts.map +1 -0
  66. package/dist/src/debug/files.test.js +89 -0
  67. package/dist/src/debug/files.test.js.map +1 -0
  68. package/dist/src/debug/index.d.ts +61 -0
  69. package/dist/src/debug/index.d.ts.map +1 -0
  70. package/dist/src/debug/index.js +69 -0
  71. package/dist/src/debug/index.js.map +1 -0
  72. package/dist/src/debug/render.d.ts +57 -0
  73. package/dist/src/debug/render.d.ts.map +1 -0
  74. package/dist/src/debug/render.js +519 -0
  75. package/dist/src/debug/render.js.map +1 -0
  76. package/dist/src/debug/render.test.d.ts +2 -0
  77. package/dist/src/debug/render.test.d.ts.map +1 -0
  78. package/dist/src/debug/render.test.js +328 -0
  79. package/dist/src/debug/render.test.js.map +1 -0
  80. package/dist/src/debug/serialize.d.ts +9 -0
  81. package/dist/src/debug/serialize.d.ts.map +1 -0
  82. package/dist/src/debug/serialize.js +70 -0
  83. package/dist/src/debug/serialize.js.map +1 -0
  84. package/dist/src/debug/symbols.d.ts +15 -0
  85. package/dist/src/debug/symbols.d.ts.map +1 -0
  86. package/dist/src/debug/symbols.js +173 -0
  87. package/dist/src/debug/symbols.js.map +1 -0
  88. package/dist/src/debug/symbols.test.d.ts +2 -0
  89. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  90. package/dist/src/debug/symbols.test.js +104 -0
  91. package/dist/src/debug/symbols.test.js.map +1 -0
  92. package/dist/src/debug/trace.d.ts +342 -0
  93. package/dist/src/debug/trace.d.ts.map +1 -0
  94. package/dist/src/debug/trace.js +443 -0
  95. package/dist/src/debug/trace.js.map +1 -0
  96. package/dist/src/devtools/devtools-protocol.d.ts +232 -0
  97. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  98. package/dist/src/devtools/devtools-protocol.js +2 -0
  99. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  100. package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
  101. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  102. package/dist/src/devtools/devtools-server.browser.js +36 -0
  103. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  104. package/dist/src/devtools/devtools-server.d.ts +72 -0
  105. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  106. package/dist/src/devtools/devtools-server.js +256 -0
  107. package/dist/src/devtools/devtools-server.js.map +1 -0
  108. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  109. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  110. package/dist/src/devtools/devtools-transport.js +114 -0
  111. package/dist/src/devtools/devtools-transport.js.map +1 -0
  112. package/dist/src/devtools-entry.browser.d.ts +4 -0
  113. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  114. package/dist/src/devtools-entry.browser.js +2 -0
  115. package/dist/src/devtools-entry.browser.js.map +1 -0
  116. package/dist/src/devtools-entry.d.ts +4 -0
  117. package/dist/src/devtools-entry.d.ts.map +1 -0
  118. package/dist/src/devtools-entry.js +2 -0
  119. package/dist/src/devtools-entry.js.map +1 -0
  120. package/dist/src/diagnostics.d.ts +34 -0
  121. package/dist/src/diagnostics.d.ts.map +1 -0
  122. package/dist/src/diagnostics.js +89 -0
  123. package/dist/src/diagnostics.js.map +1 -0
  124. package/dist/src/index.d.ts +3 -2
  125. package/dist/src/index.d.ts.map +1 -1
  126. package/dist/src/index.js +3 -2
  127. package/dist/src/index.js.map +1 -1
  128. package/dist/src/print-hook.d.ts +14 -0
  129. package/dist/src/print-hook.d.ts.map +1 -0
  130. package/dist/src/print-hook.js +10 -0
  131. package/dist/src/print-hook.js.map +1 -0
  132. package/dist/src/reactive-union-set.d.ts.map +1 -1
  133. package/dist/src/reactive-union-set.js +28 -3
  134. package/dist/src/reactive-union-set.js.map +1 -1
  135. package/dist/src/reactivity.d.ts +50 -8
  136. package/dist/src/reactivity.d.ts.map +1 -1
  137. package/dist/src/reactivity.js +225 -39
  138. package/dist/src/reactivity.js.map +1 -1
  139. package/dist/src/render-stack.d.ts +18 -1
  140. package/dist/src/render-stack.d.ts.map +1 -1
  141. package/dist/src/render-stack.js +61 -1
  142. package/dist/src/render-stack.js.map +1 -1
  143. package/dist/src/render.d.ts +8 -15
  144. package/dist/src/render.d.ts.map +1 -1
  145. package/dist/src/render.js +370 -109
  146. package/dist/src/render.js.map +1 -1
  147. package/dist/src/resource.d.ts.map +1 -1
  148. package/dist/src/resource.js +5 -0
  149. package/dist/src/resource.js.map +1 -1
  150. package/dist/src/scheduler.d.ts +8 -0
  151. package/dist/src/scheduler.d.ts.map +1 -1
  152. package/dist/src/scheduler.js +69 -3
  153. package/dist/src/scheduler.js.map +1 -1
  154. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  155. package/dist/src/symbols/basic-symbol.js +6 -1
  156. package/dist/src/symbols/basic-symbol.js.map +1 -1
  157. package/dist/src/symbols/decl.d.ts.map +1 -1
  158. package/dist/src/symbols/decl.js +5 -1
  159. package/dist/src/symbols/decl.js.map +1 -1
  160. package/dist/src/symbols/output-scope.d.ts +2 -1
  161. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  162. package/dist/src/symbols/output-scope.js +13 -8
  163. package/dist/src/symbols/output-scope.js.map +1 -1
  164. package/dist/src/symbols/output-symbol.d.ts +1 -0
  165. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  166. package/dist/src/symbols/output-symbol.js +25 -8
  167. package/dist/src/symbols/output-symbol.js.map +1 -1
  168. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  169. package/dist/src/symbols/symbol-flow.js +24 -8
  170. package/dist/src/symbols/symbol-flow.js.map +1 -1
  171. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  172. package/dist/src/symbols/symbol-slot.js +15 -0
  173. package/dist/src/symbols/symbol-slot.js.map +1 -1
  174. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  175. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  176. package/dist/src/symbols/symbol-slot.test.js +35 -0
  177. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  178. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  179. package/dist/src/symbols/symbol-table.js +6 -5
  180. package/dist/src/symbols/symbol-table.js.map +1 -1
  181. package/dist/src/trace.d.ts +2 -0
  182. package/dist/src/trace.d.ts.map +1 -0
  183. package/dist/src/trace.js +2 -0
  184. package/dist/src/trace.js.map +1 -0
  185. package/dist/src/tracer.d.ts +2 -228
  186. package/dist/src/tracer.d.ts.map +1 -1
  187. package/dist/src/tracer.js +5 -298
  188. package/dist/src/tracer.js.map +1 -1
  189. package/dist/src/utils.d.ts.map +1 -1
  190. package/dist/src/utils.js +7 -5
  191. package/dist/src/utils.js.map +1 -1
  192. package/dist/test/components/append-file.test.d.ts.map +1 -1
  193. package/dist/test/components/append-file.test.js +18 -10
  194. package/dist/test/components/append-file.test.js.map +1 -1
  195. package/dist/test/components/template-file.test.d.ts.map +1 -1
  196. package/dist/test/components/template-file.test.js +6 -4
  197. package/dist/test/components/template-file.test.js.map +1 -1
  198. package/dist/test/lazy-isempty.test.d.ts +2 -0
  199. package/dist/test/lazy-isempty.test.d.ts.map +1 -0
  200. package/dist/test/lazy-isempty.test.js +89 -0
  201. package/dist/test/lazy-isempty.test.js.map +1 -0
  202. package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
  203. package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
  204. package/dist/test/reactive-union-set-disposers.test.js +98 -0
  205. package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
  206. package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
  207. package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
  208. package/dist/test/reactivity/shallow-reactive.test.js +52 -0
  209. package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
  210. package/dist/test/rendering/basic.test.js +3 -0
  211. package/dist/test/rendering/basic.test.js.map +1 -1
  212. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -1
  213. package/dist/test/rendering/print-render-stack.test.js +91 -98
  214. package/dist/test/rendering/print-render-stack.test.js.map +1 -1
  215. package/dist/test/scheduler-extended.test.d.ts +2 -0
  216. package/dist/test/scheduler-extended.test.d.ts.map +1 -0
  217. package/dist/test/scheduler-extended.test.js +96 -0
  218. package/dist/test/scheduler-extended.test.js.map +1 -0
  219. package/dist/test/scheduler.test.d.ts +2 -0
  220. package/dist/test/scheduler.test.d.ts.map +1 -0
  221. package/dist/test/scheduler.test.js +46 -0
  222. package/dist/test/scheduler.test.js.map +1 -0
  223. package/dist/testing/create-test-wrapper.d.ts +1 -1
  224. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  225. package/dist/testing/create-test-wrapper.js +1 -1
  226. package/dist/testing/create-test-wrapper.js.map +1 -1
  227. package/dist/testing/devtools-utils.d.ts +26 -0
  228. package/dist/testing/devtools-utils.d.ts.map +1 -0
  229. package/dist/testing/devtools-utils.js +140 -0
  230. package/dist/testing/devtools-utils.js.map +1 -0
  231. package/dist/testing/extend-expect.d.ts.map +1 -1
  232. package/dist/testing/extend-expect.js +63 -1
  233. package/dist/testing/extend-expect.js.map +1 -1
  234. package/dist/testing/render.d.ts +2 -2
  235. package/dist/testing/render.d.ts.map +1 -1
  236. package/dist/testing/render.js +2 -2
  237. package/dist/testing/render.js.map +1 -1
  238. package/dist/tsconfig.tsbuildinfo +1 -1
  239. package/package.json +21 -7
  240. package/scripts/copy-devtools-ui.mjs +26 -0
  241. package/src/binder.ts +71 -16
  242. package/src/components/AccessExpression.test.tsx +132 -0
  243. package/src/components/AccessExpression.tsx +344 -0
  244. package/src/components/AppendFile.tsx +14 -9
  245. package/src/components/Block.tsx +1 -1
  246. package/src/components/Declaration.tsx +2 -1
  247. package/src/components/Prose.tsx +1 -1
  248. package/src/components/Scope.tsx +6 -1
  249. package/src/components/SourceDirectory.tsx +1 -2
  250. package/src/components/TemplateFile.tsx +18 -9
  251. package/src/components/index.tsx +1 -0
  252. package/src/content-slot.tsx +7 -7
  253. package/src/context.ts +17 -6
  254. package/src/{debug.ts → debug/cli.ts} +114 -125
  255. package/src/debug/diagnostics.test.tsx +55 -0
  256. package/src/debug/effects.test.tsx +89 -0
  257. package/src/debug/effects.ts +317 -0
  258. package/src/debug/files.test.tsx +96 -0
  259. package/src/debug/files.ts +40 -0
  260. package/src/debug/index.ts +128 -0
  261. package/src/debug/render.test.tsx +379 -0
  262. package/src/debug/render.ts +639 -0
  263. package/src/debug/serialize.ts +85 -0
  264. package/src/debug/symbols.test.tsx +106 -0
  265. package/src/debug/symbols.ts +239 -0
  266. package/src/debug/trace.ts +312 -0
  267. package/src/devtools/devtools-protocol.ts +312 -0
  268. package/src/devtools/devtools-server.browser.ts +71 -0
  269. package/src/devtools/devtools-server.ts +290 -0
  270. package/src/devtools/devtools-transport.ts +154 -0
  271. package/src/devtools-entry.browser.ts +52 -0
  272. package/src/devtools-entry.ts +54 -0
  273. package/src/diagnostics.ts +141 -0
  274. package/src/index.ts +2 -7
  275. package/src/print-hook.ts +22 -0
  276. package/src/reactive-union-set.ts +85 -44
  277. package/src/reactivity.ts +301 -59
  278. package/src/render-stack.ts +73 -1
  279. package/src/render.ts +470 -161
  280. package/src/resource.ts +28 -19
  281. package/src/scheduler.ts +80 -4
  282. package/src/symbols/basic-symbol.ts +6 -1
  283. package/src/symbols/decl.ts +5 -1
  284. package/src/symbols/output-scope.ts +21 -13
  285. package/src/symbols/output-symbol.ts +34 -14
  286. package/src/symbols/symbol-flow.ts +76 -39
  287. package/src/symbols/symbol-slot.test.tsx +41 -0
  288. package/src/symbols/symbol-slot.tsx +47 -20
  289. package/src/symbols/symbol-table.ts +6 -10
  290. package/src/trace.ts +1 -0
  291. package/src/tracer.ts +13 -242
  292. package/src/utils.tsx +24 -17
  293. package/temp/api.json +5658 -3095
  294. package/test/components/append-file.test.tsx +36 -29
  295. package/test/components/template-file.test.tsx +11 -11
  296. package/test/lazy-isempty.test.tsx +106 -0
  297. package/test/reactive-union-set-disposers.test.tsx +112 -0
  298. package/test/reactivity/shallow-reactive.test.tsx +56 -0
  299. package/test/rendering/basic.test.tsx +4 -0
  300. package/test/rendering/print-render-stack.test.tsx +52 -43
  301. package/test/scheduler-extended.test.tsx +122 -0
  302. package/test/scheduler.test.tsx +50 -0
  303. package/testing/create-test-wrapper.tsx +1 -1
  304. package/testing/devtools-utils.ts +203 -0
  305. package/testing/extend-expect.ts +89 -0
  306. package/testing/render.ts +2 -2
  307. package/testing/vitest.d.ts +9 -0
  308. package/dist/src/debug.d.ts +0 -14
  309. package/dist/src/debug.d.ts.map +0 -1
  310. package/dist/src/debug.js.map +0 -1
@@ -1,15 +1,93 @@
1
- import { isRef, ref } from "@vue/reactivity";
1
+ import { isRef } from "@vue/reactivity";
2
2
  import { doc } from "prettier";
3
3
  import prettier from "prettier/doc.js";
4
4
  import { useContext } from "./context.js";
5
5
  import { SourceFileContext } from "./context/source-file.js";
6
- import { effect, getContext, getElementCache, isCustomContext, root, untrack } from "./reactivity.js";
6
+ import { debug, getRenderNodeId, isDevtoolsEnabled } from "./debug/index.js";
7
+ import { broadcastDevtoolsMessage } from "./devtools/devtools-server.js";
8
+ import { attachDiagnosticsCollector, DiagnosticsCollector, emitDiagnostic, reportDiagnostics } from "./diagnostics.js";
9
+ import { isPrintHook, printHookTag } from "./print-hook.js";
10
+ import { effect, getContext, getElementCache, isCustomContext, onCleanup, ref, root, untrack } from "./reactivity.js";
7
11
  import { isRefkeyable, toRefkey } from "./refkey.js";
8
- import { popStack, printRenderStack, pushStack } from "./render-stack.js";
12
+ import { getRenderStackSnapshot, popStack, printRenderStack, pushStack } from "./render-stack.js";
9
13
  import { isComponentCreator, isRenderableObject, RENDERABLE } from "./runtime/component.js";
10
14
  import { isIntrinsicElement } from "./runtime/intrinsic.js";
11
- import { flushJobs, flushJobsAsync } from "./scheduler.js";
12
- import { trace, TracePhase } from "./tracer.js";
15
+ import { flushJobs, flushJobsAsync, waitForSignal } from "./scheduler.js";
16
+ const notifiedErrors = new WeakSet();
17
+ let lastRenderError = null;
18
+ function normalizeRenderError(error) {
19
+ if (error instanceof Error) {
20
+ return {
21
+ name: error.name || error.constructor?.name || "Error",
22
+ message: error.message || "",
23
+ stack: error.stack
24
+ };
25
+ }
26
+ if (error && typeof error === "object") {
27
+ const anyError = error;
28
+ return {
29
+ name: anyError.name || "Error",
30
+ message: anyError.message || String(error),
31
+ stack: anyError.stack
32
+ };
33
+ }
34
+ return {
35
+ name: "Error",
36
+ message: String(error)
37
+ };
38
+ }
39
+ function notifyRenderError(error) {
40
+ if (error && typeof error === "object") {
41
+ if (notifiedErrors.has(error)) return;
42
+ notifiedErrors.add(error);
43
+ }
44
+ if (lastRenderError) return;
45
+ const {
46
+ name,
47
+ message,
48
+ stack
49
+ } = normalizeRenderError(error);
50
+ const componentStack = getRenderStackSnapshot().map(entry => {
51
+ const renderNode = entry.context?.meta?.renderNode;
52
+ const renderNodeId = renderNode ? getRenderNodeId(renderNode) : undefined;
53
+ return {
54
+ name: entry.displayName,
55
+ props: entry.props,
56
+ renderNodeId,
57
+ source: entry.source
58
+ };
59
+ });
60
+
61
+ // Output to console
62
+ printRenderStack(error);
63
+
64
+ // Send to devtools if enabled
65
+ debug.render.error({
66
+ name,
67
+ message,
68
+ stack
69
+ }, componentStack);
70
+
71
+ // Store for diagnostics
72
+ lastRenderError = {
73
+ error: {
74
+ name,
75
+ message,
76
+ stack
77
+ },
78
+ componentStack
79
+ };
80
+ const lastEntry = componentStack.at(-1);
81
+ emitDiagnostic({
82
+ severity: "error",
83
+ message: `${name}: ${message}`,
84
+ source: lastEntry?.source
85
+ });
86
+ }
87
+ function reportLastRenderError() {
88
+ // Error already reported in notifyRenderError via debug.renderError
89
+ lastRenderError = null;
90
+ }
13
91
  const {
14
92
  builders: {
15
93
  align,
@@ -112,10 +190,27 @@ const {
112
190
  */
113
191
 
114
192
  const nodesToContext = new WeakMap();
193
+ const diagnosticsByTree = new WeakMap();
115
194
  export function getContextForRenderNode(node) {
116
195
  return nodesToContext.get(node);
117
196
  }
118
- export const printHookTag = Symbol();
197
+ export function getDiagnosticsForTree(tree) {
198
+ return diagnosticsByTree.get(tree)?.getDiagnostics() ?? [];
199
+ }
200
+ function reportDiagnosticsForTree(tree) {
201
+ const diagnostics = diagnosticsByTree.get(tree);
202
+ if (!diagnostics) return;
203
+ const entries = diagnostics.getDiagnostics();
204
+ if (entries.length === 0) return;
205
+ reportDiagnostics(diagnostics);
206
+ void broadcastDevtoolsMessage({
207
+ type: "diagnostics:report",
208
+ diagnostics: entries
209
+ });
210
+ }
211
+
212
+ // Re-export from print-hook.ts to maintain backwards compatibility
213
+ export { isPrintHook, printHookTag } from "./print-hook.js";
119
214
  export function createRenderTreeHook(subtree, hooks) {
120
215
  return {
121
216
  [printHookTag]: true,
@@ -123,9 +218,7 @@ export function createRenderTreeHook(subtree, hooks) {
123
218
  ...hooks
124
219
  };
125
220
  }
126
- export function isPrintHook(type) {
127
- return typeof type === "object" && type !== null && printHookTag in type;
128
- }
221
+
129
222
  /**
130
223
  * Render a component tree to source directories and files. Will ensure that
131
224
  * all non-async scheduled jobs are completed before returning. If async jobs
@@ -135,7 +228,14 @@ export function isPrintHook(type) {
135
228
  export function render(children, options) {
136
229
  const tree = renderTree(children);
137
230
  flushJobs();
138
- return sourceFilesForTree(tree, options);
231
+ const output = sourceFilesForTree(tree, options);
232
+ reportDiagnosticsForTree(tree);
233
+ reportLastRenderError();
234
+ debug.render.complete();
235
+ if (isDevtoolsEnabled()) {
236
+ void waitForSignal();
237
+ }
238
+ return output;
139
239
  }
140
240
 
141
241
  /**
@@ -143,20 +243,15 @@ export function render(children, options) {
143
243
  * scheduled jobs are completed before returning.
144
244
  */
145
245
  export async function renderAsync(children, options) {
246
+ await debug.prepare();
146
247
  const tree = renderTree(children);
147
- return sourceFilesForTreeAsync(tree, options);
148
- }
149
-
150
- /**
151
- * Convert a rendered text tree to source directories and files. Will ensure that
152
- * all scheduled jobs are completed, including async ones.
153
- */
154
- export async function sourceFilesForTreeAsync(tree, options) {
155
- // if we await here, we ensure all reactive updates are flushed.
156
- // sourceFilesForTree will flush again, but won't find anything, because tree
157
- // printing won't schedule anything.
248
+ // Ensure all reactive updates are flushed before printing.
158
249
  await flushJobsAsync();
159
- return sourceFilesForTree(tree, options);
250
+ const output = sourceFilesForTree(tree, options);
251
+ reportDiagnosticsForTree(tree);
252
+ reportLastRenderError();
253
+ debug.render.complete();
254
+ return output;
160
255
  }
161
256
 
162
257
  /**
@@ -167,7 +262,15 @@ export function sourceFilesForTree(tree, options) {
167
262
  let rootDirectory = undefined;
168
263
  collectSourceFiles(undefined, tree);
169
264
  if (!rootDirectory) {
170
- throw new Error("No root directory found. Make sure you are using the Output component.");
265
+ emitDiagnostic({
266
+ severity: "error",
267
+ message: "No root directory found. Make sure you are using the output component."
268
+ });
269
+ return {
270
+ kind: "directory",
271
+ path: "",
272
+ contents: []
273
+ };
171
274
  }
172
275
  return rootDirectory;
173
276
  function collectSourceFiles(currentDirectory, root) {
@@ -230,27 +333,37 @@ export function sourceFilesForTree(tree, options) {
230
333
  }
231
334
  export function renderTree(children) {
232
335
  const rootElem = [];
336
+ const diagnostics = new DiagnosticsCollector();
337
+ lastRenderError = null;
338
+ debug.effect.reset();
339
+ debug.symbols.reset();
340
+ debug.files.reset();
341
+ debug.render.initialize(rootElem);
233
342
  try {
234
343
  root(() => {
344
+ attachDiagnosticsCollector(diagnostics);
235
345
  renderWorker(rootElem, children);
236
346
  });
237
347
  } catch (e) {
238
- printRenderStack();
348
+ notifyRenderError(e);
349
+ reportLastRenderError();
239
350
  throw e;
240
351
  }
352
+ diagnosticsByTree.set(rootElem, diagnostics);
241
353
  return rootElem;
242
354
  }
243
355
  function renderWorker(node, children) {
356
+ if (lastRenderError) return;
244
357
  if (!getContext()) {
245
358
  throw new Error("Cannot render without a context. Make sure you are using the Output component.");
246
359
  }
247
- trace(TracePhase.render.worker, () => dumpChildren(children));
248
360
  if (Array.isArray(node)) {
249
361
  nodesToContext.set(node, getContext());
250
362
  }
251
363
  if (Array.isArray(children)) {
252
364
  for (const child of children.flat(Infinity)) {
253
365
  appendChild(node, child);
366
+ if (lastRenderError) break;
254
367
  }
255
368
  } else {
256
369
  appendChild(node, children);
@@ -264,10 +377,11 @@ export function notifyContentState() {
264
377
  untrack(() => {
265
378
  const startContext = getContext();
266
379
  if (startContext.childrenWithContent === 0) {
267
- if (startContext.isEmpty.value === true) {
380
+ if (startContext._lastEmpty) {
268
381
  // it was already empty, no work to do.
269
382
  return;
270
383
  }
384
+ startContext._lastEmpty = true;
271
385
  if (startContext.isEmpty) {
272
386
  startContext.isEmpty.value = true;
273
387
  }
@@ -279,17 +393,23 @@ export function notifyContentState() {
279
393
  break;
280
394
  }
281
395
  current.childrenWithContent--;
396
+ if (current.childrenWithContent > 0) {
397
+ // This isn't the last content so we have no work to do
398
+ break;
399
+ }
400
+ current._lastEmpty = true;
282
401
  if (current.isEmpty) {
283
402
  current.isEmpty.value = true;
284
403
  }
285
404
  current = current.owner;
286
405
  }
287
406
  } else {
288
- if (startContext.isEmpty.value === false) {
407
+ if (!startContext._lastEmpty) {
289
408
  // it was already non-empty, no work to do.
290
409
  return;
291
410
  }
292
- if (startContext.isEmpty && startContext.isEmpty.value) {
411
+ startContext._lastEmpty = false;
412
+ if (startContext.isEmpty) {
293
413
  startContext.isEmpty.value = false;
294
414
  }
295
415
 
@@ -301,7 +421,8 @@ export function notifyContentState() {
301
421
  // This isn't the first content so we have no work to do
302
422
  break;
303
423
  }
304
- if (current.isEmpty && current.isEmpty.value) {
424
+ current._lastEmpty = false;
425
+ if (current.isEmpty) {
305
426
  current.isEmpty.value = false;
306
427
  }
307
428
  current = current.owner;
@@ -310,74 +431,98 @@ export function notifyContentState() {
310
431
  });
311
432
  }
312
433
  function appendChild(node, rawChild) {
313
- trace(TracePhase.render.appendChild, () => debugPrintChild(rawChild));
434
+ if (lastRenderError) return;
314
435
  const child = normalizeChild(rawChild);
315
436
  if (typeof child === "string") {
316
437
  if (child !== "") {
317
438
  contentAdded();
439
+ debug.render.appendTextNode(node, node.length, child);
318
440
  }
319
441
  node.push(child);
320
442
  } else {
321
443
  const cache = getElementCache();
322
444
  if (cache.has(child)) {
323
- trace(TracePhase.render.appendChild, () => "Cached: " + debugPrintChild(child));
324
- node.push(cache.get(child));
445
+ const cachedNode = cache.get(child);
446
+ // recordSubtreeAdded detects cached nodes automatically and re-adds their children
447
+ if (isCustomContext(child)) {
448
+ debug.render.appendCustomContext(node, cachedNode);
449
+ } else {
450
+ debug.render.appendFragmentChild(node, cachedNode);
451
+ }
452
+ node.push(cachedNode);
325
453
  return;
326
454
  }
327
455
  if (isCustomContext(child)) {
328
- trace(TracePhase.render.appendChild, () => "CustomContext: " + debugPrintChild(child));
456
+ const newNode = [];
457
+ debug.render.appendCustomContext(node, newNode);
329
458
  child.useCustomContext(children => {
330
- const newNode = [];
331
459
  renderWorker(newNode, children);
332
460
  node.push(newNode);
333
461
  cache.set(child, newNode);
334
462
  notifyContentState();
463
+ notifyFileUpdateForNode(node);
335
464
  });
336
465
  } else if (isIntrinsicElement(child)) {
337
- trace(TracePhase.render.appendChild, () => "IntrinsicElement: " + debugPrintChild(child));
338
466
  // don't need a new context here because intrinsics are never reactive
467
+ const intrinsic = child;
339
468
  const newNode = [];
340
469
  function formatHookWithChildren(command) {
341
- node.push(createRenderTreeHook(newNode, {
470
+ const hook = createRenderTreeHook(newNode, {
342
471
  print(tree, print) {
343
472
  return command(print(tree));
344
473
  }
345
- }));
474
+ });
475
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
476
+ node.push(hook);
346
477
  renderWorker(newNode, child.props.children);
478
+ notifyFileUpdateForNode(node);
347
479
  }
348
480
  function formatHook(command) {
349
- return node.push(createRenderTreeHook(newNode, {
481
+ const hook = createRenderTreeHook(newNode, {
350
482
  print() {
351
483
  return command;
352
484
  }
353
- }));
485
+ });
486
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name);
487
+ node.push(hook);
488
+ return hook;
354
489
  }
355
490
  switch (child.name) {
356
491
  case "indent":
357
492
  return formatHookWithChildren(indent);
358
493
  case "indentIfBreak":
359
- node.push(createRenderTreeHook(newNode, {
360
- print(tree, print) {
361
- return indentIfBreak(print(tree), {
362
- groupId: child.props.groupId,
363
- negate: child.props.negate
364
- });
365
- }
366
- }));
494
+ {
495
+ const hook = createRenderTreeHook(newNode, {
496
+ print(tree, print) {
497
+ return indentIfBreak(print(tree), {
498
+ groupId: child.props.groupId,
499
+ negate: child.props.negate
500
+ });
501
+ }
502
+ });
503
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
504
+ node.push(hook);
505
+ }
367
506
  renderWorker(newNode, child.props.children);
507
+ notifyFileUpdateForNode(node);
368
508
  return;
369
509
  case "fill":
370
510
  return formatHookWithChildren(fill);
371
511
  case "group":
372
- node.push(createRenderTreeHook(newNode, {
373
- print(tree, print) {
374
- return group(print(tree), {
375
- id: child.props.id,
376
- shouldBreak: child.props.shouldBreak
377
- });
378
- }
379
- }));
512
+ {
513
+ const hook = createRenderTreeHook(newNode, {
514
+ print(tree, print) {
515
+ return group(print(tree), {
516
+ id: child.props.id,
517
+ shouldBreak: child.props.shouldBreak
518
+ });
519
+ }
520
+ });
521
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
522
+ node.push(hook);
523
+ }
380
524
  renderWorker(newNode, child.props.children);
525
+ notifyFileUpdateForNode(node);
381
526
  return;
382
527
  case "line":
383
528
  case "br":
@@ -392,12 +537,17 @@ function appendChild(node, rawChild) {
392
537
  case "lbr":
393
538
  return formatHook(literalline);
394
539
  case "align":
395
- node.push(createRenderTreeHook(newNode, {
396
- print(tree, print) {
397
- return align(child.props.width ?? child.props.string, print(tree));
398
- }
399
- }));
540
+ {
541
+ const hook = createRenderTreeHook(newNode, {
542
+ print(tree, print) {
543
+ return align(child.props.width ?? child.props.string, print(tree));
544
+ }
545
+ });
546
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
547
+ node.push(hook);
548
+ }
400
549
  renderWorker(newNode, child.props.children);
550
+ notifyFileUpdateForNode(node);
401
551
  return;
402
552
  case "lineSuffix":
403
553
  return formatHookWithChildren(lineSuffix);
@@ -412,59 +562,192 @@ function appendChild(node, rawChild) {
412
562
  case "markAsRoot":
413
563
  return formatHookWithChildren(markAsRoot);
414
564
  case "ifBreak":
415
- node.push(createRenderTreeHook(newNode, {
416
- print(tree, print) {
417
- return ifBreak(print(tree[0]), print(tree[1]));
418
- }
419
- }));
565
+ {
566
+ const hook = createRenderTreeHook(newNode, {
567
+ print(tree, print) {
568
+ return ifBreak(print(tree[0]), print(tree[1]));
569
+ }
570
+ });
571
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name, newNode);
572
+ node.push(hook);
573
+ }
420
574
  newNode.push([], []);
575
+ debug.render.appendFragmentChild(newNode, newNode[0]);
576
+ debug.render.appendFragmentChild(newNode, newNode[1]);
421
577
  renderWorker(newNode[0], child.props.children);
422
578
  renderWorker(newNode[1], child.props.flatContents);
579
+ notifyFileUpdateForNode(node);
423
580
  return;
424
581
  default:
425
582
  throw new Error("Unknown intrinsic element");
426
583
  }
427
584
  } else if (isComponentCreator(child)) {
585
+ const index = node.length;
586
+ const rerenderToken = isDevtoolsEnabled() ? ref(0) : undefined;
587
+ const breakNext = isDevtoolsEnabled() ? ref(false) : undefined;
428
588
  // todo: remove this effect (only needed for context, not needed for anything else)
429
589
  effect(() => {
430
- trace(TracePhase.render.appendChild, () => "Component: " + debugPrintChild(child));
590
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
591
+ rerenderToken?.value;
431
592
  const context = getContext();
432
593
  context.childrenWithContent = 0;
433
- context.isEmpty ??= ref(true);
434
594
  if (context) context.componentOwner = child;
435
- const componentRoot = [];
595
+ const existing = node[index];
596
+ const componentRoot = Array.isArray(existing) ? existing : [];
597
+ context.meta ??= {};
598
+ context.meta.renderNode = componentRoot;
599
+ const propsSource = child.props ?? undefined;
600
+ const debugSession = debug.render.beginComponent({
601
+ parent: node,
602
+ index,
603
+ node: componentRoot,
604
+ component: child,
605
+ propsSource,
606
+ source: child.source,
607
+ isExisting: Array.isArray(existing),
608
+ actions: {
609
+ rerender: () => {
610
+ lastRenderError = null;
611
+ if (rerenderToken) rerenderToken.value++;
612
+ },
613
+ rerenderAndBreak: () => {
614
+ lastRenderError = null;
615
+ if (breakNext) breakNext.value = true;
616
+ if (rerenderToken) rerenderToken.value++;
617
+ }
618
+ }
619
+ });
620
+ if (Array.isArray(existing)) {
621
+ componentRoot.length = 0;
622
+ }
436
623
  pushStack(child.component, child.props, child.source);
437
- renderWorker(componentRoot, untrack(child));
438
- popStack();
439
- node.push(componentRoot);
624
+ let renderFailed = false;
625
+ let childResult;
626
+ try {
627
+ childResult = untrack(() => {
628
+ const shouldBreak = breakNext?.value;
629
+ if (shouldBreak) {
630
+ breakNext.value = false;
631
+ // eslint-disable-next-line no-debugger
632
+ debugger;
633
+ }
634
+ return child();
635
+ });
636
+ } catch (error) {
637
+ notifyRenderError(error);
638
+ renderFailed = true;
639
+ throw error;
640
+ }
641
+ try {
642
+ if (context?.meta?.directory) {
643
+ debugSession.recordDirectory(context.meta.directory.path);
644
+ }
645
+ if (context?.meta?.sourceFile) {
646
+ context.meta.renderNode = componentRoot;
647
+ debugSession.recordFile(context.meta.sourceFile.path, context.meta.sourceFile.filetype);
648
+ context.meta.sourceFileReady = false;
649
+ }
650
+ if (!renderFailed) {
651
+ renderWorker(componentRoot, childResult);
652
+ }
653
+ } finally {
654
+ popStack();
655
+ }
656
+ if (renderFailed) {
657
+ node[index] = componentRoot;
658
+ cache.set(child, componentRoot);
659
+ notifyFileUpdateForNode(node);
660
+ notifyContentState();
661
+ onCleanup(() => debugSession.dispose());
662
+ return;
663
+ }
664
+ if (context?.meta?.sourceFile) {
665
+ context.meta.sourceFileReady = true;
666
+ notifyFileUpdateForNode(componentRoot);
667
+ }
668
+ node[index] = componentRoot;
440
669
  cache.set(child, componentRoot);
441
670
  notifyContentState();
442
- trace(TracePhase.render.appendChild, () => "Component done: " + debugPrintChild(child) + ", empty: " + context.isEmpty.value);
671
+ onCleanup(() => debugSession.dispose());
672
+ }, undefined, {
673
+ debug: {
674
+ name: `render:${child.component.name || "Anonymous"}`,
675
+ type: "render"
676
+ }
443
677
  });
444
678
  } else if (typeof child === "function") {
445
- trace(TracePhase.render.appendChild, () => "Memo: " + child.toString());
446
679
  const index = node.length;
447
680
  effect(() => {
448
- trace(TracePhase.render.renderEffect, () => "");
449
- let res = child();
450
- while (typeof res === "function" && !isComponentCreator(res)) {
451
- res = res();
681
+ let res;
682
+ let renderFailed = false;
683
+ try {
684
+ res = child();
685
+ while (typeof res === "function" && !isComponentCreator(res)) {
686
+ res = res();
687
+ }
688
+ } catch (error) {
689
+ notifyRenderError(error);
690
+ renderFailed = true;
691
+ throw error;
452
692
  }
453
693
  const context = getContext();
454
694
  context.childrenWithContent = 0;
455
- context.isEmpty ??= ref(true);
456
- const newNodes = [];
457
- renderWorker(newNodes, res);
458
- node[index] = newNodes;
459
- cache.set(child, newNodes);
695
+ const existing = node[index];
696
+ const memoNode = Array.isArray(existing) ? existing : [];
697
+ debug.render.prepareMemoNode(node, memoNode, Array.isArray(existing));
698
+ if (Array.isArray(existing)) {
699
+ memoNode.length = 0;
700
+ }
701
+ if (!renderFailed) {
702
+ renderWorker(memoNode, res);
703
+ }
704
+ node[index] = memoNode;
705
+ cache.set(child, memoNode);
706
+ notifyFileUpdateForNode(node);
460
707
  notifyContentState();
461
- return newNodes;
708
+ return memoNode;
709
+ }, undefined, {
710
+ debug: {
711
+ name: `render:memo:${child.name || "anonymous"}`,
712
+ type: "render"
713
+ }
462
714
  });
463
715
  } else {
464
716
  throw new Error("Unexpected child type");
465
717
  }
466
718
  }
467
719
  }
720
+ function findSourceFileContext(node) {
721
+ let context = getContextForRenderNode(node) ?? null;
722
+ while (context) {
723
+ if (context.meta?.sourceFile) return context;
724
+ context = context.owner;
725
+ }
726
+ return undefined;
727
+ }
728
+ function notifyFileUpdateForNode(node) {
729
+ // Only do the expensive printTree when devtools are actually enabled
730
+ if (!isDevtoolsEnabled()) return;
731
+ const context = findSourceFileContext(node);
732
+ if (!context?.meta?.sourceFile) return;
733
+ if (context.meta.sourceFileReady === false) return;
734
+ const sourceFile = context.meta.sourceFile;
735
+ const renderNode = context.meta.renderNode ?? node;
736
+ // Pass noFlush here since it flushes jobs and can re-enter rendering
737
+ // during effect setup, triggering premature cleanup.
738
+ const contents = printTree(renderNode, {
739
+ printWidth: context.meta?.printOptions?.printWidth,
740
+ tabWidth: context.meta?.printOptions?.tabWidth,
741
+ useTabs: context.meta?.printOptions?.useTabs,
742
+ insertFinalNewLine: context.meta?.printOptions?.insertFinalNewLine ?? true,
743
+ noFlush: true
744
+ });
745
+ debug.files.updated({
746
+ path: sourceFile.path,
747
+ filetype: sourceFile.filetype,
748
+ contents
749
+ });
750
+ }
468
751
  function normalizeChild(child) {
469
752
  if (Array.isArray(child)) {
470
753
  return child.map(normalizeChild);
@@ -496,29 +779,6 @@ function normalizeChild(child) {
496
779
  return String(child);
497
780
  }
498
781
  }
499
- function dumpChildren(children) {
500
- if (Array.isArray(children)) {
501
- return `[ ${children.map(debugPrintChild).join(", ")} ]`;
502
- }
503
- return debugPrintChild(children);
504
- }
505
- function debugPrintChild(child) {
506
- if (isComponentCreator(child)) {
507
- return "<" + child.component.name + ">";
508
- } else if (typeof child === "function") {
509
- return "$memo";
510
- } else if (isRef(child)) {
511
- return "$ref";
512
- } else if (isIntrinsicElement(child)) {
513
- return `<${child.name}>`;
514
- } else if (isRenderableObject(child)) {
515
- return `CustomChildElement(${JSON.stringify(child)})`;
516
- } else if (isRefkeyable(child)) {
517
- return `refkey`;
518
- } else {
519
- return JSON.stringify(child);
520
- }
521
- }
522
782
  const defaultPrintTreeOptions = {
523
783
  printWidth: 80,
524
784
  tabWidth: 2
@@ -533,9 +793,10 @@ export function printTree(tree, options) {
533
793
  ...defaultPrintTreeOptions,
534
794
  ...Object.fromEntries(Object.entries(options ?? {}).filter(([_, v]) => v !== undefined))
535
795
  };
536
-
537
- // make sure queue is empty
538
- flushJobs();
796
+ if (!options.noFlush) {
797
+ // make sure queue is empty
798
+ flushJobs();
799
+ }
539
800
  const d = printTreeWorker(tree);
540
801
  const result = doc.printer.printDocToString(d, options).formatted;
541
802
  return options.insertFinalNewLine && !result.endsWith("\n") ? `${result}\n` : result;