@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
@@ -4,7 +4,6 @@ import { SourceFile } from "../../src/components/SourceFile.jsx";
4
4
  import {
5
5
  Declaration,
6
6
  Name,
7
- OutputSymbol,
8
7
  Ref,
9
8
  refkey,
10
9
  Scope,
@@ -12,6 +11,7 @@ import {
12
11
  } from "../../src/index.js";
13
12
  import { render } from "../../src/render.js";
14
13
  import { defineSlot, rename, replace } from "../../src/slot.js";
14
+ import { OutputSymbol } from "../../src/symbols/output-symbol.js";
15
15
  import "../../testing/extend-expect.js";
16
16
 
17
17
  it("works with string keys", () => {
@@ -70,7 +70,7 @@ it("works with symbols", () => {
70
70
  const FunctionSlot = defineSlot<FunctionSlotProps>(
71
71
  (query: { fqn: string }) => {
72
72
  const binder = useBinder();
73
- return binder.resolveFQN(query.fqn);
73
+ return binder!.resolveFQN(query.fqn);
74
74
  },
75
75
  );
76
76
 
@@ -80,9 +80,8 @@ it("works with symbols", () => {
80
80
 
81
81
  function MyFunctionComponent(props: FunctionComponentProps) {
82
82
  const binder = useBinder();
83
- const sym = binder.createSymbol({
84
- name: props.name,
85
- refkey: refkey(),
83
+ const sym = new OutputSymbol(props.name, {
84
+ refkeys: refkey(),
86
85
  });
87
86
 
88
87
  const FunctionSlotInstance = FunctionSlot.create(
@@ -129,7 +128,7 @@ it("can rename", () => {
129
128
 
130
129
  const FunctionSlot = defineSlot<FunctionSlotProps>(
131
130
  (query: { fqn: string }) => {
132
- const binder = useBinder();
131
+ const binder = useBinder()!;
133
132
  return binder.resolveFQN(query.fqn);
134
133
  },
135
134
  );
@@ -139,10 +138,8 @@ it("can rename", () => {
139
138
  }
140
139
 
141
140
  function MyFunctionComponent(props: FunctionComponentProps) {
142
- const binder = useBinder();
143
- const sym = binder.createSymbol({
144
- name: props.name,
145
- refkey: refkey(),
141
+ const sym = new OutputSymbol(props.name, {
142
+ refkeys: refkey(),
146
143
  });
147
144
 
148
145
  const FunctionSlotInstance = FunctionSlot.create(
@@ -161,7 +158,7 @@ it("can rename", () => {
161
158
 
162
159
  rename(() => {
163
160
  const binder = useBinder();
164
- return binder.resolveFQN("foo.bar") as Ref<OutputSymbol | undefined>;
161
+ return binder!.resolveFQN("foo.bar") as Ref<OutputSymbol | undefined>;
165
162
  }, "bazxxx");
166
163
 
167
164
  const tree = render(
@@ -0,0 +1,191 @@
1
+ import { effect } from "@alloy-js/core/jsx-runtime";
2
+ import { reactive } from "@vue/reactivity";
3
+ import { describe, expect, it } from "vitest";
4
+ import { ReactiveUnionSet } from "../src/reactive-union-set.js";
5
+ import { flushJobs } from "../src/scheduler.js";
6
+
7
+ describe("is reactive", () => {
8
+ it("on size", () => {
9
+ const set = new ReactiveUnionSet();
10
+ let callCount = 0;
11
+ effect(() => {
12
+ callCount++;
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
14
+ set.size;
15
+ });
16
+
17
+ set.add(1);
18
+ flushJobs();
19
+ expect(callCount).toBe(2);
20
+ });
21
+
22
+ it("on iteration", () => {
23
+ const set = new ReactiveUnionSet();
24
+ let callCount = 0;
25
+ effect(() => {
26
+ callCount++;
27
+ for (const _ of set) {
28
+ // do nothing
29
+ }
30
+ });
31
+
32
+ set.add(1);
33
+ flushJobs();
34
+ expect(callCount).toBe(2);
35
+ });
36
+
37
+ it("on deletion with iteration", () => {
38
+ const set = new ReactiveUnionSet();
39
+ let callCount = 0;
40
+ effect(() => {
41
+ callCount++;
42
+ for (const _ of set) {
43
+ // do nothing
44
+ }
45
+ });
46
+
47
+ set.add(1);
48
+ flushJobs();
49
+ expect(callCount).toBe(2);
50
+
51
+ set.delete(1);
52
+ flushJobs();
53
+ expect(callCount).toBe(3);
54
+ });
55
+ });
56
+
57
+ it("adds items to main set from subset", () => {
58
+ const set = new ReactiveUnionSet();
59
+
60
+ const subset1 = reactive(new Set<number>());
61
+ const subset2 = reactive(new Set<number>());
62
+
63
+ set.addSubset(subset1);
64
+ set.addSubset(subset2);
65
+
66
+ subset1.add(1);
67
+ subset2.add(2);
68
+ flushJobs();
69
+ expect(set.has(1)).toBe(true);
70
+ expect(set.has(2)).toBe(true);
71
+
72
+ subset1.delete(1);
73
+ flushJobs();
74
+ expect(set.has(1)).toBe(false);
75
+ });
76
+
77
+ it("doesn't remove items that are present in more than one subset", () => {
78
+ const set = new ReactiveUnionSet();
79
+
80
+ const subset1 = reactive(new Set<number>());
81
+ const subset2 = reactive(new Set<number>());
82
+
83
+ set.addSubset(subset1);
84
+ set.addSubset(subset2);
85
+
86
+ subset1.add(1);
87
+ subset2.add(1);
88
+ flushJobs();
89
+ expect(set.has(1)).toBe(true);
90
+
91
+ subset1.delete(1);
92
+ flushJobs();
93
+ expect(set.has(1)).toBe(true);
94
+
95
+ subset2.delete(1);
96
+ flushJobs();
97
+ expect(set.has(1)).toBe(false);
98
+ });
99
+
100
+ it("removes all items when you clear the subset", () => {
101
+ const set = new ReactiveUnionSet();
102
+
103
+ const subset1 = reactive(new Set<number>());
104
+ const subset2 = reactive(new Set<number>());
105
+
106
+ set.addSubset(subset1);
107
+ set.addSubset(subset2);
108
+ subset1.add(1);
109
+ subset1.add(2);
110
+ subset1.add(3);
111
+ subset2.add(1);
112
+ flushJobs();
113
+ expect(set.size).toBe(3);
114
+ subset1.clear();
115
+ flushJobs();
116
+ expect(set.size).toBe(1);
117
+ });
118
+
119
+ interface Item {
120
+ id: number;
121
+ name: string;
122
+ }
123
+
124
+ describe("indexing", () => {
125
+ it("creates an index of properties", () => {
126
+ const set = new ReactiveUnionSet<Item>();
127
+
128
+ const index = set.createIndex((item) => item.id);
129
+
130
+ let callCount = 0;
131
+ effect(() => {
132
+ callCount++;
133
+ index.values();
134
+ });
135
+
136
+ const brian = { id: 1, name: "brian" };
137
+ const timothee = { id: 2, name: "timothee" };
138
+ set.add(brian);
139
+ set.add(timothee);
140
+ flushJobs();
141
+ expect(callCount).toBe(2);
142
+ expect(index.get(1)).toEqual(brian);
143
+ expect(index.get(2)).toEqual(timothee);
144
+ set.delete(brian);
145
+ flushJobs();
146
+ expect(callCount).toBe(3);
147
+ expect(index.get(1)).toBeUndefined();
148
+ });
149
+
150
+ it("updates the index when the item changeS", () => {
151
+ const set = new ReactiveUnionSet<Item>();
152
+
153
+ const index = set.createIndex((item) => item.id);
154
+
155
+ let callCount = 0;
156
+ effect(() => {
157
+ callCount++;
158
+ index.values();
159
+ });
160
+
161
+ const brian = reactive({ id: 1, name: "brian" });
162
+ set.add(brian);
163
+ flushJobs();
164
+ expect(index.get(1)).toEqual(brian);
165
+ brian.id = 2;
166
+ flushJobs();
167
+ expect(callCount).toBe(3);
168
+ expect(index.get(2)).toEqual(brian);
169
+ });
170
+ });
171
+
172
+ describe("derived sets", () => {
173
+ it("creates a derived set", () => {
174
+ const set = new ReactiveUnionSet<Item>();
175
+ const derivedSet = set.createDerivedSet((item) => item.name);
176
+ const brian = { id: 1, name: "brian" };
177
+ const timothee = { id: 2, name: "timothee" };
178
+ set.add(brian);
179
+ set.add(timothee);
180
+ flushJobs();
181
+ expect(derivedSet.size).toBe(2);
182
+ expect(derivedSet.has("brian")).toBe(true);
183
+ expect(derivedSet.has("timothee")).toBe(true);
184
+
185
+ set.delete(brian);
186
+ flushJobs();
187
+ expect(derivedSet.size).toBe(1);
188
+ expect(derivedSet.has("brian")).toBe(false);
189
+ expect(derivedSet.has("timothee")).toBe(true);
190
+ });
191
+ });
@@ -0,0 +1,302 @@
1
+ import { reactive, watch } from "@vue/reactivity";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { Binder, createOutputBinder } from "../../src/binder.js";
4
+ import { Refkey } from "../../src/refkey.js";
5
+ import { flushJobs } from "../../src/scheduler.js";
6
+ import {
7
+ OutputScope,
8
+ OutputScopeFlags,
9
+ } from "../../src/symbols/output-scope.js";
10
+ import { OutputSymbol } from "../../src/symbols/output-symbol.js";
11
+ import { SymbolTable } from "../../src/symbols/symbol-table.js";
12
+
13
+ let binder: Binder;
14
+ beforeEach(() => {
15
+ binder = createOutputBinder();
16
+ });
17
+
18
+ describe("OutputScope constructor", () => {
19
+ it("initializes properties correctly with default options", () => {
20
+ const scope = new OutputScope("testScope", { binder });
21
+ expect(scope.name).toBe("testScope");
22
+ expect(scope.binder).toBe(binder);
23
+ expect(scope.id).toEqual(expect.any(Number));
24
+ expect(scope.kind).toBe("scope");
25
+ expect(scope.flags).toBe(OutputScopeFlags.None);
26
+ expect(scope.metadata).toEqual({});
27
+ expect(scope.parent).toBe(binder.globalScope);
28
+ expect(scope.owner).toBeUndefined();
29
+ expect(scope.symbols).toBeInstanceOf(SymbolTable);
30
+ expect(scope.symbolNames.size).toBe(0);
31
+ expect(scope.children.size).toBe(0);
32
+ });
33
+
34
+ it("initializes properties correctly with custom options", () => {
35
+ const parentScope = new OutputScope("parentScope", { binder });
36
+ const metadata = { foo: "bar" };
37
+
38
+ const scope = new OutputScope("testScope", {
39
+ binder,
40
+ kind: "namespace",
41
+ metadata,
42
+ parent: parentScope,
43
+ });
44
+
45
+ expect(scope.name).toBe("testScope");
46
+ expect(scope.kind).toBe("namespace");
47
+ expect(scope.flags).toBe(OutputScopeFlags.None);
48
+ expect(scope.metadata.foo).toBe("bar");
49
+ expect(scope.parent).toBe(parentScope);
50
+ expect(parentScope.children.has(scope)).toBe(true);
51
+ });
52
+ });
53
+
54
+ describe("OutputScope reactivity", () => {
55
+ it("is reactive on name", () => {
56
+ const scope = new OutputScope("initialName", { binder });
57
+ const nameSpy = vi.fn();
58
+ watch(() => scope.name, nameSpy);
59
+
60
+ scope.name = "newName";
61
+ flushJobs();
62
+ expect(nameSpy).toHaveBeenCalled();
63
+ expect(scope.name).toBe("newName");
64
+ });
65
+
66
+ it("is reactive on flags", () => {
67
+ const scope = new OutputScope("scope", { binder });
68
+ const flagsSpy = vi.fn();
69
+ watch(() => scope.flags, flagsSpy);
70
+
71
+ scope.flags = OutputScopeFlags.InstanceMemberScope;
72
+ flushJobs();
73
+ expect(flagsSpy).toHaveBeenCalled();
74
+ expect(scope.flags).toBe(OutputScopeFlags.InstanceMemberScope);
75
+ });
76
+
77
+ it("updates symbolNames when symbols are added", () => {
78
+ const scope = new OutputScope("scope", { binder });
79
+ const symbolNamesSpy = vi.fn();
80
+ watch(() => scope.symbolNames.size, symbolNamesSpy);
81
+
82
+ new OutputSymbol("symbol1", { binder, scope });
83
+ flushJobs();
84
+ expect(symbolNamesSpy).toHaveBeenCalled();
85
+ expect(scope.symbolNames.has("symbol1")).toBe(true);
86
+ });
87
+
88
+ it("updates symbolNames when a symbol's name changes", () => {
89
+ const scope = new OutputScope("scope", { binder });
90
+ const symbol = new OutputSymbol("oldName", { binder, scope });
91
+ flushJobs();
92
+
93
+ // Verify initial state
94
+ expect(scope.symbolNames.has("oldName")).toBe(true);
95
+ expect(scope.symbolNames.has("newName")).toBe(false);
96
+
97
+ // Set up tracking for symbol names
98
+ const symbolNamesTracker = vi.fn();
99
+ watch(
100
+ () => ({
101
+ hasOld: scope.symbolNames.has("oldName"),
102
+ hasNew: scope.symbolNames.has("newName"),
103
+ }),
104
+ symbolNamesTracker,
105
+ { deep: true },
106
+ );
107
+
108
+ // Change the name
109
+ symbol.name = "newName";
110
+ flushJobs();
111
+
112
+ // Verify the changes
113
+ expect(symbolNamesTracker).toHaveBeenCalled();
114
+ expect(scope.symbolNames.has("oldName")).toBe(false);
115
+ expect(scope.symbolNames.has("newName")).toBe(true);
116
+ });
117
+
118
+ it("doesn't get wrapped in a reactive proxy", () => {
119
+ const scope = new OutputScope("scope", { binder });
120
+
121
+ const rScope = reactive(scope);
122
+ expect(rScope).toBe(scope);
123
+ });
124
+ });
125
+
126
+ describe("OutputScope#symbols", () => {
127
+ it("adds symbols to its collection", () => {
128
+ const scope = new OutputScope("scope", { binder });
129
+ const sym1 = new OutputSymbol("sym1", { binder, scope });
130
+ const sym2 = new OutputSymbol("sym2", { binder, scope });
131
+ flushJobs();
132
+
133
+ expect(scope.symbols.size).toBe(2);
134
+ expect(scope.symbols.has(sym1)).toBe(true);
135
+ expect(scope.symbols.has(sym2)).toBe(true);
136
+ });
137
+
138
+ it("resolves symbol name conflicts", () => {
139
+ const scope = new OutputScope("scope", { binder });
140
+ const s1 = new OutputSymbol("sym", { binder, scope });
141
+ const s2 = new OutputSymbol("sym", { binder, scope });
142
+ const s3 = new OutputSymbol("sym", { binder, scope });
143
+ flushJobs();
144
+
145
+ expect(s1.name).toBe("sym");
146
+ expect(s2.name).toBe("sym_2");
147
+ expect(s3.name).toBe("sym_3");
148
+ expect(scope.symbolNames.has("sym")).toBe(true);
149
+ expect(scope.symbolNames.has("sym_2")).toBe(true);
150
+ expect(scope.symbolNames.has("sym_3")).toBe(true);
151
+ });
152
+
153
+ it("updates when a symbol is deleted", () => {
154
+ const scope = new OutputScope("scope", { binder });
155
+ const sym = new OutputSymbol("sym", { binder, scope });
156
+ flushJobs();
157
+
158
+ expect(scope.symbols.size).toBe(1);
159
+ expect(scope.symbols.has(sym)).toBe(true);
160
+ expect(scope.symbolNames.has("sym")).toBe(true);
161
+
162
+ sym.delete();
163
+ flushJobs();
164
+
165
+ expect(scope.symbols.size).toBe(0);
166
+ expect(scope.symbols.has(sym)).toBe(false);
167
+ expect(scope.symbolNames.has("sym")).toBe(false);
168
+ });
169
+
170
+ it("updates when a symbol changes scope", () => {
171
+ const scope1 = new OutputScope("scope1", { binder });
172
+ const scope2 = new OutputScope("scope2", { binder });
173
+ const sym = new OutputSymbol("sym", { binder, scope: scope1 });
174
+ flushJobs();
175
+
176
+ expect(scope1.symbols.size).toBe(1);
177
+ expect(scope2.symbols.size).toBe(0);
178
+
179
+ sym.scope = scope2;
180
+ flushJobs();
181
+
182
+ expect(scope1.symbols.size).toBe(0);
183
+ expect(scope2.symbols.size).toBe(1);
184
+ expect(scope1.symbols.has(sym)).toBe(false);
185
+ expect(scope2.symbols.has(sym)).toBe(true);
186
+ expect(scope1.symbolNames.has("sym")).toBe(false);
187
+ expect(scope2.symbolNames.has("sym")).toBe(true);
188
+ });
189
+ });
190
+
191
+ describe("OutputScope#symbolsByRefkey", () => {
192
+ it("maps refkeys to symbols", () => {
193
+ const scope = new OutputScope("scope", { binder });
194
+
195
+ // Use the refkey function to create refkeys
196
+ // This is based on how refkey is being imported in binder.ts
197
+ const key1 = "key1" as unknown as Refkey;
198
+ const key2a = "key2a" as unknown as Refkey;
199
+ const key2b = "key2b" as unknown as Refkey;
200
+
201
+ // Create a symbol with a refkey
202
+ const sym1 = new OutputSymbol("sym1", {
203
+ binder,
204
+ scope,
205
+ refkeys: [key1],
206
+ });
207
+
208
+ // Create a symbol with multiple refkeys
209
+ const sym2 = new OutputSymbol("sym2", {
210
+ binder,
211
+ scope,
212
+ refkeys: [key2a, key2b],
213
+ });
214
+
215
+ flushJobs();
216
+
217
+ expect(scope.symbolsByRefkey.get(key1)).toBe(sym1);
218
+ expect(scope.symbolsByRefkey.get(key2a)).toBe(sym2);
219
+ expect(scope.symbolsByRefkey.get(key2b)).toBe(sym2);
220
+ });
221
+ });
222
+
223
+ describe("OutputScope#children", () => {
224
+ it("tracks child scopes", () => {
225
+ const parentScope = new OutputScope("parent", { binder });
226
+ const child1 = new OutputScope("child1", { binder, parent: parentScope });
227
+ const child2 = new OutputScope("child2", { binder, parent: parentScope });
228
+ flushJobs();
229
+
230
+ expect(parentScope.children.size).toBe(2);
231
+ expect(parentScope.children.has(child1)).toBe(true);
232
+ expect(parentScope.children.has(child2)).toBe(true);
233
+
234
+ // Check that each child's parent is set correctly
235
+ expect(child1.parent).toBe(parentScope);
236
+ expect(child2.parent).toBe(parentScope);
237
+ });
238
+ });
239
+
240
+ describe("OutputScope#clone", () => {
241
+ let originalScope: OutputScope;
242
+ const originalMetadata = { data: "original", nested: { value: 1 } };
243
+
244
+ beforeEach(() => {
245
+ const parentScope = new OutputScope("parent", { binder });
246
+
247
+ originalScope = new OutputScope("original", {
248
+ binder: binder,
249
+ kind: "class",
250
+ metadata: { ...originalMetadata },
251
+ parent: parentScope,
252
+ });
253
+
254
+ // Add a symbol and child scope to the original
255
+ new OutputSymbol("symbolInOriginal", { binder, scope: originalScope });
256
+ new OutputScope("childOfOriginal", { binder, parent: originalScope });
257
+ flushJobs();
258
+ });
259
+
260
+ it("clones basic properties", () => {
261
+ const newScope = new OutputScope("newScope", { binder });
262
+ const clonedScope = originalScope.clone({ parent: newScope });
263
+
264
+ expect(clonedScope.name).toBe(originalScope.name);
265
+ expect(clonedScope.kind).toBe(originalScope.kind);
266
+ expect(clonedScope.flags).toBe(originalScope.flags);
267
+ expect(clonedScope.id).not.toBe(originalScope.id);
268
+
269
+ expect(clonedScope.metadata).toEqual(originalScope.metadata);
270
+
271
+ expect(clonedScope.symbols.size).toBe(1);
272
+ expect(clonedScope.children.size).toBe(1);
273
+ });
274
+
275
+ it("can override parent in clone options", () => {
276
+ const newParent = new OutputScope("newParent", { binder });
277
+ const clonedScope = originalScope.clone({ parent: newParent });
278
+ flushJobs();
279
+
280
+ expect(clonedScope.parent).toBe(newParent);
281
+ expect(newParent.children.has(clonedScope)).toBe(true);
282
+ });
283
+
284
+ it("can override owner in clone options", () => {
285
+ const newOwnerParent = new OutputScope("newOwnerParent", { binder });
286
+ const newOwner = new OutputSymbol("newOwner", {
287
+ binder,
288
+ scope: newOwnerParent,
289
+ });
290
+ const clonedScope = originalScope.clone({ owner: newOwner });
291
+ flushJobs();
292
+
293
+ expect(clonedScope.owner).toBe(newOwner);
294
+ });
295
+
296
+ it("allows independent changes to clone properties", () => {
297
+ const clonedScope = originalScope.clone();
298
+ clonedScope.name = "clonedName";
299
+
300
+ expect(originalScope.name).toBe("original");
301
+ });
302
+ });