@alloy-js/core 0.10.0 → 0.12.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 (183) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/src/binder.d.ts.map +1 -1
  3. package/dist/src/binder.js +100 -19
  4. package/dist/src/code.js +1 -2
  5. package/dist/src/components/Block.js +2 -5
  6. package/dist/src/components/Declaration.js +2 -4
  7. package/dist/src/components/For.d.ts +2 -2
  8. package/dist/src/components/For.d.ts.map +1 -1
  9. package/dist/src/components/For.js +1 -2
  10. package/dist/src/components/Indent.js +2 -4
  11. package/dist/src/components/List.js +2 -5
  12. package/dist/src/components/MemberDeclaration.js +2 -4
  13. package/dist/src/components/MemberName.js +1 -2
  14. package/dist/src/components/MemberScope.js +2 -4
  15. package/dist/src/components/Name.js +1 -2
  16. package/dist/src/components/Output.js +2 -4
  17. package/dist/src/components/Prose.js +1 -2
  18. package/dist/src/components/ReferenceOrContent.d.ts +8 -0
  19. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -0
  20. package/dist/src/components/ReferenceOrContent.js +11 -0
  21. package/dist/src/components/Scope.js +2 -4
  22. package/dist/src/components/Show.js +1 -2
  23. package/dist/src/components/SourceDirectory.js +2 -4
  24. package/dist/src/components/SourceFile.js +2 -5
  25. package/dist/src/components/StatementList.js +2 -4
  26. package/dist/src/components/Switch.d.ts +1 -1
  27. package/dist/src/components/Switch.d.ts.map +1 -1
  28. package/dist/src/components/Switch.js +1 -2
  29. package/dist/src/components/Wrap.js +2 -4
  30. package/dist/src/components/index.d.ts +1 -0
  31. package/dist/src/components/index.d.ts.map +1 -1
  32. package/dist/src/components/index.js +2 -2
  33. package/dist/src/components/stc/index.d.ts +1 -0
  34. package/dist/src/components/stc/index.d.ts.map +1 -1
  35. package/dist/src/components/stc/index.js +2 -2
  36. package/dist/src/components/stc/sti.js +1 -2
  37. package/dist/src/context/assignment.js +1 -2
  38. package/dist/src/context/binder.js +1 -2
  39. package/dist/src/context/declaration.js +1 -2
  40. package/dist/src/context/index.js +1 -2
  41. package/dist/src/context/member-declaration.js +1 -2
  42. package/dist/src/context/member-scope.js +1 -2
  43. package/dist/src/context/name-policy.js +1 -2
  44. package/dist/src/context/scope.js +1 -2
  45. package/dist/src/context/source-directory.js +1 -2
  46. package/dist/src/context/source-file.js +1 -2
  47. package/dist/src/context.js +1 -2
  48. package/dist/src/debug.js +13 -15
  49. package/dist/src/index.browser.js +1 -2
  50. package/dist/src/index.js +1 -2
  51. package/dist/src/jsx-runtime.d.ts +1 -1
  52. package/dist/src/jsx-runtime.d.ts.map +1 -1
  53. package/dist/src/jsx-runtime.js +10 -5
  54. package/dist/src/name-policy.js +1 -2
  55. package/dist/src/refkey.js +1 -2
  56. package/dist/src/render.d.ts.map +1 -1
  57. package/dist/src/render.js +6 -2
  58. package/dist/src/scheduler.d.ts +8 -0
  59. package/dist/src/scheduler.d.ts.map +1 -0
  60. package/dist/src/scheduler.js +17 -0
  61. package/dist/src/slot.js +1 -2
  62. package/dist/src/stc.js +1 -2
  63. package/dist/src/sti.js +1 -2
  64. package/dist/src/tap.js +1 -2
  65. package/dist/src/tsdoc-metadata.json +1 -1
  66. package/dist/src/utils.js +2 -4
  67. package/dist/src/write-output.browser.js +1 -2
  68. package/dist/src/write-output.js +1 -2
  69. package/dist/test/browser-build.test.js +85 -0
  70. package/dist/test/children.test.js +27 -0
  71. package/dist/test/components/block.test.js +45 -0
  72. package/dist/test/components/declaration.test.js +32 -0
  73. package/dist/test/components/list.test.js +86 -0
  74. package/dist/test/components/prose.test.js +25 -0
  75. package/dist/test/components/reference-or-content.test.d.ts +2 -0
  76. package/dist/test/components/reference-or-content.test.d.ts.map +1 -0
  77. package/dist/test/components/reference-or-content.test.js +149 -0
  78. package/dist/test/components/slot.test.js +134 -0
  79. package/dist/test/components/source-file.test.js +64 -0
  80. package/dist/test/components/wrap.test.js +35 -0
  81. package/dist/test/control-flow/for.test.js +219 -0
  82. package/dist/test/control-flow/match.test.js +67 -0
  83. package/dist/test/control-flow/show.test.js +29 -0
  84. package/dist/test/name-policy.test.js +19 -0
  85. package/dist/test/props-with-defaults.test.js +93 -0
  86. package/dist/test/reactivity/circular-reactives.test.d.ts +2 -0
  87. package/dist/test/reactivity/circular-reactives.test.d.ts.map +1 -0
  88. package/dist/test/reactivity/circular-reactives.test.js +31 -0
  89. package/dist/test/reactivity/cleanup.test.js +82 -0
  90. package/dist/test/reactivity/memo.test.js +16 -0
  91. package/dist/test/reactivity/ref-rendering.test.js +37 -0
  92. package/dist/test/reactivity/test.test.js +61 -0
  93. package/dist/test/reactivity/untrack.test.js +26 -0
  94. package/dist/test/refkey.test.js +25 -0
  95. package/dist/test/rendering/basic.test.js +96 -0
  96. package/dist/test/rendering/code.test.js +55 -0
  97. package/dist/test/rendering/formatting.test.js +402 -0
  98. package/dist/test/rendering/indent.test.js +90 -0
  99. package/dist/test/rendering/memoization.test.js +27 -0
  100. package/dist/test/rendering/refkeys.test.js +32 -0
  101. package/dist/test/split-props.test.js +77 -0
  102. package/dist/test/stc.test.js +34 -0
  103. package/dist/test/symbols.test.js +877 -0
  104. package/dist/test/utils.test.d.ts.map +1 -1
  105. package/dist/test/utils.test.js +223 -0
  106. package/dist/testing/extend-expect.js +1 -2
  107. package/dist/testing/index.js +1 -2
  108. package/dist/testing/render.js +1 -2
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +14 -22
  111. package/src/binder.ts +100 -17
  112. package/src/components/For.tsx +6 -6
  113. package/src/components/ReferenceOrContent.tsx +22 -0
  114. package/src/components/index.tsx +1 -0
  115. package/src/components/stc/index.ts +1 -0
  116. package/src/debug.ts +12 -13
  117. package/src/jsx-runtime.ts +24 -14
  118. package/src/render.ts +5 -0
  119. package/src/scheduler.ts +24 -0
  120. package/temp/api.json +216 -15
  121. package/test/components/declaration.test.tsx +2 -0
  122. package/test/components/list.test.tsx +0 -1
  123. package/test/components/reference-or-content.test.tsx +138 -0
  124. package/test/control-flow/for.test.tsx +34 -4
  125. package/test/reactivity/circular-reactives.test.tsx +32 -0
  126. package/test/reactivity/cleanup.test.tsx +5 -0
  127. package/test/reactivity/untrack.test.ts +3 -0
  128. package/test/rendering/memoization.test.tsx +2 -0
  129. package/test/symbols.test.ts +392 -13
  130. package/test/utils.test.tsx +2 -0
  131. package/babel.config.cjs +0 -4
  132. package/dist/src/binder.js.map +0 -1
  133. package/dist/src/code.js.map +0 -1
  134. package/dist/src/components/Block.js.map +0 -1
  135. package/dist/src/components/Declaration.js.map +0 -1
  136. package/dist/src/components/For.js.map +0 -1
  137. package/dist/src/components/Indent.js.map +0 -1
  138. package/dist/src/components/List.js.map +0 -1
  139. package/dist/src/components/MemberDeclaration.js.map +0 -1
  140. package/dist/src/components/MemberName.js.map +0 -1
  141. package/dist/src/components/MemberScope.js.map +0 -1
  142. package/dist/src/components/Name.js.map +0 -1
  143. package/dist/src/components/Output.js.map +0 -1
  144. package/dist/src/components/Prose.js.map +0 -1
  145. package/dist/src/components/Scope.js.map +0 -1
  146. package/dist/src/components/Show.js.map +0 -1
  147. package/dist/src/components/SourceDirectory.js.map +0 -1
  148. package/dist/src/components/SourceFile.js.map +0 -1
  149. package/dist/src/components/StatementList.js.map +0 -1
  150. package/dist/src/components/Switch.js.map +0 -1
  151. package/dist/src/components/Wrap.js.map +0 -1
  152. package/dist/src/components/index.js.map +0 -1
  153. package/dist/src/components/stc/index.js.map +0 -1
  154. package/dist/src/components/stc/sti.js.map +0 -1
  155. package/dist/src/context/assignment.js.map +0 -1
  156. package/dist/src/context/binder.js.map +0 -1
  157. package/dist/src/context/declaration.js.map +0 -1
  158. package/dist/src/context/index.js.map +0 -1
  159. package/dist/src/context/member-declaration.js.map +0 -1
  160. package/dist/src/context/member-scope.js.map +0 -1
  161. package/dist/src/context/name-policy.js.map +0 -1
  162. package/dist/src/context/scope.js.map +0 -1
  163. package/dist/src/context/source-directory.js.map +0 -1
  164. package/dist/src/context/source-file.js.map +0 -1
  165. package/dist/src/context.js.map +0 -1
  166. package/dist/src/debug.js.map +0 -1
  167. package/dist/src/index.browser.js.map +0 -1
  168. package/dist/src/index.js.map +0 -1
  169. package/dist/src/jsx-runtime.js.map +0 -1
  170. package/dist/src/name-policy.js.map +0 -1
  171. package/dist/src/refkey.js.map +0 -1
  172. package/dist/src/render.js.map +0 -1
  173. package/dist/src/slot.js.map +0 -1
  174. package/dist/src/stc.js.map +0 -1
  175. package/dist/src/sti.js.map +0 -1
  176. package/dist/src/tap.js.map +0 -1
  177. package/dist/src/utils.js.map +0 -1
  178. package/dist/src/write-output.browser.js.map +0 -1
  179. package/dist/src/write-output.js.map +0 -1
  180. package/dist/testing/extend-expect.js.map +0 -1
  181. package/dist/testing/index.js.map +0 -1
  182. package/dist/testing/render.js.map +0 -1
  183. package/dist/testing/vitest.d.js.map +0 -1
