@alloy-js/core 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/src/binder.d.ts +18 -235
  3. package/dist/src/binder.d.ts.map +1 -1
  4. package/dist/src/binder.js +85 -386
  5. package/dist/src/components/Block.d.ts +1 -1
  6. package/dist/src/components/Block.d.ts.map +1 -1
  7. package/dist/src/components/Block.js +3 -1
  8. package/dist/src/components/Declaration.d.ts +1 -1
  9. package/dist/src/components/Declaration.d.ts.map +1 -1
  10. package/dist/src/components/Declaration.js +5 -4
  11. package/dist/src/components/For.d.ts +1 -1
  12. package/dist/src/components/For.d.ts.map +1 -1
  13. package/dist/src/components/For.js +1 -1
  14. package/dist/src/components/Indent.d.ts +1 -1
  15. package/dist/src/components/Indent.d.ts.map +1 -1
  16. package/dist/src/components/List.d.ts +1 -1
  17. package/dist/src/components/List.d.ts.map +1 -1
  18. package/dist/src/components/List.js +2 -1
  19. package/dist/src/components/MemberDeclaration.d.ts +1 -1
  20. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  21. package/dist/src/components/MemberDeclaration.js +3 -4
  22. package/dist/src/components/MemberScope.d.ts +1 -1
  23. package/dist/src/components/MemberScope.d.ts.map +1 -1
  24. package/dist/src/components/MemberScope.js +0 -2
  25. package/dist/src/components/Prose.d.ts +1 -1
  26. package/dist/src/components/Prose.d.ts.map +1 -1
  27. package/dist/src/components/ReferenceOrContent.d.ts +1 -1
  28. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -1
  29. package/dist/src/components/Scope.d.ts +1 -1
  30. package/dist/src/components/Scope.d.ts.map +1 -1
  31. package/dist/src/components/Scope.js +3 -6
  32. package/dist/src/components/Show.d.ts +1 -1
  33. package/dist/src/components/Show.d.ts.map +1 -1
  34. package/dist/src/components/StatementList.d.ts +1 -1
  35. package/dist/src/components/StatementList.d.ts.map +1 -1
  36. package/dist/src/components/StatementList.js +1 -1
  37. package/dist/src/components/Switch.d.ts +1 -1
  38. package/dist/src/components/Switch.d.ts.map +1 -1
  39. package/dist/src/components/Switch.js +1 -1
  40. package/dist/src/components/Wrap.d.ts +1 -1
  41. package/dist/src/components/Wrap.d.ts.map +1 -1
  42. package/dist/src/context/assignment.d.ts +1 -1
  43. package/dist/src/context/assignment.d.ts.map +1 -1
  44. package/dist/src/context/binder.d.ts +2 -2
  45. package/dist/src/context/binder.d.ts.map +1 -1
  46. package/dist/src/context/declaration.d.ts +1 -1
  47. package/dist/src/context/declaration.d.ts.map +1 -1
  48. package/dist/src/context/member-declaration.d.ts +1 -1
  49. package/dist/src/context/member-declaration.d.ts.map +1 -1
  50. package/dist/src/context/member-declaration.js +0 -1
  51. package/dist/src/context/member-scope.d.ts +1 -1
  52. package/dist/src/context/member-scope.d.ts.map +1 -1
  53. package/dist/src/context/name-policy.d.ts +1 -1
  54. package/dist/src/context/name-policy.d.ts.map +1 -1
  55. package/dist/src/context/scope.d.ts +1 -1
  56. package/dist/src/context/scope.d.ts.map +1 -1
  57. package/dist/src/context/source-directory.d.ts +1 -1
  58. package/dist/src/context/source-directory.d.ts.map +1 -1
  59. package/dist/src/context/source-file.d.ts +2 -2
  60. package/dist/src/context/source-file.d.ts.map +1 -1
  61. package/dist/src/index.d.ts +4 -1
  62. package/dist/src/index.d.ts.map +1 -1
  63. package/dist/src/index.js +4 -1
  64. package/dist/src/jsx-runtime.d.ts +12 -3
  65. package/dist/src/jsx-runtime.d.ts.map +1 -1
  66. package/dist/src/jsx-runtime.js +6 -2
  67. package/dist/src/reactive-union-set.d.ts +29 -0
  68. package/dist/src/reactive-union-set.d.ts.map +1 -0
  69. package/dist/src/reactive-union-set.js +183 -0
  70. package/dist/src/refkey.d.ts +36 -0
  71. package/dist/src/refkey.d.ts.map +1 -1
  72. package/dist/src/refkey.js +40 -0
  73. package/dist/src/scheduler.d.ts +2 -2
  74. package/dist/src/scheduler.d.ts.map +1 -1
  75. package/dist/src/scheduler.js +27 -6
  76. package/dist/src/slot.d.ts +1 -1
  77. package/dist/src/slot.d.ts.map +1 -1
  78. package/dist/src/stc.d.ts +1 -1
  79. package/dist/src/stc.d.ts.map +1 -1
  80. package/dist/src/sti.d.ts +1 -1
  81. package/dist/src/sti.d.ts.map +1 -1
  82. package/dist/src/sti.js +1 -1
  83. package/dist/src/symbols/index.d.ts +6 -0
  84. package/dist/src/symbols/index.d.ts.map +1 -0
  85. package/dist/src/symbols/index.js +5 -0
  86. package/dist/src/symbols/output-scope.d.ts +116 -0
  87. package/dist/src/symbols/output-scope.d.ts.map +1 -0
  88. package/dist/src/symbols/output-scope.js +246 -0
  89. package/dist/src/symbols/output-symbol.d.ts +134 -0
  90. package/dist/src/symbols/output-symbol.d.ts.map +1 -0
  91. package/dist/src/symbols/output-symbol.js +379 -0
  92. package/dist/src/symbols/symbol-flow.d.ts +13 -0
  93. package/dist/src/symbols/symbol-flow.d.ts.map +1 -0
  94. package/dist/src/symbols/symbol-flow.js +74 -0
  95. package/dist/src/symbols/symbol-slot.d.ts +12 -0
  96. package/dist/src/symbols/symbol-slot.d.ts.map +1 -0
  97. package/dist/src/symbols/symbol-slot.js +36 -0
  98. package/dist/src/symbols/symbol-table.d.ts +14 -0
  99. package/dist/src/symbols/symbol-table.d.ts.map +1 -0
  100. package/dist/src/symbols/symbol-table.js +42 -0
  101. package/dist/src/tap.d.ts +2 -1
  102. package/dist/src/tap.d.ts.map +1 -1
  103. package/dist/src/tracer.d.ts +181 -0
  104. package/dist/src/tracer.d.ts.map +1 -0
  105. package/dist/src/tracer.js +441 -0
  106. package/dist/src/tsdoc-metadata.json +1 -1
  107. package/dist/test/components/slot.test.js +5 -7
  108. package/dist/test/reactive-union-set.test.d.ts +2 -0
  109. package/dist/test/reactive-union-set.test.d.ts.map +1 -0
  110. package/dist/test/reactive-union-set.test.js +170 -0
  111. package/dist/test/symbols/output-scope.test.d.ts +2 -0
  112. package/dist/test/symbols/output-scope.test.d.ts.map +1 -0
  113. package/dist/test/symbols/output-scope.test.js +342 -0
  114. package/dist/test/symbols/output-symbol.test.d.ts +2 -0
  115. package/dist/test/symbols/output-symbol.test.d.ts.map +1 -0
  116. package/dist/test/symbols/output-symbol.test.js +446 -0
  117. package/dist/test/symbols/resolution.test.d.ts +2 -0
  118. package/dist/test/symbols/resolution.test.d.ts.map +1 -0
  119. package/dist/test/symbols/resolution.test.js +140 -0
  120. package/dist/test/symbols/utils.d.ts +24 -0
  121. package/dist/test/symbols/utils.d.ts.map +1 -0
  122. package/dist/test/symbols/utils.js +46 -0
  123. package/dist/tsconfig.tsbuildinfo +1 -1
  124. package/package.json +3 -3
  125. package/src/binder.ts +148 -672
  126. package/src/components/Block.tsx +4 -1
  127. package/src/components/Declaration.tsx +6 -6
  128. package/src/components/For.tsx +1 -1
  129. package/src/components/Indent.tsx +1 -1
  130. package/src/components/List.tsx +1 -1
  131. package/src/components/MemberDeclaration.tsx +4 -4
  132. package/src/components/MemberScope.tsx +2 -5
  133. package/src/components/Prose.tsx +1 -1
  134. package/src/components/ReferenceOrContent.tsx +1 -1
  135. package/src/components/Scope.tsx +2 -6
  136. package/src/components/Show.tsx +1 -1
  137. package/src/components/StatementList.tsx +2 -1
  138. package/src/components/Switch.tsx +1 -1
  139. package/src/components/Wrap.tsx +1 -1
  140. package/src/context/assignment.ts +1 -1
  141. package/src/context/binder.ts +2 -2
  142. package/src/context/declaration.ts +1 -1
  143. package/src/context/member-declaration.ts +1 -1
  144. package/src/context/member-scope.ts +1 -1
  145. package/src/context/name-policy.ts +1 -1
  146. package/src/context/scope.ts +1 -1
  147. package/src/context/source-directory.ts +1 -1
  148. package/src/context/source-file.ts +2 -2
  149. package/src/index.ts +11 -0
  150. package/src/jsx-runtime.ts +18 -2
  151. package/src/reactive-union-set.ts +238 -0
  152. package/src/refkey.ts +40 -0
  153. package/src/scheduler.ts +31 -6
  154. package/src/slot.ts +1 -1
  155. package/src/stc.ts +3 -3
  156. package/src/sti.ts +5 -5
  157. package/src/symbols/index.ts +5 -0
  158. package/src/symbols/output-scope.ts +323 -0
  159. package/src/symbols/output-symbol.ts +512 -0
  160. package/src/symbols/symbol-flow.ts +104 -0
  161. package/src/symbols/symbol-slot.tsx +47 -0
  162. package/src/symbols/symbol-table.ts +72 -0
  163. package/src/tap.ts +2 -1
  164. package/src/tracer.ts +440 -0
  165. package/temp/api.json +4172 -1582
  166. package/test/components/slot.test.tsx +8 -11
  167. package/test/reactive-union-set.test.tsx +191 -0
  168. package/test/symbols/output-scope.test.ts +302 -0
  169. package/test/symbols/output-symbol.test.ts +459 -0
  170. package/test/symbols/resolution.test.ts +172 -0
  171. package/test/symbols/utils.ts +95 -0
  172. package/dist/test/symbols.test.d.ts +0 -2
  173. package/dist/test/symbols.test.d.ts.map +0 -1
  174. package/dist/test/symbols.test.js +0 -884
  175. package/test/symbols.test.ts +0 -1006
