@tsonic/emitter 0.0.1

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 (209) hide show
  1. package/package.json +34 -0
  2. package/scripts/update-golden-tests.ts +119 -0
  3. package/src/adapter-generator.ts +112 -0
  4. package/src/array.test.ts +301 -0
  5. package/src/constants.ts +32 -0
  6. package/src/core/exports.ts +36 -0
  7. package/src/core/imports.test.ts +83 -0
  8. package/src/core/imports.ts +243 -0
  9. package/src/core/index.ts +9 -0
  10. package/src/core/module-emitter/assembly.ts +83 -0
  11. package/src/core/module-emitter/header.ts +19 -0
  12. package/src/core/module-emitter/index.ts +17 -0
  13. package/src/core/module-emitter/namespace.ts +39 -0
  14. package/src/core/module-emitter/orchestrator.ts +98 -0
  15. package/src/core/module-emitter/separation.ts +41 -0
  16. package/src/core/module-emitter/static-container.ts +75 -0
  17. package/src/core/module-emitter.test.ts +154 -0
  18. package/src/core/module-emitter.ts +6 -0
  19. package/src/core/module-map.ts +218 -0
  20. package/src/core/options.ts +16 -0
  21. package/src/core/type-params.ts +34 -0
  22. package/src/emitter-types/context.ts +91 -0
  23. package/src/emitter-types/core.ts +138 -0
  24. package/src/emitter-types/csharp-types.ts +42 -0
  25. package/src/emitter-types/formatting.ts +13 -0
  26. package/src/emitter-types/fqn.ts +81 -0
  27. package/src/emitter-types/index.ts +31 -0
  28. package/src/emitter.ts +107 -0
  29. package/src/expression-emitter.ts +112 -0
  30. package/src/expressions/access.ts +104 -0
  31. package/src/expressions/calls.ts +264 -0
  32. package/src/expressions/collections.ts +354 -0
  33. package/src/expressions/functions.ts +71 -0
  34. package/src/expressions/identifiers.ts +125 -0
  35. package/src/expressions/index.test.ts +515 -0
  36. package/src/expressions/index.ts +38 -0
  37. package/src/expressions/literals.ts +138 -0
  38. package/src/expressions/operators.ts +211 -0
  39. package/src/expressions/other.ts +63 -0
  40. package/src/generator-exchange.ts +120 -0
  41. package/src/generator.test.ts +193 -0
  42. package/src/golden-tests/config-parser.ts +67 -0
  43. package/src/golden-tests/discovery.ts +61 -0
  44. package/src/golden-tests/index.ts +10 -0
  45. package/src/golden-tests/registration.ts +26 -0
  46. package/src/golden-tests/runner.ts +131 -0
  47. package/src/golden-tests/tree-builder.ts +43 -0
  48. package/src/golden-tests/types.ts +21 -0
  49. package/src/golden.test.ts +40 -0
  50. package/src/hierarchical-bindings.test.ts +258 -0
  51. package/src/index.ts +14 -0
  52. package/src/integration.test.ts +303 -0
  53. package/src/specialization/call-site-rewriting.test.ts +99 -0
  54. package/src/specialization/collection/expressions.ts +184 -0
  55. package/src/specialization/collection/index.ts +7 -0
  56. package/src/specialization/collection/orchestrator.ts +25 -0
  57. package/src/specialization/collection/statements.ts +91 -0
  58. package/src/specialization/collection.ts +10 -0
  59. package/src/specialization/generation.ts +189 -0
  60. package/src/specialization/generic-classes.test.ts +59 -0
  61. package/src/specialization/generic-functions.test.ts +292 -0
  62. package/src/specialization/helpers.ts +39 -0
  63. package/src/specialization/index.ts +28 -0
  64. package/src/specialization/interfaces.test.ts +151 -0
  65. package/src/specialization/naming.ts +34 -0
  66. package/src/specialization/substitution.ts +186 -0
  67. package/src/specialization/type-aliases.test.ts +134 -0
  68. package/src/specialization/types.ts +19 -0
  69. package/src/specialization-generator.ts +23 -0
  70. package/src/statement-emitter.ts +117 -0
  71. package/src/statements/blocks.ts +115 -0
  72. package/src/statements/classes/helpers.ts +9 -0
  73. package/src/statements/classes/index.ts +13 -0
  74. package/src/statements/classes/inline-types.ts +79 -0
  75. package/src/statements/classes/members/constructors.ts +123 -0
  76. package/src/statements/classes/members/index.ts +8 -0
  77. package/src/statements/classes/members/methods.ts +137 -0
  78. package/src/statements/classes/members/orchestrator.ts +33 -0
  79. package/src/statements/classes/members/properties.ts +62 -0
  80. package/src/statements/classes/members.ts +6 -0
  81. package/src/statements/classes/parameters.ts +69 -0
  82. package/src/statements/classes/properties.ts +95 -0
  83. package/src/statements/classes.ts +14 -0
  84. package/src/statements/control/conditionals.ts +134 -0
  85. package/src/statements/control/exceptions.ts +59 -0
  86. package/src/statements/control/index.ts +11 -0
  87. package/src/statements/control/loops.ts +250 -0
  88. package/src/statements/control.ts +14 -0
  89. package/src/statements/declarations/classes.ts +89 -0
  90. package/src/statements/declarations/enums.ts +32 -0
  91. package/src/statements/declarations/functions.ts +147 -0
  92. package/src/statements/declarations/index.ts +10 -0
  93. package/src/statements/declarations/interfaces.ts +116 -0
  94. package/src/statements/declarations/structs.test.ts +182 -0
  95. package/src/statements/declarations/type-aliases.ts +104 -0
  96. package/src/statements/declarations/variables.ts +159 -0
  97. package/src/statements/declarations.ts +13 -0
  98. package/src/statements/index.test.ts +258 -0
  99. package/src/statements/index.ts +43 -0
  100. package/src/type-assertion.test.ts +143 -0
  101. package/src/type-emitter.ts +18 -0
  102. package/src/types/arrays.ts +21 -0
  103. package/src/types/dictionaries.ts +52 -0
  104. package/src/types/emitter.ts +76 -0
  105. package/src/types/functions.ts +45 -0
  106. package/src/types/index.test.ts +116 -0
  107. package/src/types/index.ts +14 -0
  108. package/src/types/intersections.ts +19 -0
  109. package/src/types/literals.ts +26 -0
  110. package/src/types/objects.ts +27 -0
  111. package/src/types/parameters.test.ts +146 -0
  112. package/src/types/parameters.ts +95 -0
  113. package/src/types/primitives.ts +24 -0
  114. package/src/types/references.ts +187 -0
  115. package/src/types/unions.test.ts +397 -0
  116. package/src/types/unions.ts +62 -0
  117. package/src/types.ts +33 -0
  118. package/testcases/README.md +213 -0
  119. package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
  120. package/testcases/arrays/basic/config.yaml +1 -0
  121. package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
  122. package/testcases/arrays/destructuring/config.yaml +1 -0
  123. package/testcases/arrays/methods/ArrayMethods.ts +6 -0
  124. package/testcases/arrays/methods/config.yaml +1 -0
  125. package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
  126. package/testcases/arrays/multidimensional/config.yaml +1 -0
  127. package/testcases/arrays/spread/ArraySpread.ts +3 -0
  128. package/testcases/arrays/spread/config.yaml +1 -0
  129. package/testcases/async/basic/AsyncFunction.ts +5 -0
  130. package/testcases/async/basic/config.yaml +1 -0
  131. package/testcases/classes/abstract/AbstractClasses.ts +53 -0
  132. package/testcases/classes/abstract/config.yaml +1 -0
  133. package/testcases/classes/basic/Person.ts +12 -0
  134. package/testcases/classes/basic/config.yaml +1 -0
  135. package/testcases/classes/constructor/User.ts +11 -0
  136. package/testcases/classes/constructor/config.yaml +1 -0
  137. package/testcases/classes/field-inference/Counter.ts +11 -0
  138. package/testcases/classes/field-inference/config.yaml +1 -0
  139. package/testcases/classes/inheritance/Inheritance.ts +24 -0
  140. package/testcases/classes/inheritance/config.yaml +1 -0
  141. package/testcases/classes/static-members/MathHelper.ts +12 -0
  142. package/testcases/classes/static-members/config.yaml +1 -0
  143. package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
  144. package/testcases/control-flow/error-handling/config.yaml +1 -0
  145. package/testcases/control-flow/loops/Loops.ts +21 -0
  146. package/testcases/control-flow/loops/config.yaml +1 -0
  147. package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
  148. package/testcases/control-flow/switch/config.yaml +1 -0
  149. package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
  150. package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
  151. package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
  152. package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
  153. package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
  154. package/testcases/edge-cases/shadowing/config.yaml +1 -0
  155. package/testcases/functions/arrow/ArrowFunction.ts +5 -0
  156. package/testcases/functions/arrow/config.yaml +1 -0
  157. package/testcases/functions/basic/Greet.ts +3 -0
  158. package/testcases/functions/basic/config.yaml +1 -0
  159. package/testcases/functions/closures/Closures.ts +11 -0
  160. package/testcases/functions/closures/config.yaml +1 -0
  161. package/testcases/functions/default-params/DefaultParams.ts +7 -0
  162. package/testcases/functions/default-params/config.yaml +1 -0
  163. package/testcases/functions/rest-params/RestParams.ts +7 -0
  164. package/testcases/functions/rest-params/config.yaml +1 -0
  165. package/testcases/functions/type-guards/TypeGuards.ts +52 -0
  166. package/testcases/functions/type-guards/config.yaml +1 -0
  167. package/testcases/operators/logical/LogicalOperators.ts +11 -0
  168. package/testcases/operators/logical/config.yaml +1 -0
  169. package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
  170. package/testcases/operators/nullish-coalescing/config.yaml +1 -0
  171. package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
  172. package/testcases/operators/optional-chaining/config.yaml +1 -0
  173. package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
  174. package/testcases/real-world/advanced-generics/config.yaml +1 -0
  175. package/testcases/real-world/async-ops/async-ops.ts +67 -0
  176. package/testcases/real-world/async-ops/config.yaml +1 -0
  177. package/testcases/real-world/business-logic/business-logic.ts +215 -0
  178. package/testcases/real-world/business-logic/config.yaml +1 -0
  179. package/testcases/real-world/calculator/calculator.ts +29 -0
  180. package/testcases/real-world/calculator/config.yaml +1 -0
  181. package/testcases/real-world/data-structures/config.yaml +1 -0
  182. package/testcases/real-world/data-structures/data-structures.ts +133 -0
  183. package/testcases/real-world/functional/config.yaml +1 -0
  184. package/testcases/real-world/functional/functional.ts +116 -0
  185. package/testcases/real-world/shapes/config.yaml +1 -0
  186. package/testcases/real-world/shapes/shapes.ts +87 -0
  187. package/testcases/real-world/string-utils/config.yaml +1 -0
  188. package/testcases/real-world/string-utils/string-utils.ts +47 -0
  189. package/testcases/real-world/todo-list/config.yaml +1 -0
  190. package/testcases/real-world/todo-list/todo-list.ts +52 -0
  191. package/testcases/real-world/type-guards/config.yaml +1 -0
  192. package/testcases/real-world/type-guards/type-guards.ts +71 -0
  193. package/testcases/structs/basic/Point.ts +9 -0
  194. package/testcases/structs/basic/config.yaml +1 -0
  195. package/testcases/types/conditional/ConditionalTypes.ts +35 -0
  196. package/testcases/types/conditional/config.yaml +1 -0
  197. package/testcases/types/constants/ModuleConstants.ts +6 -0
  198. package/testcases/types/constants/config.yaml +1 -0
  199. package/testcases/types/generics/Generics.ts +15 -0
  200. package/testcases/types/generics/config.yaml +1 -0
  201. package/testcases/types/interfaces/Interfaces.ts +14 -0
  202. package/testcases/types/interfaces/config.yaml +1 -0
  203. package/testcases/types/mapped/MappedTypes.ts +27 -0
  204. package/testcases/types/mapped/config.yaml +1 -0
  205. package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
  206. package/testcases/types/tuples-intersections/config.yaml +1 -0
  207. package/testcases/types/unions/UnionTypes.ts +11 -0
  208. package/testcases/types/unions/config.yaml +1 -0
  209. package/tsconfig.json +14 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Full pipeline test: TypeScript -> IR -> C#
