@alloy-js/core 0.1.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 (229) hide show
  1. package/LICENSE.txt +7 -0
  2. package/api-extractor.json +11 -0
  3. package/babel.config.cjs +4 -0
  4. package/dist/src/binder.d.ts +333 -0
  5. package/dist/src/binder.d.ts.map +1 -0
  6. package/dist/src/binder.js +444 -0
  7. package/dist/src/binder.js.map +1 -0
  8. package/dist/src/code.d.ts +3 -0
  9. package/dist/src/code.d.ts.map +1 -0
  10. package/dist/src/code.js +156 -0
  11. package/dist/src/code.js.map +1 -0
  12. package/dist/src/components/Declaration.d.ts +29 -0
  13. package/dist/src/components/Declaration.d.ts.map +1 -0
  14. package/dist/src/components/Declaration.js +47 -0
  15. package/dist/src/components/Declaration.js.map +1 -0
  16. package/dist/src/components/Indent.d.ts +13 -0
  17. package/dist/src/components/Indent.d.ts.map +1 -0
  18. package/dist/src/components/Indent.js +23 -0
  19. package/dist/src/components/Indent.js.map +1 -0
  20. package/dist/src/components/MemberDeclaration.d.ts +30 -0
  21. package/dist/src/components/MemberDeclaration.d.ts.map +1 -0
  22. package/dist/src/components/MemberDeclaration.js +52 -0
  23. package/dist/src/components/MemberDeclaration.js.map +1 -0
  24. package/dist/src/components/MemberName.d.ts +2 -0
  25. package/dist/src/components/MemberName.d.ts.map +1 -0
  26. package/dist/src/components/MemberName.js +11 -0
  27. package/dist/src/components/MemberName.js.map +1 -0
  28. package/dist/src/components/MemberScope.d.ts +27 -0
  29. package/dist/src/components/MemberScope.d.ts.map +1 -0
  30. package/dist/src/components/MemberScope.js +28 -0
  31. package/dist/src/components/MemberScope.js.map +1 -0
  32. package/dist/src/components/Name.d.ts +2 -0
  33. package/dist/src/components/Name.d.ts.map +1 -0
  34. package/dist/src/components/Name.js +11 -0
  35. package/dist/src/components/Name.js.map +1 -0
  36. package/dist/src/components/Output.d.ts +31 -0
  37. package/dist/src/components/Output.d.ts.map +1 -0
  38. package/dist/src/components/Output.js +44 -0
  39. package/dist/src/components/Output.js.map +1 -0
  40. package/dist/src/components/Scope.d.ts +10 -0
  41. package/dist/src/components/Scope.d.ts.map +1 -0
  42. package/dist/src/components/Scope.js +25 -0
  43. package/dist/src/components/Scope.js.map +1 -0
  44. package/dist/src/components/SourceDirectory.d.ts +7 -0
  45. package/dist/src/components/SourceDirectory.d.ts.map +1 -0
  46. package/dist/src/components/SourceDirectory.js +38 -0
  47. package/dist/src/components/SourceDirectory.js.map +1 -0
  48. package/dist/src/components/SourceFile.d.ts +12 -0
  49. package/dist/src/components/SourceFile.d.ts.map +1 -0
  50. package/dist/src/components/SourceFile.js +26 -0
  51. package/dist/src/components/SourceFile.js.map +1 -0
  52. package/dist/src/components/index.d.ts +11 -0
  53. package/dist/src/components/index.d.ts.map +1 -0
  54. package/dist/src/components/index.js +11 -0
  55. package/dist/src/components/index.js.map +1 -0
  56. package/dist/src/components/stc/index.d.ts +26 -0
  57. package/dist/src/components/stc/index.d.ts.map +1 -0
  58. package/dist/src/components/stc/index.js +9 -0
  59. package/dist/src/components/stc/index.js.map +1 -0
  60. package/dist/src/context/assignment.d.ts +39 -0
  61. package/dist/src/context/assignment.d.ts.map +1 -0
  62. package/dist/src/context/assignment.js +39 -0
  63. package/dist/src/context/assignment.js.map +1 -0
  64. package/dist/src/context/binder.d.ts +9 -0
  65. package/dist/src/context/binder.d.ts.map +1 -0
  66. package/dist/src/context/binder.js +12 -0
  67. package/dist/src/context/binder.js.map +1 -0
  68. package/dist/src/context/declaration.d.ts +4 -0
  69. package/dist/src/context/declaration.d.ts.map +1 -0
  70. package/dist/src/context/declaration.js +3 -0
  71. package/dist/src/context/declaration.js.map +1 -0
  72. package/dist/src/context/indent.d.ts +5 -0
  73. package/dist/src/context/indent.d.ts.map +1 -0
  74. package/dist/src/context/indent.js +8 -0
  75. package/dist/src/context/indent.js.map +1 -0
  76. package/dist/src/context/index.d.ts +11 -0
  77. package/dist/src/context/index.d.ts.map +1 -0
  78. package/dist/src/context/index.js +11 -0
  79. package/dist/src/context/index.js.map +1 -0
  80. package/dist/src/context/member-declaration.d.ts +9 -0
  81. package/dist/src/context/member-declaration.d.ts.map +1 -0
  82. package/dist/src/context/member-declaration.js +9 -0
  83. package/dist/src/context/member-declaration.js.map +1 -0
  84. package/dist/src/context/member-scope.d.ts +13 -0
  85. package/dist/src/context/member-scope.d.ts.map +1 -0
  86. package/dist/src/context/member-scope.js +12 -0
  87. package/dist/src/context/member-scope.js.map +1 -0
  88. package/dist/src/context/name-policy.d.ts +5 -0
  89. package/dist/src/context/name-policy.d.ts.map +1 -0
  90. package/dist/src/context/name-policy.js +10 -0
  91. package/dist/src/context/name-policy.js.map +1 -0
  92. package/dist/src/context/scope.d.ts +5 -0
  93. package/dist/src/context/scope.d.ts.map +1 -0
  94. package/dist/src/context/scope.js +6 -0
  95. package/dist/src/context/scope.js.map +1 -0
  96. package/dist/src/context/source-directory.d.ts +9 -0
  97. package/dist/src/context/source-directory.d.ts.map +1 -0
  98. package/dist/src/context/source-directory.js +3 -0
  99. package/dist/src/context/source-directory.js.map +1 -0
  100. package/dist/src/context/source-file.d.ts +12 -0
  101. package/dist/src/context/source-file.d.ts.map +1 -0
  102. package/dist/src/context/source-file.js +3 -0
  103. package/dist/src/context/source-file.js.map +1 -0
  104. package/dist/src/context.d.ts +13 -0
  105. package/dist/src/context.d.ts.map +1 -0
  106. package/dist/src/context.js +30 -0
  107. package/dist/src/context.js.map +1 -0
  108. package/dist/src/index.d.ts +13 -0
  109. package/dist/src/index.d.ts.map +1 -0
  110. package/dist/src/index.js +13 -0
  111. package/dist/src/index.js.map +1 -0
  112. package/dist/src/jsx-runtime.d.ts +43 -0
  113. package/dist/src/jsx-runtime.d.ts.map +1 -0
  114. package/dist/src/jsx-runtime.js +172 -0
  115. package/dist/src/jsx-runtime.js.map +1 -0
  116. package/dist/src/name-policy.d.ts +5 -0
  117. package/dist/src/name-policy.d.ts.map +1 -0
  118. package/dist/src/name-policy.js +8 -0
  119. package/dist/src/name-policy.js.map +1 -0
  120. package/dist/src/refkey.d.ts +9 -0
  121. package/dist/src/refkey.d.ts.map +1 -0
  122. package/dist/src/refkey.js +44 -0
  123. package/dist/src/refkey.js.map +1 -0
  124. package/dist/src/render.d.ts +147 -0
  125. package/dist/src/render.d.ts.map +1 -0
  126. package/dist/src/render.js +317 -0
  127. package/dist/src/render.js.map +1 -0
  128. package/dist/src/tsdoc-metadata.json +11 -0
  129. package/dist/src/utils.d.ts +80 -0
  130. package/dist/src/utils.d.ts.map +1 -0
  131. package/dist/src/utils.js +219 -0
  132. package/dist/src/utils.js.map +1 -0
  133. package/dist/test/children.test.d.ts +2 -0
  134. package/dist/test/children.test.d.ts.map +1 -0
  135. package/dist/test/components/source-file.test.d.ts +2 -0
  136. package/dist/test/components/source-file.test.d.ts.map +1 -0
  137. package/dist/test/name-policy.test.d.ts +2 -0
  138. package/dist/test/name-policy.test.d.ts.map +1 -0
  139. package/dist/test/reactivity/ref-rendering.test.d.ts +2 -0
  140. package/dist/test/reactivity/ref-rendering.test.d.ts.map +1 -0
  141. package/dist/test/reactivity/test.test.d.ts +2 -0
  142. package/dist/test/reactivity/test.test.d.ts.map +1 -0
  143. package/dist/test/refkey.test.d.ts +2 -0
  144. package/dist/test/refkey.test.d.ts.map +1 -0
  145. package/dist/test/rendering/basic.test.d.ts +2 -0
  146. package/dist/test/rendering/basic.test.d.ts.map +1 -0
  147. package/dist/test/rendering/code.test.d.ts +2 -0
  148. package/dist/test/rendering/code.test.d.ts.map +1 -0
  149. package/dist/test/rendering/indent.test.d.ts +2 -0
  150. package/dist/test/rendering/indent.test.d.ts.map +1 -0
  151. package/dist/test/rendering/linebreaks.test.d.ts +2 -0
  152. package/dist/test/rendering/linebreaks.test.d.ts.map +1 -0
  153. package/dist/test/rendering/refkeys.test.d.ts +2 -0
  154. package/dist/test/rendering/refkeys.test.d.ts.map +1 -0
  155. package/dist/test/stc.test.d.ts +2 -0
  156. package/dist/test/stc.test.d.ts.map +1 -0
  157. package/dist/test/symbols.test.d.ts +2 -0
  158. package/dist/test/symbols.test.d.ts.map +1 -0
  159. package/dist/test/utils.test.d.ts +2 -0
  160. package/dist/test/utils.test.d.ts.map +1 -0
  161. package/dist/testing/extend-expect.d.ts +2 -0
  162. package/dist/testing/extend-expect.d.ts.map +1 -0
  163. package/dist/testing/extend-expect.js +22 -0
  164. package/dist/testing/extend-expect.js.map +1 -0
  165. package/dist/testing/index.d.ts +3 -0
  166. package/dist/testing/index.d.ts.map +1 -0
  167. package/dist/testing/index.js +3 -0
  168. package/dist/testing/index.js.map +1 -0
  169. package/dist/testing/render.d.ts +7 -0
  170. package/dist/testing/render.d.ts.map +1 -0
  171. package/dist/testing/render.js +25 -0
  172. package/dist/testing/render.js.map +1 -0
  173. package/dist/testing/vitest.d.js +1 -0
  174. package/dist/testing/vitest.d.js.map +1 -0
  175. package/dist/tsconfig.tsbuildinfo +1 -0
  176. package/package.json +64 -0
  177. package/src/binder.ts +838 -0
  178. package/src/code.ts +220 -0
  179. package/src/components/Declaration.tsx +53 -0
  180. package/src/components/Indent.tsx +33 -0
  181. package/src/components/MemberDeclaration.tsx +62 -0
  182. package/src/components/MemberName.tsx +11 -0
  183. package/src/components/MemberScope.tsx +40 -0
  184. package/src/components/Name.tsx +11 -0
  185. package/src/components/Output.tsx +69 -0
  186. package/src/components/Scope.tsx +27 -0
  187. package/src/components/SourceDirectory.tsx +43 -0
  188. package/src/components/SourceFile.tsx +33 -0
  189. package/src/components/index.tsx +10 -0
  190. package/src/components/stc/index.ts +9 -0
  191. package/src/context/assignment.ts +57 -0
  192. package/src/context/binder.ts +14 -0
  193. package/src/context/declaration.ts +5 -0
  194. package/src/context/indent.ts +10 -0
  195. package/src/context/index.ts +10 -0
  196. package/src/context/member-declaration.ts +10 -0
  197. package/src/context/member-scope.ts +17 -0
  198. package/src/context/name-policy.ts +13 -0
  199. package/src/context/scope.ts +8 -0
  200. package/src/context/source-directory.ts +11 -0
  201. package/src/context/source-file.ts +12 -0
  202. package/src/context.ts +53 -0
  203. package/src/index.ts +21 -0
  204. package/src/jsx-runtime.ts +266 -0
  205. package/src/name-policy.ts +13 -0
  206. package/src/refkey.ts +62 -0
  207. package/src/render.ts +389 -0
  208. package/src/utils.ts +288 -0
  209. package/temp/api.json +8840 -0
  210. package/test/children.test.tsx +33 -0
  211. package/test/components/source-file.test.tsx +45 -0
  212. package/test/name-policy.test.tsx +19 -0
  213. package/test/reactivity/ref-rendering.test.tsx +50 -0
  214. package/test/reactivity/test.test.tsx +83 -0
  215. package/test/refkey.test.ts +32 -0
  216. package/test/rendering/basic.test.tsx +156 -0
  217. package/test/rendering/code.test.tsx +62 -0
  218. package/test/rendering/indent.test.tsx +608 -0
  219. package/test/rendering/linebreaks.test.tsx +72 -0
  220. package/test/rendering/refkeys.test.tsx +35 -0
  221. package/test/stc.test.tsx +21 -0
  222. package/test/symbols.test.ts +406 -0
  223. package/test/utils.test.tsx +150 -0
  224. package/testing/extend-expect.ts +20 -0
  225. package/testing/index.ts +2 -0
  226. package/testing/render.ts +37 -0
  227. package/testing/vitest.d.ts +10 -0
  228. package/tsconfig.json +17 -0
  229. package/vitest.config.ts +18 -0