@@ -0,0 +1,72 @@
1
+ import { NameConflictResolver } from "../binder.js";
2
+ import { ReactiveUnionSet } from "../reactive-union-set.js";
3
+ import { queueJob } from "../scheduler.js";
4
+ import {
5
+ formatScopeName,
6
+ formatSymbolName,
7
+ trace,
8
+ TracePhase,
9
+ } from "../tracer.js";
10
+ import { OutputScope } from "./output-scope.js";
11
+ import { OutputSymbol } from "./output-symbol.js";
12
+
13
+ export class SymbolTable extends ReactiveUnionSet<OutputSymbol> {
14
+ private _namesToDeconflict: Set<string> = new Set();
15
+ private _nameConflictResolver?: NameConflictResolver;
16
+ private _deconflictNames = () => {
17
+ for (const name of this._namesToDeconflict) {
18
+ const conflictedSymbols = [...this].filter(
19
+ (sym) => sym.originalName === name,
20
+ );
21
+ if (this._nameConflictResolver) {
22
+ this._nameConflictResolver(name, conflictedSymbols);
23
+ } else {
24
+ defaultConflictHandler(name, conflictedSymbols);
25
+ }
26
+ this._namesToDeconflict.delete(name);
27
+ }
28
+ };
29
+ public scope;
30
+ constructor(
31
+ scope: OutputScope,
32
+ options: {
33
+ nameConflictResolver?: NameConflictResolver;
34
+ } = {},
35
+ ) {
36
+ super({
37
+ onAdd: (symbol) => {
38
+ trace(
39
+ TracePhase.symbol.addToScope,
40
+ () => `${formatSymbolName(symbol)} -> ${formatScopeName(scope)}`,
41
+ );
42
+
43
+ this._namesToDeconflict.add(symbol.name);
44
+
45
+ queueJob(this._deconflictNames);
46
+
47
+ return symbol;
48
+ },
49
+ onDelete(symbol) {
50
+ trace(
51
+ TracePhase.symbol.removeFromScope,
52
+ () => `${formatSymbolName(symbol)} -> ${formatScopeName(scope)}`,
53
+ );
54
+ },
55
+ });
56
+
57
+ this.scope = scope;
58
+
59
+ this._nameConflictResolver = options.nameConflictResolver;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Default conflict handler. This will rename all but the first symbol
65
+ * to have a suffix of _2, _3, etc.
66
+ */
67
+ function defaultConflictHandler(_: string, conflictedSymbols: OutputSymbol[]) {
68
+ for (let i = 1; i < conflictedSymbols.length; i++) {
69
+ conflictedSymbols[i].name =
70
+ conflictedSymbols[i].originalName + "_" + (i + 1);
71
+ }
72
+ }
package/src/tap.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { ShallowRef, shallowRef } from "@vue/reactivity";
2
- import { OutputScope, OutputSymbol } from "./binder.js";
3
2
  import { useContext } from "./context.js";
4
3
  import { DeclarationContext } from "./context/declaration.js";
5
4
  import { MemberDeclarationContext } from "./context/member-declaration.js";
6
5
  import { useScope } from "./context/scope.js";
7
6
  import { SourceFileContext } from "./context/source-file.js";
8
7
  import { ComponentDefinition } from "./jsx-runtime.js";
8
+ import { OutputScope } from "./symbols/output-scope.js";
9
+ import { OutputSymbol } from "./symbols/output-symbol.js";
9
10
 
10
11
  /**
11
12
  * The return value of {@link createTap}, this holds a reference to the tapped
package/src/tracer.ts ADDED
@@ -0,0 +1,440 @@
1
+ import { untrack } from "@alloy-js/core/jsx-runtime";
2
+ import { effect, ReactiveEffectRunner } from "@vue/reactivity";
3
+ import type { Refkey } from "./refkey.js";
4
+ import { scheduler } from "./scheduler.js";
5
+ import { OutputScope, OutputScopeFlags } from "./symbols/output-scope.js";
6
+ import { OutputSymbol, OutputSymbolFlags } from "./symbols/output-symbol.js";
7
+
8
+ // enable tracing for specific phases using a comma separated list of
9
+ // dotted identifiers, e.g. `scope.update,symbol.create`.
10
+ const traceEnv = process.env.ALLOY_TRACE ?? "";
11
+ const tracePhases = new Set<string>(
12
+ traceEnv === "" ? [] : traceEnv.split(",").map((t) => t.trim()),
13
+ );
14
+
15
+ if (tracePhases.size > 0) {
16
+ // eslint-disable-next-line no-console
17
+ console.log(
18
+ "Tracing enabled for phases:",
19
+ Array.from(tracePhases).join(", "),
20
+ );
21
+ }
22
+
23
+ const debuggerIdsEnv = process.env.ALLOY_BREAK_ON_DID ?? "";
24
+ const dids = new Set<number>();
25
+
26
+ debuggerIdsEnv.split(",").forEach((id) => {
27
+ const num = parseInt(id, 10);
28
+ if (!isNaN(num)) {
29
+ dids.add(num);
30
+ }
31
+ });
32
+ export const TracePhase = {
33
+ render: {},
34
+ scope: {
35
+ update: {
36
+ area: "scope",
37
+ subarea: "update",
38
+ bg: { r: 0, g: 255, b: 100 },
39
+ },
40
+ create: {
41
+ area: "scope",
42
+ subarea: "create",
43
+ bg: { r: 0, g: 150, b: 100 },
44
+ },
45
+ copySymbols: {
46
+ area: "scope",
47
+ subarea: "copySymbols",
48
+ bg: { r: 0, g: 100, b: 100 },
49
+ },
50
+ },
51
+ symbol: {
52
+ update: {
53
+ area: "symbol",
54
+ subarea: "update",
55
+ bg: { r: 0, g: 0, b: 255 },
56
+ },
57
+ resolve: {
58
+ area: "symbol",
59
+ subarea: "resolve",
60
+ bg: { r: 0, g: 0, b: 200 },
61
+ },
62
+ create: {
63
+ area: "symbol",
64
+ subarea: "create",
65
+ bg: { r: 0, g: 0, b: 150 },
66
+ },
67
+ flow: {
68
+ area: "symbol",
69
+ subarea: "flow",
70
+ bg: { r: 0, g: 0, b: 100 },
71
+ },
72
+ addToScope: {
73
+ area: "symbol",
74
+ subarea: "addToScope",
75
+ bg: { r: 0, g: 0, b: 50 },
76
+ },
77
+ instantiate: {
78
+ area: "symbol",
79
+ subarea: "instantiate",
80
+ bg: { r: 0, g: 0, b: 25 },
81
+ },
82
+ clone: {
83
+ area: "symbol",
84
+ subarea: "clone",
85
+ bg: { r: 0, g: 0, b: 25 },
86
+ },
87
+ delete: {
88
+ area: "symbol",
89
+ subarea: "delete",
90
+ bg: { r: 100, g: 0, b: 100 },
91
+ },
92
+ removeFromScope: {
93
+ area: "symbol",
94
+ subarea: "removeFromScope",
95
+ bg: { r: 50, g: 0, b: 50 },
96
+ },
97
+ },
98
+ resolve: {
99
+ success: {
100
+ area: "resolve",
101
+ subarea: "success",
102
+ bg: { r: 0, g: 255, b: 0 },
103
+ },
104
+ pending: {
105
+ area: "resolve",
106
+ subarea: "pending",
107
+ bg: { r: 255, g: 255, b: 0 },
108
+ },
109
+ failure: {
110
+ area: "resolve",
111
+ subarea: "failure",
112
+ bg: { r: 100, g: 50, b: 50 },
113
+ },
114
+ },
115
+ } as const;
116
+
117
+ interface TracePhase extends TextFormat {
118
+ area: string;
119
+ subarea: string;
120
+ }
121
+
122
+ let triggerCount = 0;
123
+
124
+ function shouldTrace(phase: TracePhase) {
125
+ return (
126
+ tracePhases.has(phase.area) ||
127
+ tracePhases.has(phase.area + "." + phase.subarea)
128
+ );
129
+ }
130
+ export function trace(
131
+ phase: TracePhase,
132
+ cb: () => string,
133
+ triggerIds: Set<number> = new Set(),
134
+ ) {
135
+ if (!shouldTrace(phase)) {
136
+ return;
137
+ }
138
+ if (triggerIds.size === 0) {
139
+ // not an effect trace.
140
+ const id = triggerCount++;
141
+ triggerIds.add(id);
142
+
143
+ if (dids.has(id)) {
144
+ // eslint-disable-next-line no-debugger
145
+ debugger;
146
+ }
147
+ }
148
+
149
+ const areaTag = ` ${phase.area + ":" + phase.subarea} `;
150
+ // eslint-disable-next-line no-console
151
+ console.log(
152
+ colorText(areaTag, { ...phase, bold: true }) +
153
+ " " +
154
+ colorText("[" + [...triggerIds].join(",") + "]", {
155
+ fg: { r: 50, g: 50, b: 50 },
156
+ }) +
157
+ " " +
158
+ cb() +
159
+ "\n",
160
+ );
161
+ }
162
+
163
+ export function traceEffect(phase: TracePhase, cb: () => string) {
164
+ if (!shouldTrace(phase)) {
165
+ return;
166
+ }
167
+ let first = true;
168
+ const triggerIds = new Set<number>();
169
+
170
+ const runner: ReactiveEffectRunner<void> = effect(
171
+ () => {
172
+ if (first) {
173
+ // just track what we need, don't log.
174
+ cb();
175
+ first = false;
176
+ return;
177
+ }
178
+ trace(phase, cb, triggerIds);
179
+ triggerIds.clear();
180
+ },
181
+ {
182
+ scheduler: scheduler(() => runner, true),
183
+ onTrigger(event) {
184
+ const id = triggerCount++;
185
+ if (dids.has(id)) {
186
+ // eslint-disable-next-line no-debugger
187
+ debugger;
188
+ }
189
+ triggerIds.add(id);
190
+ },
191
+ },
192
+ );
193
+ }
194
+
195
+ interface Color {
196
+ r: number;
197
+ g: number;
198
+ b: number;
199
+ }
200
+
201
+ /** Descriptor for how to format the text */
202
+ interface TextFormat {
203
+ fg?: Color; // optional foreground color
204
+ bg?: Color; // optional background color
205
+ bold?: boolean; // optional bold flag
206
+ }
207
+
208
+ /**
209
+ * Wraps `text` in ANSI escape codes according to the given format.
210
+ *
211
+ * @param text The string to format.
212
+ * @param fmt Optional formatting descriptor.
213
+ * @returns The text wrapped in ANSI codes (or unmodified if no styles given).
214
+ */
215
+ function colorText(text: string, fmt?: TextFormat): string {
216
+ if (!fmt) return text;
217
+
218
+ const codes: string[] = [];
219
+
220
+ if (fmt.bold) {
221
+ codes.push("1"); // ANSI code for bold
222
+ }
223
+
224
+ if (fmt.fg) {
225
+ const { r, g, b } = fmt.fg;
226
+ codes.push(`38;2;${r};${g};${b}`);
227
+ }
228
+
229
+ if (fmt.bg) {
230
+ const { r, g, b } = fmt.bg;
231
+ codes.push(`48;2;${r};${g};${b}`);
232
+ }
233
+
234
+ if (codes.length === 0) {
235
+ return text;
236
+ }
237
+
238
+ const prefix = `\x1b[${codes.join(";")}m`;
239
+ const reset = `\x1b[0m`;
240
+ return `${prefix}${text}${reset}`;
241
+ }
242
+
243
+ /**
244
+ * Format flag values in a concise way, showing only the flags that are set
245
+ * @param flags The numeric flags value to format
246
+ * @param flagEnum The enum containing flag definitions
247
+ * @returns An array of flag names that are set
248
+ */
249
+ function formatFlags<T extends Record<string, string | number>>(
250
+ flags: number,
251
+ flagEnum: T,
252
+ ): string[] {
253
+ return Object.entries(flagEnum)
254
+ .filter(
255
+ ([name, value]) =>
256
+ typeof value === "number" && value !== 0 && (flags & value) === value,
257
+ )
258
+ .map(([name]) => name);
259
+ }
260
+
261
+ /**
262
+ * Format a symbol name with its ID in a blue-green color
263
+ * @param symbol The symbol to format
264
+ * @returns A formatted string representation of the symbol name with ID
265
+ */
266
+ export function formatSymbolName(symbol: OutputSymbol): string {
267
+ return colorText(`Symbol ${symbol.name}[${symbol.id}]`, {
268
+ fg: {
269
+ r: 0,
270
+ b: 150,
271
+ g: 100,
272
+ },
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Format the symbols in a member scope, showing their names and IDs
278
+ * @param scope The member scope containing the symbols to format
279
+ * @returns A formatted string representation of the member scope symbols
280
+ */
281
+ function formatMemberScopeSymbols(scope: OutputScope): string {
282
+ if (!scope || scope.symbols.size === 0) {
283
+ return "none";
284
+ }
285
+
286
+ return Array.from(scope.symbols)
287
+ .map((symbol) => formatSymbolName(symbol))
288
+ .join(", ");
289
+ }
290
+
291
+ export function formatSymbol(symbol: OutputSymbol): string {
292
+ // Base display with name and ID
293
+ let result = formatSymbolName(symbol);
294
+
295
+ // Add details on subsequent lines
296
+ const details: string[] = [];
297
+
298
+ if (!symbol.binder) {
299
+ details.push(colorText(" !UNBOUND", { fg: { r: 255, g: 0, b: 0 } }));
300
+ }
301
+
302
+ // Show only enabled flags
303
+ const flagsInfo = formatFlags(symbol.flags, OutputSymbolFlags);
304
+
305
+ if (flagsInfo.length > 0) {
306
+ details.push(` flags: ${flagsInfo.join(", ")}`);
307
+ }
308
+
309
+ // Show scope info with formatted name
310
+ if (symbol.scope) {
311
+ details.push(untrack(() => ` scope: ${formatScopeName(symbol.scope)}`));
312
+ }
313
+
314
+ // Show member scopes if present
315
+ if (symbol.instanceMemberScope) {
316
+ untrack(() => {
317
+ const memberCount = symbol.instanceMemberScope!.symbols.size;
318
+ details.push(
319
+ ` instance members: ${memberCount} - ${formatMemberScopeSymbols(symbol.instanceMemberScope!)}`,
320
+ );
321
+ });
322
+ }
323
+
324
+ if (symbol.staticMemberScope) {
325
+ untrack(() => {
326
+ const memberCount = symbol.staticMemberScope!.symbols.size;
327
+ details.push(
328
+ ` static members: ${memberCount} - ${formatMemberScopeSymbols(symbol.staticMemberScope!)}`,
329
+ );
330
+ });
331
+ }
332
+
333
+ // Show all refkeys with proper formatting
334
+ if (symbol.refkeys && symbol.refkeys.length > 0) {
335
+ details.push(` refkeys: ${formatRefkeys(symbol.refkeys)}`);
336
+ }
337
+
338
+ if (details.length > 0) {
339
+ result += "\n" + details.join("\n");
340
+ }
341
+
342
+ return result;
343
+ }
344
+
345
+ export function formatScopeName(scope: OutputScope): string {
346
+ let text = colorText(`${scope.name}[${scope.id}]`, {
347
+ fg: {
348
+ r: 0,
349
+ g: 150,
350
+ b: 50,
351
+ },
352
+ });
353
+
354
+ if (scope.owner) {
355
+ text += untrack(() => ` of ${formatSymbolName(scope.owner!)}`);
356
+ }
357
+
358
+ return text;
359
+ }
360
+
361
+ /**
362
+ * Format an OutputScope for display, showing information in a concise format
363
+ * @param scope The scope to format
364
+ * @returns A formatted string representation of the scope
365
+ */
366
+ export function formatScope(scope: OutputSymbol["scope"]): string {
367
+ if (!scope) {
368
+ return "!Undefined scope!";
369
+ }
370
+ // Base display with name
371
+ let result = colorText(`Scope ${formatScopeName(scope)}`, {
372
+ fg: {
373
+ r: 0,
374
+ b: 200,
375
+ g: 100,
376
+ },
377
+ });
378
+
379
+ // Add details on subsequent lines
380
+ const details: string[] = [];
381
+
382
+ if (!scope.binder) {
383
+ details.push(colorText(" !UNBOUND", { fg: { r: 255, g: 0, b: 0 } }));
384
+ }
385
+ // Show only enabled flags
386
+ const flagsInfo = formatFlags(scope.flags, OutputScopeFlags);
387
+
388
+ if (flagsInfo.length > 0) {
389
+ details.push(` flags: ${flagsInfo.join(", ")}`);
390
+ }
391
+
392
+ // Show symbol count
393
+ const symbolCount = scope.symbols.size;
394
+ if (symbolCount > 0) {
395
+ details.push(` symbols: ${symbolCount}`);
396
+ }
397
+
398
+ // Show parent scope if present
399
+ if (scope.parent) {
400
+ details.push(` parent: ${formatScopeName(scope.parent)}`);
401
+ }
402
+
403
+ // Show owner if present (for member scopes)
404
+ if (scope.owner) {
405
+ details.push(` owner: ${formatSymbolName(scope.owner)}`);
406
+ }
407
+
408
+ // Show child scopes if present
409
+ if (scope.children && scope.children.size > 0) {
410
+ details.push(` children: ${scope.children.size}`);
411
+ }
412
+
413
+ if (details.length > 0) {
414
+ result += "\n" + details.join("\n");
415
+ }
416
+
417
+ return result;
418
+ }
419
+
420
+ export function formatRefkeys(refkeys: Refkey[] | Refkey | undefined) {
421
+ if (!refkeys) {
422
+ return "";
423
+ }
424
+
425
+ if (Array.isArray(refkeys)) {
426
+ return refkeys.map(formatRefkey).join(", ");
427
+ }
428
+
429
+ return formatRefkey(refkeys);
430
+ }
431
+
432
+ function formatRefkey(refkey: Refkey): string {
433
+ return colorText(`refkey[${refkey.key}]`, {
434
+ fg: {
435
+ r: 150,
436
+ g: 0,
437
+ b: 0,
438
+ },
439
+ });
440
+ }