3
+ * Tests hierarchical bindings end-to-end
4
+ */
5
+
6
+ import { describe, it } from "mocha";
7
+ import { expect } from "chai";
8
+ import * as ts from "typescript";
9
+ import {
10
+ buildIrModule,
11
+ DotnetMetadataRegistry,
12
+ BindingRegistry,
13
+ createDotNetImportResolver,
14
+ } from "@tsonic/frontend";
15
+ import { emitModule } from "./emitter.js";
16
+
17
+ describe("Hierarchical Bindings - Full Pipeline", () => {
18
+ it("should compile TypeScript with hierarchical bindings to correct C#", () => {
19
+ const source = `
20
+ export function processData() {
21
+ const arr = [1, 2, 3];
22
+ const result = systemLinq.enumerable.selectMany(arr, x => [x, x * 2]);
23
+ return result;
24
+ }
25
+ `;
26
+
27
+ // Create hierarchical binding manifest
28
+ const bindings = new BindingRegistry();
29
+ bindings.addBindings("/test/system-linq.json", {
30
+ assembly: "System.Linq",
31
+ namespaces: [
32
+ {
33
+ name: "System.Linq",
34
+ alias: "systemLinq",
35
+ types: [
36
+ {
37
+ name: "Enumerable",
38
+ alias: "enumerable",
39
+ kind: "class",
40
+ members: [
41
+ {
42
+ kind: "method",
43
+ name: "SelectMany",
44
+ alias: "selectMany",
45
+ binding: {
46
+ assembly: "System.Linq",
47
+ type: "System.Linq.Enumerable",
48
+ member: "SelectMany",
49
+ },
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ ],
56
+ });
57
+
58
+ // Create TypeScript program
59
+ const fileName = "/test/sample.ts";
60
+ const sourceFile = ts.createSourceFile(
61
+ fileName,
62
+ source,
63
+ ts.ScriptTarget.ES2022,
64
+ true,
65
+ ts.ScriptKind.TS
66
+ );
67
+
68
+ const program = ts.createProgram(
69
+ [fileName],
70
+ {
71
+ target: ts.ScriptTarget.ES2022,
72
+ module: ts.ModuleKind.ES2022,
73
+ },
74
+ {
75
+ getSourceFile: (name) => (name === fileName ? sourceFile : undefined),
76
+ writeFile: () => {},
77
+ getCurrentDirectory: () => "/test",
78
+ getDirectories: () => [],
79
+ fileExists: () => true,
80
+ readFile: () => source,
81
+ getCanonicalFileName: (f) => f,
82
+ useCaseSensitiveFileNames: () => true,
83
+ getNewLine: () => "\n",
84
+ getDefaultLibFileName: (_options) => "lib.d.ts",
85
+ }
86
+ );
87
+
88
+ const testProgram = {
89
+ program,
90
+ checker: program.getTypeChecker(),
91
+ options: { sourceRoot: "/test", rootNamespace: "TestApp", strict: true },
92
+ sourceFiles: [sourceFile],
93
+ metadata: new DotnetMetadataRegistry(),
94
+ bindings,
95
+ dotnetResolver: createDotNetImportResolver("/test"),
96
+ };
97
+
98
+ // Step 1: Build IR
99
+ const irResult = buildIrModule(sourceFile, testProgram, {
100
+ sourceRoot: "/test",
101
+ rootNamespace: "TestApp",
102
+ });
103
+
104
+ if (!irResult.ok) {
105
+ throw new Error(
106
+ `IR build must succeed for full pipeline test: ${JSON.stringify(irResult.error)}`
107
+ );
108
+ }
109
+
110
+ const irModule = irResult.value;
111
+
112
+ // Step 2: Emit C# code
113
+ const csharpCode = emitModule(irModule);
114
+
115
+ // Verify correct CLR member call
116
+ expect(csharpCode).to.include(
117
+ "System.Linq.Enumerable.SelectMany",
118
+ "C# should use full CLR type.member from binding"
119
+ );
120
+
121
+ // Verify using statement was added
122
+ expect(csharpCode).to.include(
123
+ "using System.Linq",
124
+ "C# should include using for assembly"
125
+ );
126
+
127
+ // Verify intermediate TypeScript names are NOT in output
128
+ expect(csharpCode).to.not.include(
129
+ "systemLinq",
130
+ "C# should not contain TS namespace identifier"
131
+ );
132
+ expect(csharpCode).to.not.include(
133
+ "enumerable",
134
+ "C# should not contain TS type identifier"
135
+ );
136
+
137
+ // Verify function structure (may be void if return type not inferred)
138
+ expect(csharpCode).to.match(
139
+ /public static (void|object) processData\(/,
140
+ "C# should have processData function"
141
+ );
142
+ });
143
+
144
+ it("should handle multiple hierarchical bindings in same code", () => {
145
+ const source = `
146
+ export function multipleBindings() {
147
+ const a = myLib.typeA.methodA(1);
148
+ const b = myLib.typeB.methodB("test");
149
+ return a + b;
150
+ }
151
+ `;
152
+
153
+ const bindings = new BindingRegistry();
154
+ bindings.addBindings("/test/mylib.json", {
155
+ assembly: "MyLib",
156
+ namespaces: [
157
+ {
158
+ name: "MyLib",
159
+ alias: "myLib",
160
+ types: [
161
+ {
162
+ name: "TypeA",
163
+ alias: "typeA",
164
+ kind: "class",
165
+ members: [
166
+ {
167
+ kind: "method",
168
+ name: "MethodA",
169
+ alias: "methodA",
170
+ binding: {
171
+ assembly: "MyLib",
172
+ type: "MyLib.TypeA",
173
+ member: "MethodA",
174
+ },
175
+ },
176
+ ],
177
+ },
178
+ {
179
+ name: "TypeB",
180
+ alias: "typeB",
181
+ kind: "class",
182
+ members: [
183
+ {
184
+ kind: "method",
185
+ name: "MethodB",
186
+ alias: "methodB",
187
+ binding: {
188
+ assembly: "MyLib",
189
+ type: "MyLib.TypeB",
190
+ member: "MethodB",
191
+ },
192
+ },
193
+ ],
194
+ },
195
+ ],
196
+ },
197
+ ],
198
+ });
199
+
200
+ const fileName = "/test/multi.ts";
201
+ const sourceFile = ts.createSourceFile(
202
+ fileName,
203
+ source,
204
+ ts.ScriptTarget.ES2022,
205
+ true
206
+ );
207
+
208
+ const program = ts.createProgram(
209
+ [fileName],
210
+ {
211
+ target: ts.ScriptTarget.ES2022,
212
+ module: ts.ModuleKind.ES2022,
213
+ },
214
+ {
215
+ getSourceFile: (name) => (name === fileName ? sourceFile : undefined),
216
+ writeFile: () => {},
217
+ getCurrentDirectory: () => "/test",
218
+ getDirectories: () => [],
219
+ fileExists: () => true,
220
+ readFile: () => source,
221
+ getCanonicalFileName: (f) => f,
222
+ useCaseSensitiveFileNames: () => true,
223
+ getNewLine: () => "\n",
224
+ getDefaultLibFileName: (_options) => "lib.d.ts",
225
+ }
226
+ );
227
+
228
+ const testProgram = {
229
+ program,
230
+ checker: program.getTypeChecker(),
231
+ options: { sourceRoot: "/test", rootNamespace: "TestApp", strict: true },
232
+ sourceFiles: [sourceFile],
233
+ metadata: new DotnetMetadataRegistry(),
234
+ bindings,
235
+ dotnetResolver: createDotNetImportResolver("/test"),
236
+ };
237
+
238
+ const irResult = buildIrModule(sourceFile, testProgram, {
239
+ sourceRoot: "/test",
240
+ rootNamespace: "TestApp",
241
+ });
242
+
243
+ if (!irResult.ok) {
244
+ throw new Error("IR build failed for multiple bindings test");
245
+ }
246
+
247
+ const csharpCode = emitModule(irResult.value);
248
+
249
+ // Both CLR calls should be present
250
+ expect(csharpCode).to.include("MyLib.TypeA.MethodA");
251
+ expect(csharpCode).to.include("MyLib.TypeB.MethodB");
252
+
253
+ // TypeScript identifiers should not appear
254
+ expect(csharpCode).to.not.include("myLib");
255
+ expect(csharpCode).to.not.include("typeA");
256
+ expect(csharpCode).to.not.include("typeB");
257
+ });
258
+ });
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tsonic Emitter - C# code generator
3
+ */
4
+
5
+ export * from "./types.js";
6
+ export * from "./type-emitter.js";
7
+ export * from "./expression-emitter.js";
8
+ export * from "./statement-emitter.js";
9
+ export {
10
+ emitModule,
11
+ emitCSharpFile,
12
+ emitCSharpFiles,
13
+ EmitResult,
14
+ } from "./emitter.js";
@@ -0,0 +1,303 @@
1
+ /**
2
+ * End-to-end integration tests for generics implementation
3
+ * Tests the complete pipeline: TypeScript -> IR -> C#
4
+ */
5
+
6
+ import { describe, it } from "mocha";
7
+ import { expect } from "chai";
8
+ import * as ts from "typescript";
9
+ import {
10
+ buildIrModule,
11
+ DotnetMetadataRegistry,
12
+ BindingRegistry,
13
+ createDotNetImportResolver,
14
+ } from "@tsonic/frontend";
15
+ import { emitModule } from "./emitter.js";
16
+
17
+ /**
18
+ * Helper to compile TypeScript source to C#
19
+ */
20
+ const compileToCSharp = (
21
+ source: string,
22
+ fileName = "/test/test.ts"
23
+ ): string => {
24
+ const sourceFile = ts.createSourceFile(
25
+ fileName,
26
+ source,
27
+ ts.ScriptTarget.Latest,
28
+ true,
29
+ ts.ScriptKind.TS
30
+ );
31
+
32
+ const compilerOptions: ts.CompilerOptions = {
33
+ target: ts.ScriptTarget.ES2022,
34
+ module: ts.ModuleKind.NodeNext,
35
+ strict: true,
36
+ noEmit: true,
37
+ };
38
+
39
+ const host = ts.createCompilerHost(compilerOptions);
40
+ const originalGetSourceFile = host.getSourceFile;
41
+ host.getSourceFile = (
42
+ name: string,
43
+ languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,
44
+ onError?: (message: string) => void,
45
+ shouldCreateNewSourceFile?: boolean
46
+ ) => {
47
+ if (name === fileName) {
48
+ return sourceFile;
49
+ }
50
+ return originalGetSourceFile.call(
51
+ host,
52
+ name,
53
+ languageVersionOrOptions,
54
+ onError,
55
+ shouldCreateNewSourceFile
56
+ );
57
+ };
58
+
59
+ const tsProgram = ts.createProgram([fileName], compilerOptions, host);
60
+
61
+ const tsonicProgram = {
62
+ program: tsProgram,
63
+ checker: tsProgram.getTypeChecker(),
64
+ options: {
65
+ sourceRoot: "/test",
66
+ rootNamespace: "Test",
67
+ },
68
+ sourceFiles: [sourceFile],
69
+ metadata: new DotnetMetadataRegistry(),
70
+ bindings: new BindingRegistry(),
71
+ dotnetResolver: createDotNetImportResolver("/test"),
72
+ };
73
+
74
+ // Build IR
75
+ const irResult = buildIrModule(sourceFile, tsonicProgram, {
76
+ sourceRoot: "/test",
77
+ rootNamespace: "Test",
78
+ });
79
+
80
+ if (!irResult.ok) {
81
+ throw new Error(`IR build failed: ${irResult.error.message}`);
82
+ }
83
+
84
+ // Emit C#
85
+ return emitModule(irResult.value);
86
+ };
87
+
88
+ describe("End-to-End Integration", () => {
89
+ describe("Generic Functions", () => {
90
+ it("should compile generic identity function to C#", () => {
91
+ const source = `
92
+ export function identity<T>(value: T): T {
93
+ return value;
94
+ }
95
+ `;
96
+
97
+ const csharp = compileToCSharp(source);
98
+
99
+ // Should emit generic function signature
100
+ expect(csharp).to.match(/public\s+static\s+T\s+identity\s*<T>/);
101
+ expect(csharp).to.include("(T value)");
102
+ expect(csharp).to.include("return value;");
103
+ });
104
+
105
+ it("should compile generic function with type alias constraint", () => {
106
+ const source = `
107
+ type HasId = { id: number };
108
+
109
+ export function getId<T extends HasId>(obj: T): number {
110
+ return obj.id;
111
+ }
112
+ `;
113
+
114
+ const csharp = compileToCSharp(source);
115
+
116
+ // Should emit type alias as class
117
+ expect(csharp).to.include("class HasId__Alias");
118
+ expect(csharp).to.match(/double\s+id\s*\{\s*get;\s*set;/);
119
+
120
+ // Should use type alias as constraint
121
+ expect(csharp).to.include("where T : HasId");
122
+
123
+ // Should have function
124
+ expect(csharp).to.match(/public\s+static\s+double\s+getId<T>/);
125
+ });
126
+ });
127
+
128
+ describe("Interfaces and Type Aliases", () => {
129
+ it("should compile interface to C# class", () => {
130
+ const source = `
131
+ export interface User {
132
+ id: number;
133
+ name: string;
134
+ email?: string;
135
+ }
136
+ `;
137
+
138
+ const csharp = compileToCSharp(source);
139
+
140
+ // Should emit C# class (not interface)
141
+ expect(csharp).to.match(/public\s+class\s+User/);
142
+ expect(csharp).not.to.include("interface User");
143
+
144
+ // Should have auto-properties
145
+ expect(csharp).to.match(/public\s+double\s+id\s*\{\s*get;\s*set;/);
146
+ expect(csharp).to.match(/public\s+string\s+name\s*\{\s*get;\s*set;/);
147
+
148
+ // Optional property should be nullable
149
+ expect(csharp).to.match(/public\s+string\?\s+email\s*\{\s*get;\s*set;/);
150
+ });
151
+
152
+ it("should compile structural type alias to sealed class", () => {
153
+ const source = `
154
+ export type Point = {
155
+ x: number;
156
+ y: number;
157
+ };
158
+ `;
159
+
160
+ const csharp = compileToCSharp(source);
161
+
162
+ // Should emit sealed class with __Alias suffix
163
+ expect(csharp).to.match(/public\s+sealed\s+class\s+Point__Alias/);
164
+ expect(csharp).to.match(/public\s+double\s+x\s*\{\s*get;\s*set;/);
165
+ expect(csharp).to.match(/public\s+double\s+y\s*\{\s*get;\s*set;/);
166
+ });
167
+
168
+ it("should compile generic interface", () => {
169
+ const source = `
170
+ export interface Result<T> {
171
+ ok: boolean;
172
+ value: T;
173
+ }
174
+ `;
175
+
176
+ const csharp = compileToCSharp(source);
177
+
178
+ // Should emit generic class
179
+ expect(csharp).to.match(/public\s+class\s+Result\s*<T>/);
180
+ expect(csharp).to.match(/public\s+bool\s+ok/);
181
+ expect(csharp).to.match(/public\s+T\s+value/);
182
+ });
183
+ });
184
+
185
+ describe("Generic Classes", () => {
186
+ it("should compile generic class with methods", () => {
187
+ const source = `
188
+ export class Container<T> {
189
+ constructor(private value: T) {}
190
+
191
+ getValue(): T {
192
+ return this.value;
193
+ }
194
+
195
+ setValue(newValue: T): void {
196
+ this.value = newValue;
197
+ }
198
+ }
199
+ `;
200
+
201
+ const csharp = compileToCSharp(source);
202
+
203
+ // Should emit generic class
204
+ expect(csharp).to.match(/public\s+class\s+Container\s*<T>/);
205
+
206
+ // Should have generic methods
207
+ expect(csharp).to.match(/public\s+T\s+getValue\s*\(\s*\)/);
208
+ expect(csharp).to.match(
209
+ /public\s+void\s+setValue\s*\(\s*T\s+newValue\s*\)/
210
+ );
211
+ });
212
+ });
213
+
214
+ describe("Combined Features", () => {
215
+ it("should compile code with multiple generic features", () => {
216
+ const source = `
217
+ export interface Repository<T> {
218
+ items: T[];
219
+ add(item: T): void;
220
+ findById(id: number): T | undefined;
221
+ }
222
+
223
+ export class InMemoryRepository<T extends { id: number }> {
224
+ private items: T[] = [];
225
+
226
+ add(item: T): void {
227
+ this.items.push(item);
228
+ }
229
+
230
+ findById(id: number): T | undefined {
231
+ return this.items.find(item => item.id === id);
232
+ }
233
+ }
234
+ `;
235
+
236
+ const csharp = compileToCSharp(source);
237
+
238
+ // Should emit Repository as generic class (interfaces become classes)
239
+ expect(csharp).to.match(/public\s+class\s+Repository\s*<T>/);
240
+
241
+ // Should emit InMemoryRepository as generic class with constraint
242
+ expect(csharp).to.match(/public\s+class\s+InMemoryRepository\s*<T>/);
243
+ expect(csharp).to.include("where T : __Constraint_T");
244
+
245
+ // Should generate constraint adapter
246
+ expect(csharp).to.match(/public\s+interface\s+__Constraint_T/);
247
+ expect(csharp).to.match(/double\s+id\s*\{\s*get;\s*\}/);
248
+ });
249
+ });
250
+
251
+ describe("Full Module Compilation", () => {
252
+ it("should compile a complete module with all features", () => {
253
+ const source = `
254
+ // Type definitions
255
+ export interface User {
256
+ id: number;
257
+ name: string;
258
+ email?: string;
259
+ }
260
+
261
+ export type UserId = number;
262
+
263
+ // Generic repository
264
+ export class UserRepository {
265
+ private users: User[] = [];
266
+
267
+ add(user: User): void {
268
+ this.users.push(user);
269
+ }
270
+
271
+ findById(id: UserId): User | undefined {
272
+ return this.users.find(u => u.id === id);
273
+ }
274
+
275
+ all(): User[] {
276
+ return this.users;
277
+ }
278
+ }
279
+
280
+ // Generic utility function
281
+ export function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
282
+ return arr.map(fn);
283
+ }
284
+ `;
285
+
286
+ const csharp = compileToCSharp(source);
287
+
288
+ // Should have all type definitions
289
+ expect(csharp).to.include("class User");
290
+ expect(csharp).to.include("// type UserId = double"); // number → double in C#
291
+
292
+ // Should have the repository class
293
+ expect(csharp).to.include("class UserRepository");
294
+
295
+ // Should have the generic function
296
+ expect(csharp).to.match(/public\s+static\s+List<U>\s+map\s*<T,\s*U>/);
297
+
298
+ // Should have proper namespace structure
299
+ expect(csharp).to.include("namespace Test");
300
+ expect(csharp).to.include("public static class test");
301
+ });
302
+ });
303
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Tests for Call-Site Rewriting
3
+ * Covers spec/15-generics.md §5 - Call-Site Rewriting and Monomorphisation
4
+ */
5
+
6
+ import { describe, it } from "mocha";
7
+ import { expect } from "chai";
8
+ import { emitModule } from "../emitter.js";
9
+ import { IrModule } from "@tsonic/frontend";
10
+
11
+ describe("Call-Site Rewriting (spec/15 §5)", () => {
12
+ it("should emit call with explicit type arguments", () => {
13
+ const module: IrModule = {
14
+ kind: "module",
15
+ filePath: "/src/main.ts",
16
+ namespace: "MyApp",
17
+ className: "main",
18
+ isStaticContainer: true,
19
+ imports: [],
20
+ body: [
21
+ {
22
+ kind: "functionDeclaration",
23
+ name: "test",
24
+ parameters: [],
25
+ returnType: { kind: "voidType" },
26
+ body: {
27
+ kind: "blockStatement",
28
+ statements: [
29
+ {
30
+ kind: "expressionStatement",
31
+ expression: {
32
+ kind: "call",
33
+ callee: { kind: "identifier", name: "identity" },
34
+ arguments: [{ kind: "literal", value: "hello" }],
35
+ isOptional: false,
36
+ typeArguments: [{ kind: "primitiveType", name: "string" }],
37
+ requiresSpecialization: false,
38
+ },
39
+ },
40
+ ],
41
+ },
42
+ isExported: false,
43
+ isAsync: false,
44
+ isGenerator: false,
45
+ },
46
+ ],
47
+ exports: [],
48
+ };
49
+
50
+ const result = emitModule(module);
51
+
52
+ expect(result).to.include("identity<string>(");
53
+ });
54
+
55
+ it("should emit specialized call when requiresSpecialization is true", () => {
56
+ const module: IrModule = {
57
+ kind: "module",
58
+ filePath: "/src/main.ts",
59
+ namespace: "MyApp",
60
+ className: "main",
61
+ isStaticContainer: true,
62
+ imports: [],
63
+ body: [
64
+ {
65
+ kind: "functionDeclaration",
66
+ name: "test",
67
+ parameters: [],
68
+ returnType: { kind: "voidType" },
69
+ body: {
70
+ kind: "blockStatement",
71
+ statements: [
72
+ {
73
+ kind: "expressionStatement",
74
+ expression: {
75
+ kind: "call",
76
+ callee: { kind: "identifier", name: "process" },
77
+ arguments: [{ kind: "literal", value: "data" }],
78
+ isOptional: false,
79
+ typeArguments: [{ kind: "primitiveType", name: "string" }],
80
+ requiresSpecialization: true,
81
+ },
82
+ },
83
+ ],
84
+ },
85
+ isExported: false,
86
+ isAsync: false,
87
+ isGenerator: false,
88
+ },
89
+ ],
90
+ exports: [],
91
+ };
92
+
93
+ const result = emitModule(module);
94
+
95
+ // Should generate specialized name
96
+ expect(result).to.include("process__string(");
97
+ expect(result).not.to.include("process<string>(");
98
+ });
99
+ });