@@ -0,0 +1,877 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createOutputBinder, OutputScopeFlags, OutputSymbolFlags } from "../src/binder.js";
3
+ import { refkey } from "../src/refkey.js";
4
+ import { flushJobs } from "../src/scheduler.js";
5
+ it("works", () => {
6
+ const binder = createOutputBinder();
7
+ const scope = binder.createScope({
8
+ kind: "foo",
9
+ name: "scope",
10
+ parent: binder.globalScope
11
+ });
12
+ const symbol = binder.createSymbol({
13
+ name: "sym",
14
+ scope
15
+ });
16
+ flushJobs();
17
+ expect([...scope.getSymbolNames()]).toEqual(["sym"]);
18
+ symbol.name = "bar";
19
+ flushJobs();
20
+ expect([...scope.getSymbolNames()]).toEqual(["bar"]);
21
+ });
22
+ it("resolves symbol conflicts", () => {
23
+ const binder = createOutputBinder();
24
+ const scope = binder.createScope({
25
+ kind: "foo",
26
+ name: "scope",
27
+ parent: binder.globalScope
28
+ });
29
+ const _s1 = binder.createSymbol({
30
+ name: "sym",
31
+ scope
32
+ });
33
+ const s2 = binder.createSymbol({
34
+ name: "sym",
35
+ scope
36
+ });
37
+ expect(s2.name).toEqual("sym_2");
38
+ });
39
+ function createScopeTree(binder, tree) {
40
+ const createdItems = {
41
+ symbols: {},
42
+ scopes: {}
43
+ };
44
+ for (const [name, desc] of Object.entries(tree)) {
45
+ createScope(name, desc);
46
+ }
47
+ return createdItems;
48
+ function createScope(name, descriptor, parent = binder.globalScope) {
49
+ const scope = binder.createScope({
50
+ kind: "useless",
51
+ name,
52
+ parent,
53
+ flags: descriptor.flags ?? OutputScopeFlags.None
54
+ });
55
+ createdItems.scopes[name] = scope;
56
+ for (const [name, desc] of Object.entries(descriptor.symbols)) {
57
+ createSymbol(name, desc, scope);
58
+ }
59
+ for (const [name, desc] of Object.entries(descriptor.scopes ?? {})) {
60
+ createScope(name, desc, scope);
61
+ }
62
+ }
63
+ function createSymbol(name, descriptor, parent) {
64
+ const symbol = binder.createSymbol({
65
+ name,
66
+ scope: parent,
67
+ refkey: descriptor.refkey ?? refkey(),
68
+ flags: descriptor.flags ?? OutputSymbolFlags.None
69
+ });
70
+ createdItems.symbols[name] = symbol;
71
+ if (descriptor.instanceMembers) {
72
+ for (const [name, desc] of Object.entries(descriptor.instanceMembers)) {
73
+ createSymbol(name, desc, symbol.instanceMemberScope);
74
+ }
75
+ }
76
+ if (descriptor.staticMembers) {
77
+ for (const [name, desc] of Object.entries(descriptor.staticMembers)) {
78
+ createSymbol(name, desc, symbol.staticMemberScope);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ describe("static members", () => {
84
+ it("resolves static symbols", () => {
85
+ const binder = createOutputBinder();
86
+ const {
87
+ scopes: {
88
+ root
89
+ },
90
+ symbols: {
91
+ root: rootSym,
92
+ static: staticSym
93
+ }
94
+ } = createScopeTree(binder, {
95
+ root: {
96
+ symbols: {
97
+ root: {
98
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer,
99
+ staticMembers: {
100
+ static: {
101
+ flags: OutputSymbolFlags.StaticMember
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ });
108
+ const resolution = binder.resolveDeclarationByKey(root, undefined, staticSym.refkeys[0]);
109
+ expect(resolution.value).toBeDefined();
110
+ const {
111
+ commonScope,
112
+ pathUp,
113
+ pathDown,
114
+ targetDeclaration,
115
+ memberPath
116
+ } = resolution.value;
117
+ expect(commonScope).toBe(root);
118
+ expect(targetDeclaration).toBe(rootSym);
119
+ expect(pathDown).toEqual([]);
120
+ expect(pathUp).toEqual([]);
121
+ expect(memberPath.map(s => s.name)).toEqual(["root", "static"]);
122
+ });
123
+ it("resolves deeply nested static symbols", () => {
124
+ const binder = createOutputBinder();
125
+ const {
126
+ scopes: {
127
+ root
128
+ },
129
+ symbols: {
130
+ root: rootSym,
131
+ nested_static
132
+ }
133
+ } = createScopeTree(binder, {
134
+ root: {
135
+ symbols: {
136
+ root: {
137
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer,
138
+ staticMembers: {
139
+ static: {
140
+ flags: OutputSymbolFlags.StaticMember | OutputSymbolFlags.StaticMemberContainer,
141
+ staticMembers: {
142
+ nested_static: {
143
+ flags: OutputSymbolFlags.StaticMember
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ });
152
+ const resolution = binder.resolveDeclarationByKey(root, undefined, nested_static.refkeys[0]);
153
+ expect(resolution.value).toBeDefined();
154
+ const {
155
+ commonScope,
156
+ pathUp,
157
+ pathDown,
158
+ targetDeclaration,
159
+ memberPath
160
+ } = resolution.value;
161
+ expect(commonScope).toBe(root);
162
+ expect(targetDeclaration).toBe(rootSym);
163
+ expect(pathDown).toEqual([]);
164
+ expect(pathUp).toEqual([]);
165
+ expect(memberPath.map(s => s.name)).toEqual(["root", "static", "nested_static"]);
166
+ });
167
+ it("resolves static symbols lazily", () => {
168
+ const staticSymRefkey = refkey();
169
+ const binder = createOutputBinder();
170
+ const {
171
+ scopes: {
172
+ root
173
+ },
174
+ symbols: {
175
+ root: rootSym
176
+ }
177
+ } = createScopeTree(binder, {
178
+ root: {
179
+ symbols: {
180
+ root: {
181
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer
182
+ }
183
+ }
184
+ }
185
+ });
186
+ const resolution = binder.resolveDeclarationByKey(root, undefined, staticSymRefkey);
187
+ expect(resolution.value).toBeUndefined();
188
+ binder.createSymbol({
189
+ name: "static",
190
+ scope: rootSym.staticMemberScope,
191
+ refkey: staticSymRefkey,
192
+ flags: OutputSymbolFlags.StaticMember
193
+ });
194
+ expect(resolution.value).toBeDefined();
195
+ const {
196
+ commonScope,
197
+ pathUp,
198
+ pathDown,
199
+ targetDeclaration,
200
+ memberPath
201
+ } = resolution.value;
202
+ expect(commonScope).toBe(root);
203
+ expect(targetDeclaration).toBe(rootSym);
204
+ expect(pathDown).toEqual([]);
205
+ expect(pathUp).toEqual([]);
206
+ expect(memberPath.map(s => s.name)).toEqual(["root", "static"]);
207
+ });
208
+ });
209
+ describe("instance members", () => {
210
+ it("resolves", () => {
211
+ const binder = createOutputBinder();
212
+ const {
213
+ symbols: {
214
+ root: rootSym,
215
+ instance
216
+ }
217
+ } = createScopeTree(binder, {
218
+ root: {
219
+ symbols: {
220
+ root: {
221
+ flags: OutputSymbolFlags.InstanceMemberContainer,
222
+ instanceMembers: {
223
+ instance: {
224
+ flags: OutputSymbolFlags.InstanceMember
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ });
231
+ const resolution = binder.resolveDeclarationByKey(undefined, rootSym.instanceMemberScope, instance.refkeys[0]);
232
+ expect(resolution.value).toBeDefined();
233
+ const {
234
+ commonScope,
235
+ pathUp,
236
+ pathDown,
237
+ targetDeclaration,
238
+ memberPath
239
+ } = resolution.value;
240
+ expect(commonScope).toBe(rootSym.instanceMemberScope);
241
+ expect(targetDeclaration).toBe(instance);
242
+ expect(pathDown).toEqual([]);
243
+ expect(pathUp).toEqual([]);
244
+ expect(memberPath).toEqual([instance]);
245
+ });
246
+ });
247
+ describe("instantiating members", () => {
248
+ it("instantiates instance members", () => {
249
+ const binder = createOutputBinder();
250
+
251
+ /**
252
+ * The following structure would match code like this:
253
+ * ```ts
254
+ * // A class with instance members
255
+ * class Source {
256
+ * instance() {
257
+ * print("instance");
258
+ * }
259
+ * }
260
+ *
261
+ * // Instantiates into t
262
+ * var t = new Source();
263
+ *
264
+ * t.instance();
265
+ * ```
266
+ */
267
+ const {
268
+ symbols: {
269
+ rootSymbol,
270
+ instance,
271
+ instantiation
272
+ }
273
+ } = createScopeTree(binder, {
274
+ rootScope: {
275
+ symbols: {
276
+ rootSymbol: {
277
+ flags: OutputSymbolFlags.InstanceMemberContainer,
278
+ instanceMembers: {
279
+ instance: {
280
+ flags: OutputSymbolFlags.InstanceMember
281
+ }
282
+ }
283
+ },
284
+ instantiation: {}
285
+ }
286
+ }
287
+ });
288
+ binder.instantiateSymbolInto(rootSymbol, instantiation);
289
+ expect(instantiation.flags & OutputSymbolFlags.StaticMemberContainer).toBeTruthy();
290
+ expect(instantiation.staticMemberScope).toBeDefined();
291
+ const expectedRefkey = refkey(instantiation.refkeys[0], instance.refkeys[0]);
292
+ expect(instantiation.staticMemberScope.symbolsByRefkey.get(expectedRefkey)).toBeDefined();
293
+ });
294
+ it("doesn't duplicate symbols", () => {
295
+ const binder = createOutputBinder();
296
+ const {
297
+ symbols: {
298
+ rootSymbol,
299
+ instantiation
300
+ }
301
+ } = createScopeTree(binder, {
302
+ rootScope: {
303
+ symbols: {
304
+ rootSymbol: {
305
+ flags: OutputSymbolFlags.InstanceMemberContainer,
306
+ instanceMembers: {
307
+ instance: {
308
+ flags: OutputSymbolFlags.InstanceMember
309
+ }
310
+ }
311
+ },
312
+ instantiation: {}
313
+ }
314
+ }
315
+ });
316
+ binder.instantiateSymbolInto(rootSymbol, instantiation);
317
+ flushJobs();
318
+ expect(instantiation.staticMemberScope.symbols.size).toBe(1);
319
+ const lateKey = refkey();
320
+ // now add a brand‐new static member to source
321
+ binder.createSymbol({
322
+ name: "lateChild",
323
+ scope: rootSymbol.instanceMemberScope,
324
+ refkey: lateKey,
325
+ flags: OutputSymbolFlags.InstanceMember
326
+ });
327
+ flushJobs();
328
+ expect(rootSymbol.instanceMemberScope.symbols.size).toBe(2);
329
+ expect(instantiation.staticMemberScope.symbols.size).toBe(2);
330
+ });
331
+ it("should remove members in instance when source deleted them", () => {
332
+ const binder = createOutputBinder();
333
+ const {
334
+ symbols: {
335
+ rootSymbol,
336
+ instantiation
337
+ }
338
+ } = createScopeTree(binder, {
339
+ rootScope: {
340
+ symbols: {
341
+ rootSymbol: {
342
+ flags: OutputSymbolFlags.InstanceMemberContainer,
343
+ instanceMembers: {
344
+ instance: {
345
+ flags: OutputSymbolFlags.InstanceMember
346
+ }
347
+ }
348
+ },
349
+ instantiation: {}
350
+ }
351
+ }
352
+ });
353
+ binder.instantiateSymbolInto(rootSymbol, instantiation);
354
+ expect(instantiation.staticMemberScope.symbols.size).toBe(1);
355
+ const lateKey = refkey();
356
+ // now add a brand‐new static member to source
357
+ binder.createSymbol({
358
+ name: "lateChild",
359
+ scope: rootSymbol.instanceMemberScope,
360
+ refkey: lateKey,
361
+ flags: OutputSymbolFlags.InstanceMember
362
+ });
363
+ flushJobs();
364
+ expect(rootSymbol.instanceMemberScope.symbols.size).toBe(2);
365
+ expect(instantiation.staticMemberScope.symbols.size).toBe(2);
366
+ binder.deleteSymbol(rootSymbol.instanceMemberScope.symbols.values().next().value);
367
+ flushJobs();
368
+ expect(rootSymbol.instanceMemberScope.symbols.size).toBe(1);
369
+ expect(instantiation.staticMemberScope.symbols.size).toBe(1);
370
+ });
371
+ it("instantiates instance members added after the instantiation", () => {
372
+ const binder = createOutputBinder();
373
+ const {
374
+ symbols: {
375
+ rootSymbol,
376
+ instance,
377
+ instantiation
378
+ }
379
+ } = createScopeTree(binder, {
380
+ rootScope: {
381
+ symbols: {
382
+ rootSymbol: {
383
+ flags: OutputSymbolFlags.InstanceMemberContainer,
384
+ instanceMembers: {
385
+ instance: {
386
+ flags: OutputSymbolFlags.InstanceMember
387
+ }
388
+ }
389
+ },
390
+ instantiation: {}
391
+ }
392
+ }
393
+ });
394
+ binder.instantiateSymbolInto(rootSymbol, instantiation);
395
+ flushJobs();
396
+ expect(instantiation.flags & OutputSymbolFlags.StaticMemberContainer).toBeTruthy();
397
+ expect(instantiation.staticMemberScope).toBeDefined();
398
+ const expectedRefkey = refkey(instantiation.refkeys[0], instance.refkeys[0]);
399
+ expect(instantiation.staticMemberScope.symbolsByRefkey.get(expectedRefkey)).toBeDefined();
400
+ const newInstanceMemberRefkey = refkey();
401
+ binder.createSymbol({
402
+ name: "newInstanceMember",
403
+ scope: rootSymbol.instanceMemberScope,
404
+ refkey: newInstanceMemberRefkey,
405
+ flags: OutputSymbolFlags.InstanceMember
406
+ });
407
+ const newExpectedRefkey = refkey(instantiation.refkeys[0], newInstanceMemberRefkey);
408
+ flushJobs();
409
+ expect(instantiation.staticMemberScope.symbolsByRefkey.get(newExpectedRefkey)).toBeDefined();
410
+ });
411
+ it("instantiates static symbols for a static container source", () => {
412
+ const binder = createOutputBinder();
413
+
414
+ /**
415
+ * The following structure would match code like this:
416
+ * ```ts
417
+ * // A class with instance members
418
+ * class Source {
419
+ * static child() {
420
+ * print("child");
421
+ * }
422
+ * }
423
+ *
424
+ *
425
+ * var printChild = Source.child;
426
+ *
427
+ * printChild();
428
+ * ```
429
+ */
430
+ const {
431
+ symbols: {
432
+ source,
433
+ child,
434
+ target
435
+ }
436
+ } = createScopeTree(binder, {
437
+ root: {
438
+ symbols: {
439
+ source: {
440
+ flags: OutputSymbolFlags.StaticMemberContainer,
441
+ staticMembers: {
442
+ child: {
443
+ flags: OutputSymbolFlags.StaticMember
444
+ }
445
+ }
446
+ },
447
+ target: {}
448
+ }
449
+ }
450
+ });
451
+ binder.instantiateSymbolInto(source, target);
452
+
453
+ // target must now be a StaticMemberContainer too
454
+ expect(target.flags & OutputSymbolFlags.StaticMemberContainer).toBeTruthy();
455
+ expect(target.staticMemberScope).toBeDefined();
456
+ const expectedKey = refkey(target.refkeys[0], child.refkeys[0]);
457
+ expect(target.staticMemberScope.symbolsByRefkey.get(expectedKey)).toBeDefined();
458
+ });
459
+ it("instantiates static symbols added after instantiation", () => {
460
+ const binder = createOutputBinder();
461
+ const lateKey = refkey();
462
+ const {
463
+ symbols: {
464
+ source,
465
+ target
466
+ }
467
+ } = createScopeTree(binder, {
468
+ root: {
469
+ symbols: {
470
+ source: {
471
+ flags: OutputSymbolFlags.StaticMemberContainer
472
+ },
473
+ target: {}
474
+ }
475
+ }
476
+ });
477
+
478
+ // hook up instantiation
479
+ binder.instantiateSymbolInto(source, target);
480
+
481
+ // now add a brand‐new static member to source
482
+ const late = binder.createSymbol({
483
+ name: "lateChild",
484
+ scope: source.staticMemberScope,
485
+ refkey: lateKey,
486
+ flags: OutputSymbolFlags.StaticMember
487
+ });
488
+ flushJobs();
489
+
490
+ // it should *automatically* show up on target.staticMemberScope
491
+ const expectedKey = refkey(target.refkeys[0], late.refkeys[0]);
492
+ expect(target.staticMemberScope.symbolsByRefkey.get(expectedKey)).toBeDefined();
493
+ });
494
+ it("recursively instantiates nested static members", () => {
495
+ const binder = createOutputBinder();
496
+
497
+ /**
498
+ * The following structure would match code like this:
499
+ * ```ts
500
+ * class Source {
501
+ * static Level1 = class {
502
+ * static level2() { print("deep"); }
503
+ * }
504
+ * }
505
+ *
506
+ * var target = Source;
507
+ *
508
+ * target.Level1.level2()
509
+ * ```
510
+ */
511
+ const {
512
+ symbols: {
513
+ source,
514
+ level1,
515
+ level2,
516
+ target
517
+ }
518
+ } = createScopeTree(binder, {
519
+ root: {
520
+ symbols: {
521
+ source: {
522
+ flags: OutputSymbolFlags.StaticMemberContainer,
523
+ staticMembers: {
524
+ level1: {
525
+ flags: OutputSymbolFlags.StaticMember | OutputSymbolFlags.StaticMemberContainer,
526
+ staticMembers: {
527
+ level2: {
528
+ flags: OutputSymbolFlags.StaticMember
529
+ }
530
+ }
531
+ }
532
+ }
533
+ },
534
+ target: {}
535
+ }
536
+ }
537
+ });
538
+ binder.instantiateSymbolInto(source, target);
539
+
540
+ // level1 should appear under target.staticMemberScope
541
+ const key1 = refkey(target.refkeys[0], level1.refkeys[0]);
542
+ const instantiated1 = target.staticMemberScope.symbolsByRefkey.get(key1);
543
+ expect(instantiated1.name).toBe(level1.name);
544
+
545
+ // and level2 should appear under the *child* staticMemberScope of that instantiated level1
546
+ const childScope = instantiated1.staticMemberScope;
547
+ const key2 = refkey(instantiated1.refkeys[0], level2.refkeys[0]);
548
+ expect(childScope.symbolsByRefkey.get(key2)).toBeDefined();
549
+ });
550
+ it("copies both instance *and* static members when source has both flags", () => {
551
+ const binder = createOutputBinder();
552
+
553
+ /**
554
+ * ```ts
555
+ * class Source {
556
+ * instance() { print("inst"); }
557
+ * static s1() { print("static"); }
558
+ * }
559
+ *
560
+ * let t = new Source()
561
+ * t.instance()
562
+ * t.s1()
563
+ * ```
564
+ */
565
+ const {
566
+ symbols: {
567
+ source,
568
+ inst
569
+ }
570
+ } = createScopeTree(binder, {
571
+ root: {
572
+ symbols: {
573
+ source: {
574
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer,
575
+ instanceMembers: {
576
+ i1: {
577
+ flags: OutputSymbolFlags.InstanceMember
578
+ }
579
+ },
580
+ staticMembers: {
581
+ s1: {
582
+ flags: OutputSymbolFlags.StaticMember
583
+ }
584
+ }
585
+ },
586
+ inst: {}
587
+ }
588
+ }
589
+ });
590
+ binder.instantiateSymbolInto(source, inst);
591
+ expect(inst.staticMemberScope).toBeDefined();
592
+ expect([...inst.staticMemberScope.symbols].some(s => s.name === "i1")).toBe(true);
593
+
594
+ // static side
595
+ const symbols = [...source.staticMemberScope.symbols];
596
+ expect(inst.staticMemberScope).toBeDefined();
597
+ const sKey = refkey(inst.refkeys[0], symbols[0].refkeys[0]);
598
+ expect(inst.staticMemberScope.symbolsByRefkey.has(sKey)).toBe(true);
599
+ });
600
+ it("is idempotent, calling twice does not duplicate", () => {
601
+ const binder = createOutputBinder();
602
+ const {
603
+ symbols: {
604
+ source,
605
+ target
606
+ }
607
+ } = createScopeTree(binder, {
608
+ root: {
609
+ symbols: {
610
+ source: {
611
+ flags: OutputSymbolFlags.StaticMemberContainer,
612
+ staticMembers: {
613
+ a: {
614
+ flags: OutputSymbolFlags.StaticMember
615
+ }
616
+ }
617
+ },
618
+ target: {}
619
+ }
620
+ }
621
+ });
622
+ binder.instantiateSymbolInto(source, target);
623
+ flushJobs();
624
+ const initialCount = target.staticMemberScope.symbols.size;
625
+ binder.instantiateSymbolInto(source, target);
626
+ flushJobs();
627
+ expect(target.staticMemberScope.symbols.size).toBe(initialCount);
628
+ });
629
+ it("instantiates static children of instance members under the instance scope", () => {
630
+ const binder = createOutputBinder();
631
+ /**
632
+ * ```ts
633
+ * class Source {
634
+ * instM = class {
635
+ * static deep() { print("deep"); }
636
+ * }
637
+ * }
638
+ *
639
+ * var t = new Source();
640
+ * t.instM.deep();
641
+ * ```
642
+ */
643
+ const {
644
+ symbols: {
645
+ source,
646
+ deep,
647
+ target
648
+ }
649
+ } = createScopeTree(binder, {
650
+ root: {
651
+ symbols: {
652
+ source: {
653
+ flags: OutputSymbolFlags.InstanceMemberContainer,
654
+ instanceMembers: {
655
+ instM: {
656
+ flags: OutputSymbolFlags.InstanceMember | OutputSymbolFlags.StaticMemberContainer,
657
+ staticMembers: {
658
+ deep: {
659
+ flags: OutputSymbolFlags.StaticMember
660
+ }
661
+ }
662
+ }
663
+ }
664
+ },
665
+ target: {}
666
+ }
667
+ }
668
+ });
669
+ binder.instantiateSymbolInto(source, target);
670
+
671
+ // Find the instantiated copy of instM under target.instanceMemberScope
672
+ const instMSym = [...target.staticMemberScope.symbols].find(s => s.name === "instM");
673
+
674
+ // instMSym should have gotten its own staticMemberScope via the StaticMemberContainer flag
675
+ expect(instMSym.staticMemberScope).toBeDefined();
676
+
677
+ // compute the expected key for the deep child:
678
+ // (<target>, <instM>) then (on that) (<deep original>)
679
+ const expectedDeepKey = refkey(instMSym.refkeys[0], deep.refkeys[0]);
680
+ expect(instMSym.staticMemberScope.symbolsByRefkey.has(expectedDeepKey)).toBe(true);
681
+ });
682
+ });
683
+ describe("symbol name resolution", () => {
684
+ it("resolves static symbols", () => {
685
+ const binder = createOutputBinder();
686
+ const {
687
+ symbols: {
688
+ static: staticSym
689
+ }
690
+ } = createScopeTree(binder, {
691
+ root: {
692
+ symbols: {
693
+ root: {
694
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer,
695
+ staticMembers: {
696
+ static: {
697
+ flags: OutputSymbolFlags.StaticMember
698
+ }
699
+ }
700
+ }
701
+ }
702
+ }
703
+ });
704
+ const result = binder.resolveFQN("root.root.static");
705
+ expect(result.value).toEqual(staticSym);
706
+ });
707
+ it("resolves static symbols that are added later", () => {
708
+ const binder = createOutputBinder();
709
+ const result = binder.resolveFQN("root.root.static");
710
+ expect(result.value).toBeUndefined();
711
+ const {
712
+ symbols: {
713
+ static: staticSym
714
+ }
715
+ } = createScopeTree(binder, {
716
+ root: {
717
+ symbols: {
718
+ root: {
719
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer,
720
+ staticMembers: {
721
+ static: {
722
+ flags: OutputSymbolFlags.StaticMember
723
+ }
724
+ }
725
+ }
726
+ }
727
+ }
728
+ });
729
+ expect(result.value).toEqual(staticSym);
730
+ });
731
+ it("resolves instance symbols", () => {
732
+ const binder = createOutputBinder();
733
+ const {
734
+ symbols: {
735
+ instance
736
+ }
737
+ } = createScopeTree(binder, {
738
+ root: {
739
+ symbols: {
740
+ root: {
741
+ flags: OutputSymbolFlags.InstanceMemberContainer,
742
+ instanceMembers: {
743
+ instance: {
744
+ flags: OutputSymbolFlags.InstanceMember
745
+ }
746
+ }
747
+ }
748
+ }
749
+ }
750
+ });
751
+ const result = binder.resolveFQN("root.root#instance");
752
+ expect(result.value).toEqual(instance);
753
+ });
754
+ it("resolves instance symbols that are added later", () => {
755
+ const binder = createOutputBinder();
756
+ const result = binder.resolveFQN("root.root#instance");
757
+ expect(result.value).toBeUndefined();
758
+ const {
759
+ symbols: {
760
+ instance
761
+ }
762
+ } = createScopeTree(binder, {
763
+ root: {
764
+ symbols: {
765
+ root: {
766
+ flags: OutputSymbolFlags.InstanceMemberContainer,
767
+ instanceMembers: {
768
+ instance: {
769
+ flags: OutputSymbolFlags.InstanceMember
770
+ }
771
+ }
772
+ }
773
+ }
774
+ }
775
+ });
776
+ expect(result.value).toEqual(instance);
777
+ });
778
+ });
779
+ describe("refkey resolution", () => {
780
+ it("resolves existing symbols by refkey", () => {
781
+ const key = refkey();
782
+ const binder = createOutputBinder();
783
+ const sym = binder.createSymbol({
784
+ name: "foo",
785
+ refkey: key,
786
+ scope: binder.globalScope
787
+ });
788
+ const resolvedSym = binder.resolveDeclarationByKey(undefined, undefined, key);
789
+ expect(resolvedSym.value?.targetDeclaration).toBe(sym);
790
+ });
791
+ it("resolves symbols by refkey when symbol is added later", () => {
792
+ const key = refkey();
793
+ const binder = createOutputBinder();
794
+ const resolvedSym = binder.resolveDeclarationByKey(undefined, undefined, key);
795
+ const sym = binder.createSymbol({
796
+ name: "foo",
797
+ refkey: key,
798
+ scope: binder.globalScope
799
+ });
800
+ expect(resolvedSym.value?.targetDeclaration).toBe(sym);
801
+ });
802
+ it("resolves symbols by refkey when refkey is added later", () => {
803
+ const key = refkey();
804
+ const binder = createOutputBinder();
805
+ const resolvedSym = binder.resolveDeclarationByKey(undefined, undefined, key);
806
+ const sym = binder.createSymbol({
807
+ name: "foo",
808
+ scope: binder.globalScope
809
+ });
810
+ expect(resolvedSym.value).toBe(undefined);
811
+ sym.refkeys[0] = key;
812
+ flushJobs();
813
+ expect(resolvedSym.value?.targetDeclaration).toBe(sym);
814
+ });
815
+ });
816
+ describe("Deleting symbols", () => {
817
+ it("updates resolutions", () => {
818
+ const key = refkey();
819
+ const binder = createOutputBinder();
820
+ const resolvedSym = binder.resolveDeclarationByKey(undefined, undefined, key);
821
+ const sym = binder.createSymbol({
822
+ name: "foo",
823
+ scope: binder.globalScope
824
+ });
825
+ expect(resolvedSym.value).toBe(undefined);
826
+ sym.refkeys[0] = key;
827
+ flushJobs();
828
+ expect(resolvedSym.value?.targetDeclaration).toBe(sym);
829
+ binder.deleteSymbol(sym);
830
+ flushJobs();
831
+ expect(resolvedSym.value).toBe(undefined);
832
+ });
833
+ it("removes from parent scopes", () => {
834
+ const binder = createOutputBinder();
835
+ const result = binder.resolveFQN("root.root#instance");
836
+ expect(result.value).toBeUndefined();
837
+ const {
838
+ symbols: {
839
+ instance,
840
+ root,
841
+ staticc
842
+ },
843
+ scopes: {
844
+ rootScope
845
+ }
846
+ } = createScopeTree(binder, {
847
+ rootScope: {
848
+ symbols: {
849
+ root: {
850
+ flags: OutputSymbolFlags.InstanceMemberContainer | OutputSymbolFlags.StaticMemberContainer,
851
+ instanceMembers: {
852
+ instance: {
853
+ flags: OutputSymbolFlags.InstanceMember
854
+ }
855
+ },
856
+ staticMembers: {
857
+ staticc: {
858
+ flags: OutputSymbolFlags.StaticMember
859
+ }
860
+ }
861
+ }
862
+ }
863
+ }
864
+ });
865
+ const staticScope = root.staticMemberScope;
866
+ const instanceScope = root.instanceMemberScope;
867
+ expect(staticScope.symbols.size).toBe(1);
868
+ binder.deleteSymbol(staticc);
869
+ expect(staticScope.symbols.size).toBe(0);
870
+ expect(instanceScope.symbols.size).toBe(1);
871
+ binder.deleteSymbol(instance);
872
+ expect(instanceScope.symbols.size).toBe(0);
873
+ expect(rootScope.symbols.size).toBe(1);
874
+ binder.deleteSymbol(root);
875
+ expect(rootScope.symbols.size).toBe(0);
876
+ });
877
+ });