@@ -0,0 +1,406 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ Binder,
4
+ createOutputBinder,
5
+ OutputScope,
6
+ OutputScopeFlags,
7
+ OutputSymbol,
8
+ OutputSymbolFlags,
9
+ } from "../src/binder.js";
10
+ import { Refkey, refkey } from "../src/refkey.js";
11
+
12
+ it("works", () => {
13
+ const binder = createOutputBinder();
14
+ const scope = binder.createScope({
15
+ kind: "foo",
16
+ name: "scope",
17
+ parent: binder.globalScope,
18
+ });
19
+
20
+ const symbol = binder.createSymbol({
21
+ name: "sym",
22
+ scope,
23
+ refkey: "foo",
24
+ });
25
+
26
+ expect([...scope.getSymbolNames()]).toEqual(["sym"]);
27
+
28
+ symbol.name = "bar";
29
+
30
+ expect([...scope.getSymbolNames()]).toEqual(["bar"]);
31
+ });
32
+
33
+ it("resolves symbol conflicts", () => {
34
+ const binder = createOutputBinder();
35
+ const scope = binder.createScope({
36
+ kind: "foo",
37
+ name: "scope",
38
+ parent: binder.globalScope,
39
+ });
40
+
41
+ const _s1 = binder.createSymbol({
42
+ name: "sym",
43
+ scope,
44
+ refkey: "foo",
45
+ });
46
+
47
+ const s2 = binder.createSymbol({
48
+ name: "sym",
49
+ scope,
50
+ refkey: "foo",
51
+ });
52
+
53
+ expect(s2.name).toEqual("sym_2");
54
+ });
55
+
56
+ type ScopeRecords = Record<string, ScopeDescriptor>;
57
+ type SymbolRecords = Record<string, SymbolDescriptor>;
58
+
59
+ interface ScopeDescriptor {
60
+ flags?: OutputScopeFlags;
61
+ scopes?: ScopeRecords;
62
+ symbols: SymbolRecords;
63
+ }
64
+
65
+ interface SymbolDescriptor {
66
+ refkey?: Refkey;
67
+ flags?: OutputSymbolFlags;
68
+ instanceMembers?: SymbolRecords;
69
+ staticMembers?: SymbolRecords;
70
+ }
71
+
72
+ interface ScopeTreeResult {
73
+ symbols: Record<string, OutputSymbol>;
74
+ scopes: Record<string, OutputScope>;
75
+ }
76
+ function createScopeTree(binder: Binder, tree: ScopeRecords): ScopeTreeResult {
77
+ const createdItems: ScopeTreeResult = {
78
+ symbols: {},
79
+ scopes: {},
80
+ };
81
+
82
+ for (const [name, desc] of Object.entries(tree)) {
83
+ createScope(name, desc);
84
+ }
85
+
86
+ return createdItems;
87
+
88
+ function createScope(
89
+ name: string,
90
+ descriptor: ScopeDescriptor,
91
+ parent = binder.globalScope,
92
+ ) {
93
+ const scope = binder.createScope({
94
+ kind: "useless",
95
+ name,
96
+ parent,
97
+ flags: descriptor.flags ?? OutputScopeFlags.None,
98
+ });
99
+
100
+ createdItems.scopes[name] = scope;
101
+
102
+ for (const [name, desc] of Object.entries(descriptor.symbols)) {
103
+ createSymbol(name, desc, scope);
104
+ }
105
+
106
+ for (const [name, desc] of Object.entries(descriptor.scopes ?? {})) {
107
+ createScope(name, desc, scope);
108
+ }
109
+ }
110
+
111
+ function createSymbol(
112
+ name: string,
113
+ descriptor: SymbolDescriptor,
114
+ parent: OutputScope,
115
+ ) {
116
+ const symbol = binder.createSymbol({
117
+ name,
118
+ scope: parent,
119
+ refkey: descriptor.refkey ?? refkey(),
120
+ flags: descriptor.flags ?? OutputSymbolFlags.None,
121
+ });
122
+
123
+ createdItems.symbols[name] = symbol;
124
+
125
+ if (descriptor.instanceMembers) {
126
+ for (const [name, desc] of Object.entries(descriptor.instanceMembers)) {
127
+ createSymbol(name, desc, symbol.instanceMemberScope!);
128
+ }
129
+ }
130
+
131
+ if (descriptor.staticMembers) {
132
+ for (const [name, desc] of Object.entries(descriptor.staticMembers)) {
133
+ createSymbol(name, desc, symbol.staticMemberScope!);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ describe("static members", () => {
139
+ it("resolves static symbols", () => {
140
+ const binder = createOutputBinder();
141
+ const {
142
+ scopes: { root },
143
+ symbols: { root: rootSym, static: staticSym },
144
+ } = createScopeTree(binder, {
145
+ root: {
146
+ symbols: {
147
+ root: {
148
+ flags:
149
+ OutputSymbolFlags.InstanceMemberContainer |
150
+ OutputSymbolFlags.StaticMemberContainer,
151
+ staticMembers: {
152
+ static: {
153
+ flags: OutputSymbolFlags.StaticMember,
154
+ },
155
+ },
156
+ },
157
+ },
158
+ },
159
+ });
160
+
161
+ const resolution = binder.resolveDeclarationByKey(
162
+ root,
163
+ undefined,
164
+ staticSym.refkey,
165
+ );
166
+ expect(resolution.value).toBeDefined();
167
+ const { commonScope, pathUp, pathDown, targetDeclaration, memberPath } =
168
+ resolution.value!;
169
+ expect(commonScope).toBe(root);
170
+ expect(targetDeclaration).toBe(rootSym);
171
+ expect(pathDown).toEqual([]);
172
+ expect(pathUp).toEqual([]);
173
+ expect(memberPath!.map((s) => s.name)).toEqual(["root", "static"]);
174
+ });
175
+
176
+ it("resolves deeply nested static symbols", () => {
177
+ const binder = createOutputBinder();
178
+ const {
179
+ scopes: { root },
180
+ symbols: { root: rootSym, nested_static },
181
+ } = createScopeTree(binder, {
182
+ root: {
183
+ symbols: {
184
+ root: {
185
+ flags:
186
+ OutputSymbolFlags.InstanceMemberContainer |
187
+ OutputSymbolFlags.StaticMemberContainer,
188
+ staticMembers: {
189
+ static: {
190
+ flags:
191
+ OutputSymbolFlags.StaticMember |
192
+ OutputSymbolFlags.StaticMemberContainer,
193
+ staticMembers: {
194
+ nested_static: {
195
+ flags: OutputSymbolFlags.StaticMember,
196
+ },
197
+ },
198
+ },
199
+ },
200
+ },
201
+ },
202
+ },
203
+ });
204
+
205
+ const resolution = binder.resolveDeclarationByKey(
206
+ root,
207
+ undefined,
208
+ nested_static.refkey,
209
+ );
210
+ expect(resolution.value).toBeDefined();
211
+ const { commonScope, pathUp, pathDown, targetDeclaration, memberPath } =
212
+ resolution.value!;
213
+ expect(commonScope).toBe(root);
214
+ expect(targetDeclaration).toBe(rootSym);
215
+ expect(pathDown).toEqual([]);
216
+ expect(pathUp).toEqual([]);
217
+ expect(memberPath!.map((s) => s.name)).toEqual([
218
+ "root",
219
+ "static",
220
+ "nested_static",
221
+ ]);
222
+ });
223
+
224
+ it("resolves static symbols lazily", () => {
225
+ const staticSymRefkey = refkey();
226
+
227
+ const binder = createOutputBinder();
228
+ const {
229
+ scopes: { root },
230
+ symbols: { root: rootSym },
231
+ } = createScopeTree(binder, {
232
+ root: {
233
+ symbols: {
234
+ root: {
235
+ flags:
236
+ OutputSymbolFlags.InstanceMemberContainer |
237
+ OutputSymbolFlags.StaticMemberContainer,
238
+ },
239
+ },
240
+ },
241
+ });
242
+
243
+ const resolution = binder.resolveDeclarationByKey(
244
+ root,
245
+ undefined,
246
+ staticSymRefkey,
247
+ );
248
+ expect(resolution.value).toBeUndefined();
249
+ binder.createSymbol({
250
+ name: "static",
251
+ scope: rootSym.staticMemberScope!,
252
+ refkey: staticSymRefkey,
253
+ flags: OutputSymbolFlags.StaticMember,
254
+ });
255
+
256
+ expect(resolution.value).toBeDefined();
257
+ const { commonScope, pathUp, pathDown, targetDeclaration, memberPath } =
258
+ resolution.value!;
259
+ expect(commonScope).toBe(root);
260
+ expect(targetDeclaration).toBe(rootSym);
261
+ expect(pathDown).toEqual([]);
262
+ expect(pathUp).toEqual([]);
263
+ expect(memberPath!.map((s) => s.name)).toEqual(["root", "static"]);
264
+ });
265
+ });
266
+
267
+ describe("instance members", () => {
268
+ it("resolves", () => {
269
+ const binder = createOutputBinder();
270
+ const {
271
+ symbols: { root: rootSym, instance },
272
+ } = createScopeTree(binder, {
273
+ root: {
274
+ symbols: {
275
+ root: {
276
+ flags: OutputSymbolFlags.InstanceMemberContainer,
277
+ instanceMembers: {
278
+ instance: {
279
+ flags: OutputSymbolFlags.InstanceMember,
280
+ },
281
+ },
282
+ },
283
+ },
284
+ },
285
+ });
286
+
287
+ const resolution = binder.resolveDeclarationByKey(
288
+ undefined,
289
+ rootSym.instanceMemberScope!,
290
+ instance.refkey,
291
+ );
292
+ expect(resolution.value).toBeDefined();
293
+ const { commonScope, pathUp, pathDown, targetDeclaration, memberPath } =
294
+ resolution.value!;
295
+
296
+ expect(commonScope).toBe(rootSym.instanceMemberScope);
297
+ expect(targetDeclaration).toBe(instance);
298
+ expect(pathDown).toEqual([]);
299
+ expect(pathUp).toEqual([]);
300
+ expect(memberPath).toEqual([instance]);
301
+ });
302
+
303
+ it("doesn't resolve from outside the member scope", () => {
304
+ const binder = createOutputBinder();
305
+ const {
306
+ scopes: { root },
307
+ symbols: { instance },
308
+ } = createScopeTree(binder, {
309
+ root: {
310
+ symbols: {
311
+ root: {
312
+ flags: OutputSymbolFlags.InstanceMemberContainer,
313
+ instanceMembers: {
314
+ instance: {
315
+ flags: OutputSymbolFlags.InstanceMember,
316
+ },
317
+ },
318
+ },
319
+ },
320
+ },
321
+ });
322
+
323
+ expect(() =>
324
+ binder.resolveDeclarationByKey(root, undefined, instance.refkey),
325
+ ).toThrow(/Cannot resolve member symbols/);
326
+ });
327
+ });
328
+
329
+ describe("instantiating members", () => {
330
+ it("instantiates static symbols", () => {
331
+ const binder = createOutputBinder();
332
+ const {
333
+ symbols: { rootSymbol, instance, instantiation },
334
+ } = createScopeTree(binder, {
335
+ rootScope: {
336
+ symbols: {
337
+ rootSymbol: {
338
+ flags: OutputSymbolFlags.InstanceMemberContainer,
339
+ instanceMembers: {
340
+ instance: {
341
+ flags: OutputSymbolFlags.StaticMember,
342
+ },
343
+ },
344
+ },
345
+ instantiation: {},
346
+ },
347
+ },
348
+ });
349
+
350
+ binder.instantiateSymbolInto(rootSymbol, instantiation);
351
+ expect(
352
+ instantiation.flags & OutputSymbolFlags.InstanceMemberContainer,
353
+ ).toBeTruthy();
354
+ expect(instantiation.instanceMemberScope).toBeDefined();
355
+ const expectedRefkey = refkey(instantiation.refkey, instance.refkey);
356
+ expect(
357
+ instantiation.instanceMemberScope!.symbolsByRefkey.get(expectedRefkey),
358
+ ).toBeDefined();
359
+ });
360
+
361
+ it("instantiates static symbols that are added after the instantiation", () => {
362
+ const binder = createOutputBinder();
363
+ const {
364
+ symbols: { rootSymbol, instance, instantiation },
365
+ } = createScopeTree(binder, {
366
+ rootScope: {
367
+ symbols: {
368
+ rootSymbol: {
369
+ flags: OutputSymbolFlags.InstanceMemberContainer,
370
+ instanceMembers: {
371
+ instance: {
372
+ flags: OutputSymbolFlags.StaticMember,
373
+ },
374
+ },
375
+ },
376
+ instantiation: {},
377
+ },
378
+ },
379
+ });
380
+
381
+ binder.instantiateSymbolInto(rootSymbol, instantiation);
382
+ expect(
383
+ instantiation.flags & OutputSymbolFlags.InstanceMemberContainer,
384
+ ).toBeTruthy();
385
+ expect(instantiation.instanceMemberScope).toBeDefined();
386
+ const expectedRefkey = refkey(instantiation.refkey, instance.refkey);
387
+ expect(
388
+ instantiation.instanceMemberScope!.symbolsByRefkey.get(expectedRefkey),
389
+ ).toBeDefined();
390
+
391
+ const newInstanceMemberRefkey = refkey();
392
+ binder.createSymbol({
393
+ name: "newInstanceMember",
394
+ scope: rootSymbol.instanceMemberScope!,
395
+ refkey: newInstanceMemberRefkey,
396
+ flags: OutputSymbolFlags.InstanceMember,
397
+ });
398
+ const newExpectedRefkey = refkey(
399
+ instantiation.refkey,
400
+ newInstanceMemberRefkey,
401
+ );
402
+ expect(
403
+ instantiation.instanceMemberScope!.symbolsByRefkey.get(newExpectedRefkey),
404
+ ).toBeDefined();
405
+ });
406
+ });
@@ -0,0 +1,150 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { join, mapJoin } from "../src/utils.js";
3
+ import "../testing/extend-expect.js";
4
+
5
+ describe("mapJoin", () => {
6
+ it("can map a map", () => {
7
+ const map = new Map([
8
+ ["a", 1],
9
+ ["b", 2],
10
+ ]);
11
+
12
+ function Foo(props: { key: string; value: number }) {
13
+ return <>Key: {props.key}, Value: {props.value}</>;
14
+ }
15
+
16
+ const joined = mapJoin(map, (
17
+ key,
18
+ value,
19
+ ) => <Foo key={key} value={value} />);
20
+
21
+ expect(joined).toRenderTo(`
22
+ Key: a, Value: 1
23
+ Key: b, Value: 2
24
+ `);
25
+ });
26
+
27
+ it("can map an array", () => {
28
+ const arr = [1, 2];
29
+
30
+ function Foo(props: { value: number }) {
31
+ return <>Value: {props.value}</>;
32
+ }
33
+
34
+ const joined = mapJoin(arr, (value) => <Foo value={value} />);
35
+
36
+ expect(joined).toRenderTo(`
37
+ Value: 1
38
+ Value: 2
39
+ `);
40
+ });
41
+
42
+ it("can map a joiner", () => {
43
+ const arr = [1, 2];
44
+
45
+ function Foo(props: { value: number }) {
46
+ return <>Value: {props.value}</>;
47
+ }
48
+
49
+ const joined = mapJoin(arr, (value) => <Foo value={value} />, {
50
+ joiner: "-",
51
+ });
52
+
53
+ expect(joined).toRenderTo(`
54
+ Value: 1-Value: 2
55
+ `);
56
+ });
57
+
58
+ it("can map an ender", () => {
59
+ const arr = [1, 2];
60
+
61
+ function Foo(props: { value: number }) {
62
+ return <>Value: {props.value}</>;
63
+ }
64
+
65
+ const joined = mapJoin(arr, (value) => <Foo value={value} />, {
66
+ joiner: "-",
67
+ ender: ";",
68
+ });
69
+
70
+ expect(joined).toRenderTo(`
71
+ Value: 1-Value: 2;
72
+ `);
73
+ });
74
+
75
+ it("can map using an iterator", () => {
76
+ const arr = [1, 2].values();
77
+
78
+ function Foo(props: { value: number }) {
79
+ return <>Value: {props.value}</>;
80
+ }
81
+
82
+ const joined = mapJoin(arr, (value) => <Foo value={value} />, {
83
+ joiner: "-",
84
+ ender: ";",
85
+ });
86
+
87
+ expect(joined).toRenderTo(`
88
+ Value: 1-Value: 2;
89
+ `);
90
+ });
91
+ });
92
+
93
+ describe("join", () => {
94
+ it("can join an array", () => {
95
+ const arr = [<Foo value={1} />, <Foo value={2} />];
96
+
97
+ function Foo(props: { value: number }) {
98
+ return <>Value: {props.value}</>;
99
+ }
100
+
101
+ const joined = join(arr);
102
+
103
+ expect(joined).toRenderTo(`
104
+ Value: 1
105
+ Value: 2
106
+ `);
107
+ });
108
+
109
+ it("can join an array with a joiner", () => {
110
+ const arr = [<Foo value={1} />, <Foo value={2} />];
111
+
112
+ function Foo(props: { value: number }) {
113
+ return <>Value: {props.value}</>;
114
+ }
115
+
116
+ const joined = join(arr, { joiner: "-" });
117
+
118
+ expect(joined).toRenderTo(`
119
+ Value: 1-Value: 2
120
+ `);
121
+ });
122
+
123
+ it("can join an array with an ender", () => {
124
+ const arr = [<Foo value={1} />, <Foo value={2} />];
125
+
126
+ function Foo(props: { value: number }) {
127
+ return <>Value: {props.value}</>;
128
+ }
129
+
130
+ const joined = join(arr, { joiner: "-", ender: ";" });
131
+
132
+ expect(joined).toRenderTo(`
133
+ Value: 1-Value: 2;
134
+ `);
135
+ });
136
+
137
+ it("can join using an iterator", () => {
138
+ const arrIter = [<Foo value={1} />, <Foo value={2} />].values();
139
+
140
+ function Foo(props: { value: number }) {
141
+ return <>Value: {props.value}</>;
142
+ }
143
+
144
+ const joined = join(arrIter, { joiner: "-", ender: ";" });
145
+
146
+ expect(joined).toRenderTo(`
147
+ Value: 1-Value: 2;
148
+ `);
149
+ });
150
+ });
@@ -0,0 +1,20 @@
1
+ import { Children, renderTree } from "@alloy-js/core";
2
+ import { expect } from "vitest";
3
+ import { dedent, printTree } from "./render.js";
4
+
5
+ expect.extend({
6
+ toRenderTo(received: Children, expectedRaw: string) {
7
+ const { isNot } = this;
8
+ const tree = renderTree(received);
9
+ const actual = printTree(tree);
10
+ const expected = dedent(expectedRaw);
11
+ return {
12
+ pass: actual === expected,
13
+ message: () => {
14
+ return `Render is${isNot ? " not" : ""} incorrect`;
15
+ },
16
+ actual,
17
+ expected,
18
+ };
19
+ },
20
+ });
@@ -0,0 +1,2 @@
1
+ import "./extend-expect.js";
2
+ export * from "./render.js";
@@ -0,0 +1,37 @@
1
+ import { RenderTextTree, renderTree } from "@alloy-js/core";
2
+ import { Children } from "@alloy-js/core/jsx-runtime";
3
+
4
+ export function printTree(tree: RenderTextTree) {
5
+ return (tree as any).flat(Infinity).join("");
6
+ }
7
+
8
+ export function renderToString(element: Children) {
9
+ return printTree(renderTree(element));
10
+ }
11
+
12
+ export function d(strings: TemplateStringsArray, ...values: any[]): string {
13
+ // Combine the strings and values
14
+ const result = strings.reduce(
15
+ (acc, str, i) => acc + str + (values[i] ?? ""),
16
+ "",
17
+ );
18
+
19
+ return dedent(result);
20
+ }
21
+
22
+ export function dedent(str: string): string {
23
+ // Remove leading and trailing line breaks
24
+ str = str.replace(/^\n|\n[ ]*$/g, "");
25
+
26
+ // Find the indent of the first line
27
+ const match = str.match(/^[ \t]+/);
28
+ const indent = match ? match[0] : "";
29
+
30
+ // Remove the indent from each line
31
+ const dedented = str
32
+ .split("\n")
33
+ .map((line) => (line.startsWith(indent) ? line.slice(indent.length) : line))
34
+ .join("\n");
35
+
36
+ return dedented;
37
+ }
@@ -0,0 +1,10 @@
1
+ import "vitest";
2
+
3
+ interface CustomMatchers<R = unknown> {
4
+ toRenderTo: (str: string) => R;
5
+ }
6
+
7
+ declare module "vitest" {
8
+ interface Assertion<T = any> extends CustomMatchers<T> {}
9
+ interface AsymmetricMatchersContaining extends CustomMatchers {}
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "emitDeclarationOnly": true,
5
+ "declaration": true,
6
+ "outDir": "dist"
7
+ },
8
+ "include": [
9
+ "src/**/*.ts",
10
+ "src/**/*.tsx",
11
+ "test/**/*.ts",
12
+ "test/**/*.tsx",
13
+ "testing/**/*.ts",
14
+ "testing/**/*.d.ts"
15
+ ],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
@@ -0,0 +1,18 @@
1
+ import { babel } from "@rollup/plugin-babel";
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig({
5
+ esbuild: {
6
+ jsx: "preserve",
7
+ sourcemap: "both",
8
+ },
9
+ plugins: [
10
+ babel({
11
+ inputSourceMap: true as any,
12
+ sourceMaps: "both",
13
+ babelHelpers: "bundled",
14
+ extensions: [".ts", ".tsx"],
15
+ presets: ["@babel/preset-typescript", ["@alloy-js/babel-preset", {}]],
16
+ }),
17
+ ],
18
+ });