@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
package/src/render.ts CHANGED
@@ -1,8 +1,27 @@
1
- import { isRef, ref } from "@vue/reactivity";
1
+ import { isRef } from "@vue/reactivity";
2
2
  import { Doc, 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 {
7
+ debug,
8
+ getRenderNodeId,
9
+ isDevtoolsEnabled,
10
+ type RenderTreeNodeInfo,
11
+ } from "./debug/index.js";
12
+ import { broadcastDevtoolsMessage } from "./devtools/devtools-server.js";
13
+ import {
14
+ attachDiagnosticsCollector,
15
+ DiagnosticsCollector,
16
+ emitDiagnostic,
17
+ reportDiagnostics,
18
+ } from "./diagnostics.js";
19
+ import {
20
+ isPrintHook,
21
+ printHookTag,
22
+ type PrintHook,
23
+ type RenderedTextTree,
24
+ } from "./print-hook.js";
6
25
  import {
7
26
  Context,
8
27
  CustomContext,
@@ -10,11 +29,18 @@ import {
10
29
  getContext,
11
30
  getElementCache,
12
31
  isCustomContext,
32
+ onCleanup,
33
+ ref,
13
34
  root,
14
35
  untrack,
15
36
  } from "./reactivity.js";
16
37
  import { isRefkeyable, toRefkey } from "./refkey.js";
17
- import { popStack, printRenderStack, pushStack } from "./render-stack.js";
38
+ import {
39
+ getRenderStackSnapshot,
40
+ popStack,
41
+ printRenderStack,
42
+ pushStack,
43
+ } from "./render-stack.js";
18
44
  import {
19
45
  Child,
20
46
  Children,
@@ -23,8 +49,91 @@ import {
23
49
  RENDERABLE,
24
50
  } from "./runtime/component.js";
25
51
  import { IntrinsicElement, isIntrinsicElement } from "./runtime/intrinsic.js";
26
- import { flushJobs, flushJobsAsync } from "./scheduler.js";
27
- import { trace, TracePhase } from "./tracer.js";
52
+ import { flushJobs, flushJobsAsync, waitForSignal } from "./scheduler.js";
53
+
54
+ const notifiedErrors = new WeakSet<object>();
55
+ let lastRenderError: {
56
+ error: { name: string; message: string; stack?: string };
57
+ componentStack: Array<{
58
+ name: string;
59
+ props?: Record<string, unknown> | undefined;
60
+ propsSerialized?: string;
61
+ renderNodeId?: number;
62
+ source?: RenderTreeNodeInfo["source"];
63
+ }>;
64
+ } | null = null;
65
+
66
+ function normalizeRenderError(error: unknown): {
67
+ name: string;
68
+ message: string;
69
+ stack?: string;
70
+ } {
71
+ if (error instanceof Error) {
72
+ return {
73
+ name: error.name || error.constructor?.name || "Error",
74
+ message: error.message || "",
75
+ stack: error.stack,
76
+ };
77
+ }
78
+ if (error && typeof error === "object") {
79
+ const anyError = error as {
80
+ name?: string;
81
+ message?: string;
82
+ stack?: string;
83
+ };
84
+ return {
85
+ name: anyError.name || "Error",
86
+ message: anyError.message || String(error),
87
+ stack: anyError.stack,
88
+ };
89
+ }
90
+ return {
91
+ name: "Error",
92
+ message: String(error),
93
+ };
94
+ }
95
+
96
+ function notifyRenderError(error: unknown) {
97
+ if (error && typeof error === "object") {
98
+ if (notifiedErrors.has(error)) return;
99
+ notifiedErrors.add(error);
100
+ }
101
+ if (lastRenderError) return;
102
+
103
+ const { name, message, stack } = normalizeRenderError(error);
104
+ const componentStack = getRenderStackSnapshot().map((entry) => {
105
+ const renderNode = entry.context?.meta?.renderNode as
106
+ | RenderedTextTree
107
+ | undefined;
108
+ const renderNodeId = renderNode ? getRenderNodeId(renderNode) : undefined;
109
+ return {
110
+ name: entry.displayName,
111
+ props: entry.props as Record<string, unknown> | undefined,
112
+ renderNodeId,
113
+ source: entry.source,
114
+ };
115
+ });
116
+
117
+ // Output to console
118
+ printRenderStack(error);
119
+
120
+ // Send to devtools if enabled
121
+ debug.render.error({ name, message, stack }, componentStack);
122
+
123
+ // Store for diagnostics
124
+ lastRenderError = { error: { name, message, stack }, componentStack };
125
+ const lastEntry = componentStack.at(-1);
126
+ emitDiagnostic({
127
+ severity: "error",
128
+ message: `${name}: ${message}`,
129
+ source: lastEntry?.source,
130
+ });
131
+ }
132
+
133
+ function reportLastRenderError() {
134
+ // Error already reported in notifyRenderError via debug.renderError
135
+ lastRenderError = null;
136
+ }
28
137
 
29
138
  const {
30
139
  builders: {
@@ -150,23 +259,36 @@ export interface ContentOutputFile extends OutputFileBase {
150
259
  export type OutputFile = ContentOutputFile | CopyOutputFile;
151
260
 
152
261
  const nodesToContext = new WeakMap<RenderedTextTree, Context>();
262
+ const diagnosticsByTree = new WeakMap<RenderedTextTree, DiagnosticsCollector>();
153
263
 
154
264
  export function getContextForRenderNode(node: RenderedTextTree) {
155
265
  return nodesToContext.get(node);
156
266
  }
157
267
 
158
- export const printHookTag = Symbol();
268
+ export function getDiagnosticsForTree(tree: RenderedTextTree) {
269
+ return diagnosticsByTree.get(tree)?.getDiagnostics() ?? [];
270
+ }
159
271
 
160
- export interface PrintHook {
161
- [printHookTag]: true;
162
- transform?(tree: RenderedTextTree): RenderedTextTree;
163
- print?(
164
- tree: RenderedTextTree,
165
- print: (subtree: RenderedTextTree) => Doc,
166
- ): Doc;
167
- subtree: RenderedTextTree;
272
+ function reportDiagnosticsForTree(tree: RenderedTextTree) {
273
+ const diagnostics = diagnosticsByTree.get(tree);
274
+ if (!diagnostics) return;
275
+ const entries = diagnostics.getDiagnostics();
276
+ if (entries.length === 0) return;
277
+ reportDiagnostics(diagnostics);
278
+ void broadcastDevtoolsMessage({
279
+ type: "diagnostics:report",
280
+ diagnostics: entries,
281
+ });
168
282
  }
169
283
 
284
+ // Re-export from print-hook.ts to maintain backwards compatibility
285
+ export {
286
+ isPrintHook,
287
+ printHookTag,
288
+ type PrintHook,
289
+ type RenderedTextTree,
290
+ } from "./print-hook.js";
291
+
170
292
  export function createRenderTreeHook(
171
293
  subtree: RenderedTextTree,
172
294
  hooks: Omit<PrintHook, typeof printHookTag | "subtree">,
@@ -178,12 +300,6 @@ export function createRenderTreeHook(
178
300
  };
179
301
  }
180
302
 
181
- export function isPrintHook(type: unknown): type is PrintHook {
182
- return typeof type === "object" && type !== null && printHookTag in type;
183
- }
184
-
185
- export type RenderedTextTree = (string | RenderedTextTree | PrintHook)[];
186
-
187
303
  /**
188
304
  * Render a component tree to source directories and files. Will ensure that
189
305
  * all non-async scheduled jobs are completed before returning. If async jobs
@@ -196,7 +312,14 @@ export function render(
196
312
  ): OutputDirectory {
197
313
  const tree = renderTree(children);
198
314
  flushJobs();
199
- return sourceFilesForTree(tree, options);
315
+ const output = sourceFilesForTree(tree, options);
316
+ reportDiagnosticsForTree(tree);
317
+ reportLastRenderError();
318
+ debug.render.complete();
319
+ if (isDevtoolsEnabled()) {
320
+ void waitForSignal();
321
+ }
322
+ return output;
200
323
  }
201
324
 
202
325
  /**
@@ -207,23 +330,16 @@ export async function renderAsync(
207
330
  children: Children,
208
331
  options?: PrintTreeOptions,
209
332
  ): Promise<OutputDirectory> {
333
+ await debug.prepare();
210
334
  const tree = renderTree(children);
211
- return sourceFilesForTreeAsync(tree, options);
212
- }
213
-
214
- /**
215
- * Convert a rendered text tree to source directories and files. Will ensure that
216
- * all scheduled jobs are completed, including async ones.
217
- */
218
- export async function sourceFilesForTreeAsync(
219
- tree: RenderedTextTree,
220
- options?: PrintTreeOptions,
221
- ) {
222
- // if we await here, we ensure all reactive updates are flushed.
223
- // sourceFilesForTree will flush again, but won't find anything, because tree
224
- // printing won't schedule anything.
335
+ // Ensure all reactive updates are flushed before printing.
225
336
  await flushJobsAsync();
226
- return sourceFilesForTree(tree, options);
337
+ const output = sourceFilesForTree(tree, options);
338
+ reportDiagnosticsForTree(tree);
339
+ reportLastRenderError();
340
+ debug.render.complete();
341
+
342
+ return output;
227
343
  }
228
344
 
229
345
  /**
@@ -239,9 +355,12 @@ export function sourceFilesForTree(
239
355
  collectSourceFiles(undefined, tree);
240
356
 
241
357
  if (!rootDirectory) {
242
- throw new Error(
243
- "No root directory found. Make sure you are using the Output component.",
244
- );
358
+ emitDiagnostic({
359
+ severity: "error",
360
+ message:
361
+ "No root directory found. Make sure you are using the output component.",
362
+ });
363
+ return { kind: "directory", path: "", contents: [] };
245
364
  }
246
365
 
247
366
  return rootDirectory;
@@ -325,25 +444,35 @@ export function sourceFilesForTree(
325
444
  }
326
445
  export function renderTree(children: Children) {
327
446
  const rootElem: RenderedTextTree = [];
447
+ const diagnostics = new DiagnosticsCollector();
448
+ lastRenderError = null;
449
+ debug.effect.reset();
450
+ debug.symbols.reset();
451
+ debug.files.reset();
452
+ debug.render.initialize(rootElem);
328
453
  try {
329
454
  root(() => {
455
+ attachDiagnosticsCollector(diagnostics);
330
456
  renderWorker(rootElem, children);
331
457
  });
332
458
  } catch (e) {
333
- printRenderStack();
459
+ notifyRenderError(e);
460
+ reportLastRenderError();
334
461
  throw e;
335
462
  }
336
463
 
464
+ diagnosticsByTree.set(rootElem, diagnostics);
465
+
337
466
  return rootElem;
338
467
  }
339
468
 
340
469
  function renderWorker(node: RenderedTextTree, children: Children) {
470
+ if (lastRenderError) return;
341
471
  if (!getContext()) {
342
472
  throw new Error(
343
473
  "Cannot render without a context. Make sure you are using the Output component.",
344
474
  );
345
475
  }
346
- trace(TracePhase.render.worker, () => dumpChildren(children));
347
476
 
348
477
  if (Array.isArray(node)) {
349
478
  nodesToContext.set(node, getContext()!);
@@ -352,6 +481,7 @@ function renderWorker(node: RenderedTextTree, children: Children) {
352
481
  if (Array.isArray(children)) {
353
482
  for (const child of (children as any).flat(Infinity)) {
354
483
  appendChild(node, child);
484
+ if (lastRenderError) break;
355
485
  }
356
486
  } else {
357
487
  appendChild(node, children);
@@ -368,11 +498,12 @@ export function notifyContentState() {
368
498
  const startContext = getContext()!;
369
499
 
370
500
  if (startContext.childrenWithContent === 0) {
371
- if (startContext.isEmpty!.value === true) {
501
+ if (startContext._lastEmpty) {
372
502
  // it was already empty, no work to do.
373
503
  return;
374
504
  }
375
505
 
506
+ startContext._lastEmpty = true;
376
507
  if (startContext.isEmpty) {
377
508
  startContext.isEmpty.value = true;
378
509
  }
@@ -384,18 +515,24 @@ export function notifyContentState() {
384
515
  break;
385
516
  }
386
517
  current.childrenWithContent--;
518
+ if (current.childrenWithContent > 0) {
519
+ // This isn't the last content so we have no work to do
520
+ break;
521
+ }
522
+ current._lastEmpty = true;
387
523
  if (current.isEmpty) {
388
524
  current.isEmpty.value = true;
389
525
  }
390
526
  current = current.owner;
391
527
  }
392
528
  } else {
393
- if (startContext.isEmpty!.value === false) {
529
+ if (!startContext._lastEmpty) {
394
530
  // it was already non-empty, no work to do.
395
531
  return;
396
532
  }
397
533
 
398
- if (startContext.isEmpty && startContext.isEmpty.value) {
534
+ startContext._lastEmpty = false;
535
+ if (startContext.isEmpty) {
399
536
  startContext.isEmpty.value = false;
400
537
  }
401
538
 
@@ -408,7 +545,8 @@ export function notifyContentState() {
408
545
  break;
409
546
  }
410
547
 
411
- if (current.isEmpty && current.isEmpty.value) {
548
+ current._lastEmpty = false;
549
+ if (current.isEmpty) {
412
550
  current.isEmpty.value = false;
413
551
  }
414
552
 
@@ -419,95 +557,120 @@ export function notifyContentState() {
419
557
  }
420
558
 
421
559
  function appendChild(node: RenderedTextTree, rawChild: Child) {
422
- trace(TracePhase.render.appendChild, () => debugPrintChild(rawChild));
560
+ if (lastRenderError) return;
423
561
  const child = normalizeChild(rawChild);
424
562
 
425
563
  if (typeof child === "string") {
426
564
  if (child !== "") {
427
565
  contentAdded();
566
+ debug.render.appendTextNode(node, node.length, child);
428
567
  }
429
568
  node.push(child);
430
569
  } else {
431
570
  const cache = getElementCache();
432
571
  if (cache.has(child as any)) {
433
- trace(
434
- TracePhase.render.appendChild,
435
- () => "Cached: " + debugPrintChild(child),
436
- );
437
- node.push(cache.get(child as any)!);
572
+ const cachedNode = cache.get(child as any)!;
573
+ // recordSubtreeAdded detects cached nodes automatically and re-adds their children
574
+ if (isCustomContext(child)) {
575
+ debug.render.appendCustomContext(node, cachedNode);
576
+ } else {
577
+ debug.render.appendFragmentChild(node, cachedNode);
578
+ }
579
+ node.push(cachedNode);
438
580
  return;
439
581
  }
440
582
  if (isCustomContext(child)) {
441
- trace(
442
- TracePhase.render.appendChild,
443
- () => "CustomContext: " + debugPrintChild(child),
444
- );
583
+ const newNode: RenderedTextTree = [];
584
+ debug.render.appendCustomContext(node, newNode);
445
585
  child.useCustomContext((children) => {
446
- const newNode: RenderedTextTree = [];
447
586
  renderWorker(newNode, children);
448
587
  node.push(newNode);
449
588
  cache.set(child, newNode);
450
589
  notifyContentState();
590
+ notifyFileUpdateForNode(node);
451
591
  });
452
592
  } else if (isIntrinsicElement(child)) {
453
- trace(
454
- TracePhase.render.appendChild,
455
- () => "IntrinsicElement: " + debugPrintChild(child),
456
- );
457
593
  // don't need a new context here because intrinsics are never reactive
594
+ const intrinsic = child as IntrinsicElement;
458
595
  const newNode: RenderedTextTree = [];
459
596
 
460
597
  function formatHookWithChildren(command: (doc: Doc) => Doc) {
461
- node.push(
462
- createRenderTreeHook(newNode, {
463
- print(tree, print) {
464
- return command(print(tree));
465
- },
466
- }),
598
+ const hook = createRenderTreeHook(newNode, {
599
+ print(tree, print) {
600
+ return command(print(tree));
601
+ },
602
+ });
603
+ debug.render.appendPrintHook(
604
+ node,
605
+ node.length,
606
+ hook,
607
+ intrinsic.name,
608
+ newNode,
467
609
  );
610
+ node.push(hook);
468
611
  renderWorker(newNode, (child as any).props.children);
612
+ notifyFileUpdateForNode(node);
469
613
  }
470
614
 
471
615
  function formatHook(command: Doc) {
472
- return node.push(
473
- createRenderTreeHook(newNode, {
474
- print() {
475
- return command;
476
- },
477
- }),
478
- );
616
+ const hook = createRenderTreeHook(newNode, {
617
+ print() {
618
+ return command;
619
+ },
620
+ });
621
+ debug.render.appendPrintHook(node, node.length, hook, intrinsic.name);
622
+ node.push(hook);
623
+ return hook;
479
624
  }
480
625
 
481
626
  switch (child.name) {
482
627
  case "indent":
483
628
  return formatHookWithChildren(indent);
484
629
  case "indentIfBreak":
485
- node.push(
486
- createRenderTreeHook(newNode, {
630
+ {
631
+ const hook = createRenderTreeHook(newNode, {
487
632
  print(tree, print) {
488
633
  return indentIfBreak(print(tree), {
489
634
  groupId: child.props.groupId,
490
635
  negate: child.props.negate,
491
636
  });
492
637
  },
493
- }),
494
- );
638
+ });
639
+ debug.render.appendPrintHook(
640
+ node,
641
+ node.length,
642
+ hook,
643
+ intrinsic.name,
644
+ newNode,
645
+ );
646
+ node.push(hook);
647
+ }
495
648
  renderWorker(newNode, child.props.children);
649
+ notifyFileUpdateForNode(node);
496
650
  return;
497
651
  case "fill":
498
652
  return formatHookWithChildren(fill as any);
499
653
  case "group":
500
- node.push(
501
- createRenderTreeHook(newNode, {
654
+ {
655
+ const hook = createRenderTreeHook(newNode, {
502
656
  print(tree, print) {
503
657
  return group(print(tree), {
504
658
  id: child.props.id,
505
659
  shouldBreak: child.props.shouldBreak,
506
660
  });
507
661
  },
508
- }),
509
- );
662
+ });
663
+ debug.render.appendPrintHook(
664
+ node,
665
+ node.length,
666
+ hook,
667
+ intrinsic.name,
668
+ newNode,
669
+ );
670
+ node.push(hook);
671
+ }
510
672
  renderWorker(newNode, child.props.children);
673
+ notifyFileUpdateForNode(node);
511
674
  return;
512
675
  case "line":
513
676
  case "br":
@@ -522,17 +685,26 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
522
685
  case "lbr":
523
686
  return formatHook(literalline);
524
687
  case "align":
525
- node.push(
526
- createRenderTreeHook(newNode, {
688
+ {
689
+ const hook = createRenderTreeHook(newNode, {
527
690
  print(tree, print) {
528
691
  return align(
529
692
  (child.props as any).width ?? (child.props as any).string!,
530
693
  print(tree),
531
694
  );
532
695
  },
533
- }),
534
- );
696
+ });
697
+ debug.render.appendPrintHook(
698
+ node,
699
+ node.length,
700
+ hook,
701
+ intrinsic.name,
702
+ newNode,
703
+ );
704
+ node.push(hook);
705
+ }
535
706
  renderWorker(newNode, (child as any).props.children);
707
+ notifyFileUpdateForNode(node);
536
708
  return;
537
709
  case "lineSuffix":
538
710
  return formatHookWithChildren(lineSuffix);
@@ -547,17 +719,33 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
547
719
  case "markAsRoot":
548
720
  return formatHookWithChildren(markAsRoot);
549
721
  case "ifBreak":
550
- node.push(
551
- createRenderTreeHook(newNode, {
722
+ {
723
+ const hook = createRenderTreeHook(newNode, {
552
724
  print(tree, print) {
553
725
  return ifBreak(
554
726
  print((tree as RenderedTextTree[])[0]),
555
727
  print((tree as RenderedTextTree[])[1]),
556
728
  );
557
729
  },
558
- }),
559
- );
730
+ });
731
+ debug.render.appendPrintHook(
732
+ node,
733
+ node.length,
734
+ hook,
735
+ intrinsic.name,
736
+ newNode,
737
+ );
738
+ node.push(hook);
739
+ }
560
740
  newNode.push([], []);
741
+ debug.render.appendFragmentChild(
742
+ newNode,
743
+ newNode[0] as RenderedTextTree,
744
+ );
745
+ debug.render.appendFragmentChild(
746
+ newNode,
747
+ newNode[1] as RenderedTextTree,
748
+ );
561
749
  renderWorker(
562
750
  newNode[0] as RenderedTextTree[],
563
751
  (child as any).props.children,
@@ -566,66 +754,204 @@ function appendChild(node: RenderedTextTree, rawChild: Child) {
566
754
  newNode[1] as RenderedTextTree[],
567
755
  (child as any).props.flatContents,
568
756
  );
757
+ notifyFileUpdateForNode(node);
569
758
  return;
570
759
  default:
571
760
  throw new Error("Unknown intrinsic element");
572
761
  }
573
762
  } else if (isComponentCreator(child)) {
763
+ const index = node.length;
764
+ const rerenderToken = isDevtoolsEnabled() ? ref(0) : undefined;
765
+ const breakNext = isDevtoolsEnabled() ? ref(false) : undefined;
574
766
  // todo: remove this effect (only needed for context, not needed for anything else)
575
- effect(() => {
576
- trace(
577
- TracePhase.render.appendChild,
578
- () => "Component: " + debugPrintChild(child),
579
- );
580
- const context = getContext();
581
- context!.childrenWithContent = 0;
582
- context!.isEmpty ??= ref(true);
583
-
584
- if (context) context.componentOwner = child;
585
- const componentRoot: RenderedTextTree = [];
586
-
587
- pushStack(child.component, child.props, child.source);
588
- renderWorker(componentRoot, untrack(child));
589
- popStack();
590
- node.push(componentRoot);
591
- cache.set(child, componentRoot);
592
- notifyContentState();
593
- trace(
594
- TracePhase.render.appendChild,
595
- () =>
596
- "Component done: " +
597
- debugPrintChild(child) +
598
- ", empty: " +
599
- context!.isEmpty!.value,
600
- );
601
- });
767
+ effect(
768
+ () => {
769
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
770
+ rerenderToken?.value;
771
+ const context = getContext();
772
+ context!.childrenWithContent = 0;
773
+
774
+ if (context) context.componentOwner = child;
775
+ const existing = node[index];
776
+ const componentRoot: RenderedTextTree =
777
+ Array.isArray(existing) ? existing : [];
778
+ context!.meta ??= {};
779
+ context!.meta.renderNode = componentRoot;
780
+ const propsSource = (child.props ?? undefined) as
781
+ | Record<string, unknown>
782
+ | undefined;
783
+ const debugSession = debug.render.beginComponent({
784
+ parent: node,
785
+ index,
786
+ node: componentRoot,
787
+ component: child,
788
+ propsSource,
789
+ source: child.source,
790
+ isExisting: Array.isArray(existing),
791
+ actions: {
792
+ rerender: () => {
793
+ lastRenderError = null;
794
+ if (rerenderToken) rerenderToken.value++;
795
+ },
796
+ rerenderAndBreak: () => {
797
+ lastRenderError = null;
798
+ if (breakNext) breakNext.value = true;
799
+ if (rerenderToken) rerenderToken.value++;
800
+ },
801
+ },
802
+ });
803
+ if (Array.isArray(existing)) {
804
+ componentRoot.length = 0;
805
+ }
806
+
807
+ pushStack(child.component, child.props, child.source);
808
+ let renderFailed = false;
809
+ let childResult: Children | undefined;
810
+ try {
811
+ childResult = untrack(() => {
812
+ const shouldBreak = breakNext?.value;
813
+ if (shouldBreak) {
814
+ breakNext!.value = false;
815
+ // eslint-disable-next-line no-debugger
816
+ debugger;
817
+ }
818
+ return child();
819
+ });
820
+ } catch (error) {
821
+ notifyRenderError(error);
822
+ renderFailed = true;
823
+ throw error;
824
+ }
825
+ try {
826
+ if (context?.meta?.directory) {
827
+ debugSession.recordDirectory(context.meta.directory.path);
828
+ }
829
+ if (context?.meta?.sourceFile) {
830
+ context.meta.renderNode = componentRoot;
831
+ debugSession.recordFile(
832
+ context.meta.sourceFile.path,
833
+ context.meta.sourceFile.filetype,
834
+ );
835
+ context.meta.sourceFileReady = false;
836
+ }
837
+ if (!renderFailed) {
838
+ renderWorker(componentRoot, childResult);
839
+ }
840
+ } finally {
841
+ popStack();
842
+ }
843
+ if (renderFailed) {
844
+ node[index] = componentRoot;
845
+ cache.set(child, componentRoot);
846
+ notifyFileUpdateForNode(node);
847
+ notifyContentState();
848
+ onCleanup(() => debugSession.dispose());
849
+ return;
850
+ }
851
+ if (context?.meta?.sourceFile) {
852
+ context.meta.sourceFileReady = true;
853
+ notifyFileUpdateForNode(componentRoot);
854
+ }
855
+ node[index] = componentRoot;
856
+ cache.set(child, componentRoot);
857
+ notifyContentState();
858
+ onCleanup(() => debugSession.dispose());
859
+ },
860
+ undefined,
861
+ {
862
+ debug: {
863
+ name: `render:${child.component.name || "Anonymous"}`,
864
+ type: "render",
865
+ },
866
+ },
867
+ );
602
868
  } else if (typeof child === "function") {
603
- trace(TracePhase.render.appendChild, () => "Memo: " + child.toString());
604
869
  const index = node.length;
605
- effect(() => {
606
- trace(TracePhase.render.renderEffect, () => "");
607
- let res = child();
608
- while (typeof res === "function" && !isComponentCreator(res)) {
609
- res = res();
610
- }
611
- const context = getContext();
612
- context!.childrenWithContent = 0;
613
- context!.isEmpty ??= ref(true);
614
-
615
- const newNodes: RenderedTextTree = [];
616
- renderWorker(newNodes, res);
617
- node[index] = newNodes;
618
- cache.set(child, newNodes);
619
-
620
- notifyContentState();
621
- return newNodes;
622
- });
870
+ effect(
871
+ () => {
872
+ let res: Child | Children | undefined;
873
+ let renderFailed = false;
874
+ try {
875
+ res = child();
876
+ while (typeof res === "function" && !isComponentCreator(res)) {
877
+ res = res();
878
+ }
879
+ } catch (error) {
880
+ notifyRenderError(error);
881
+ renderFailed = true;
882
+ throw error;
883
+ }
884
+ const context = getContext();
885
+ context!.childrenWithContent = 0;
886
+
887
+ const existing = node[index];
888
+ const memoNode: RenderedTextTree =
889
+ Array.isArray(existing) ? existing : [];
890
+
891
+ debug.render.prepareMemoNode(node, memoNode, Array.isArray(existing));
892
+ if (Array.isArray(existing)) {
893
+ memoNode.length = 0;
894
+ }
895
+
896
+ if (!renderFailed) {
897
+ renderWorker(memoNode, res);
898
+ }
899
+ node[index] = memoNode;
900
+ cache.set(child, memoNode);
901
+ notifyFileUpdateForNode(node);
902
+ notifyContentState();
903
+ return memoNode;
904
+ },
905
+ undefined,
906
+ {
907
+ debug: {
908
+ name: `render:memo:${child.name || "anonymous"}`,
909
+ type: "render",
910
+ },
911
+ },
912
+ );
623
913
  } else {
624
914
  throw new Error("Unexpected child type");
625
915
  }
626
916
  }
627
917
  }
628
918
 
919
+ function findSourceFileContext(node: RenderedTextTree) {
920
+ let context: Context | null | undefined =
921
+ getContextForRenderNode(node) ?? null;
922
+ while (context) {
923
+ if (context.meta?.sourceFile) return context;
924
+ context = context.owner;
925
+ }
926
+ return undefined;
927
+ }
928
+
929
+ function notifyFileUpdateForNode(node: RenderedTextTree) {
930
+ // Only do the expensive printTree when devtools are actually enabled
931
+ if (!isDevtoolsEnabled()) return;
932
+ const context = findSourceFileContext(node);
933
+ if (!context?.meta?.sourceFile) return;
934
+ if (context.meta.sourceFileReady === false) return;
935
+ const sourceFile = context.meta.sourceFile;
936
+ const renderNode: RenderedTextTree =
937
+ (context.meta.renderNode as RenderedTextTree | undefined) ?? node;
938
+ // Pass noFlush here since it flushes jobs and can re-enter rendering
939
+ // during effect setup, triggering premature cleanup.
940
+ const contents = printTree(renderNode, {
941
+ printWidth: context.meta?.printOptions?.printWidth,
942
+ tabWidth: context.meta?.printOptions?.tabWidth,
943
+ useTabs: context.meta?.printOptions?.useTabs,
944
+ insertFinalNewLine: context.meta?.printOptions?.insertFinalNewLine ?? true,
945
+ noFlush: true,
946
+ });
947
+
948
+ debug.files.updated({
949
+ path: sourceFile.path,
950
+ filetype: sourceFile.filetype,
951
+ contents,
952
+ });
953
+ }
954
+
629
955
  type NormalizedChildren = NormalizedChild | NormalizedChildren[];
630
956
  type NormalizedChild =
631
957
  | string
@@ -668,31 +994,6 @@ function normalizeChild(child: Child): NormalizedChildren {
668
994
  }
669
995
  }
670
996
 
671
- function dumpChildren(children: Children): string {
672
- if (Array.isArray(children)) {
673
- return `[ ${children.map(debugPrintChild).join(", ")} ]`;
674
- }
675
- return debugPrintChild(children);
676
- }
677
-
678
- function debugPrintChild(child: Children): string {
679
- if (isComponentCreator(child)) {
680
- return "<" + child.component.name + ">";
681
- } else if (typeof child === "function") {
682
- return "$memo";
683
- } else if (isRef(child)) {
684
- return "$ref";
685
- } else if (isIntrinsicElement(child)) {
686
- return `<${child.name}>`;
687
- } else if (isRenderableObject(child)) {
688
- return `CustomChildElement(${JSON.stringify(child)})`;
689
- } else if (isRefkeyable(child)) {
690
- return `refkey`;
691
- } else {
692
- return JSON.stringify(child);
693
- }
694
- }
695
-
696
997
  export interface PrintTreeOptions {
697
998
  /**
698
999
  * The number of characters the printer will wrap on. Defaults to 100
@@ -715,6 +1016,12 @@ export interface PrintTreeOptions {
715
1016
  * @default true
716
1017
  */
717
1018
  insertFinalNewLine?: boolean;
1019
+
1020
+ /**
1021
+ * Skip flushing scheduled jobs before printing.
1022
+ * @default false
1023
+ */
1024
+ noFlush?: boolean;
718
1025
  }
719
1026
 
720
1027
  const defaultPrintTreeOptions: PrintTreeOptions = {
@@ -734,8 +1041,10 @@ export function printTree(tree: RenderedTextTree, options?: PrintTreeOptions) {
734
1041
  ),
735
1042
  };
736
1043
 
737
- // make sure queue is empty
738
- flushJobs();
1044
+ if (!options.noFlush) {
1045
+ // make sure queue is empty
1046
+ flushJobs();
1047
+ }
739
1048
 
740
1049
  const d = printTreeWorker(tree);
741
1050
  const result = doc.printer.printDocToString(