@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.
- package/package.json +34 -0
- package/scripts/update-golden-tests.ts +119 -0
- package/src/adapter-generator.ts +112 -0
- package/src/array.test.ts +301 -0
- package/src/constants.ts +32 -0
- package/src/core/exports.ts +36 -0
- package/src/core/imports.test.ts +83 -0
- package/src/core/imports.ts +243 -0
- package/src/core/index.ts +9 -0
- package/src/core/module-emitter/assembly.ts +83 -0
- package/src/core/module-emitter/header.ts +19 -0
- package/src/core/module-emitter/index.ts +17 -0
- package/src/core/module-emitter/namespace.ts +39 -0
- package/src/core/module-emitter/orchestrator.ts +98 -0
- package/src/core/module-emitter/separation.ts +41 -0
- package/src/core/module-emitter/static-container.ts +75 -0
- package/src/core/module-emitter.test.ts +154 -0
- package/src/core/module-emitter.ts +6 -0
- package/src/core/module-map.ts +218 -0
- package/src/core/options.ts +16 -0
- package/src/core/type-params.ts +34 -0
- package/src/emitter-types/context.ts +91 -0
- package/src/emitter-types/core.ts +138 -0
- package/src/emitter-types/csharp-types.ts +42 -0
- package/src/emitter-types/formatting.ts +13 -0
- package/src/emitter-types/fqn.ts +81 -0
- package/src/emitter-types/index.ts +31 -0
- package/src/emitter.ts +107 -0
- package/src/expression-emitter.ts +112 -0
- package/src/expressions/access.ts +104 -0
- package/src/expressions/calls.ts +264 -0
- package/src/expressions/collections.ts +354 -0
- package/src/expressions/functions.ts +71 -0
- package/src/expressions/identifiers.ts +125 -0
- package/src/expressions/index.test.ts +515 -0
- package/src/expressions/index.ts +38 -0
- package/src/expressions/literals.ts +138 -0
- package/src/expressions/operators.ts +211 -0
- package/src/expressions/other.ts +63 -0
- package/src/generator-exchange.ts +120 -0
- package/src/generator.test.ts +193 -0
- package/src/golden-tests/config-parser.ts +67 -0
- package/src/golden-tests/discovery.ts +61 -0
- package/src/golden-tests/index.ts +10 -0
- package/src/golden-tests/registration.ts +26 -0
- package/src/golden-tests/runner.ts +131 -0
- package/src/golden-tests/tree-builder.ts +43 -0
- package/src/golden-tests/types.ts +21 -0
- package/src/golden.test.ts +40 -0
- package/src/hierarchical-bindings.test.ts +258 -0
- package/src/index.ts +14 -0
- package/src/integration.test.ts +303 -0
- package/src/specialization/call-site-rewriting.test.ts +99 -0
- package/src/specialization/collection/expressions.ts +184 -0
- package/src/specialization/collection/index.ts +7 -0
- package/src/specialization/collection/orchestrator.ts +25 -0
- package/src/specialization/collection/statements.ts +91 -0
- package/src/specialization/collection.ts +10 -0
- package/src/specialization/generation.ts +189 -0
- package/src/specialization/generic-classes.test.ts +59 -0
- package/src/specialization/generic-functions.test.ts +292 -0
- package/src/specialization/helpers.ts +39 -0
- package/src/specialization/index.ts +28 -0
- package/src/specialization/interfaces.test.ts +151 -0
- package/src/specialization/naming.ts +34 -0
- package/src/specialization/substitution.ts +186 -0
- package/src/specialization/type-aliases.test.ts +134 -0
- package/src/specialization/types.ts +19 -0
- package/src/specialization-generator.ts +23 -0
- package/src/statement-emitter.ts +117 -0
- package/src/statements/blocks.ts +115 -0
- package/src/statements/classes/helpers.ts +9 -0
- package/src/statements/classes/index.ts +13 -0
- package/src/statements/classes/inline-types.ts +79 -0
- package/src/statements/classes/members/constructors.ts +123 -0
- package/src/statements/classes/members/index.ts +8 -0
- package/src/statements/classes/members/methods.ts +137 -0
- package/src/statements/classes/members/orchestrator.ts +33 -0
- package/src/statements/classes/members/properties.ts +62 -0
- package/src/statements/classes/members.ts +6 -0
- package/src/statements/classes/parameters.ts +69 -0
- package/src/statements/classes/properties.ts +95 -0
- package/src/statements/classes.ts +14 -0
- package/src/statements/control/conditionals.ts +134 -0
- package/src/statements/control/exceptions.ts +59 -0
- package/src/statements/control/index.ts +11 -0
- package/src/statements/control/loops.ts +250 -0
- package/src/statements/control.ts +14 -0
- package/src/statements/declarations/classes.ts +89 -0
- package/src/statements/declarations/enums.ts +32 -0
- package/src/statements/declarations/functions.ts +147 -0
- package/src/statements/declarations/index.ts +10 -0
- package/src/statements/declarations/interfaces.ts +116 -0
- package/src/statements/declarations/structs.test.ts +182 -0
- package/src/statements/declarations/type-aliases.ts +104 -0
- package/src/statements/declarations/variables.ts +159 -0
- package/src/statements/declarations.ts +13 -0
- package/src/statements/index.test.ts +258 -0
- package/src/statements/index.ts +43 -0
- package/src/type-assertion.test.ts +143 -0
- package/src/type-emitter.ts +18 -0
- package/src/types/arrays.ts +21 -0
- package/src/types/dictionaries.ts +52 -0
- package/src/types/emitter.ts +76 -0
- package/src/types/functions.ts +45 -0
- package/src/types/index.test.ts +116 -0
- package/src/types/index.ts +14 -0
- package/src/types/intersections.ts +19 -0
- package/src/types/literals.ts +26 -0
- package/src/types/objects.ts +27 -0
- package/src/types/parameters.test.ts +146 -0
- package/src/types/parameters.ts +95 -0
- package/src/types/primitives.ts +24 -0
- package/src/types/references.ts +187 -0
- package/src/types/unions.test.ts +397 -0
- package/src/types/unions.ts +62 -0
- package/src/types.ts +33 -0
- package/testcases/README.md +213 -0
- package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
- package/testcases/arrays/basic/config.yaml +1 -0
- package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
- package/testcases/arrays/destructuring/config.yaml +1 -0
- package/testcases/arrays/methods/ArrayMethods.ts +6 -0
- package/testcases/arrays/methods/config.yaml +1 -0
- package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
- package/testcases/arrays/multidimensional/config.yaml +1 -0
- package/testcases/arrays/spread/ArraySpread.ts +3 -0
- package/testcases/arrays/spread/config.yaml +1 -0
- package/testcases/async/basic/AsyncFunction.ts +5 -0
- package/testcases/async/basic/config.yaml +1 -0
- package/testcases/classes/abstract/AbstractClasses.ts +53 -0
- package/testcases/classes/abstract/config.yaml +1 -0
- package/testcases/classes/basic/Person.ts +12 -0
- package/testcases/classes/basic/config.yaml +1 -0
- package/testcases/classes/constructor/User.ts +11 -0
- package/testcases/classes/constructor/config.yaml +1 -0
- package/testcases/classes/field-inference/Counter.ts +11 -0
- package/testcases/classes/field-inference/config.yaml +1 -0
- package/testcases/classes/inheritance/Inheritance.ts +24 -0
- package/testcases/classes/inheritance/config.yaml +1 -0
- package/testcases/classes/static-members/MathHelper.ts +12 -0
- package/testcases/classes/static-members/config.yaml +1 -0
- package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
- package/testcases/control-flow/error-handling/config.yaml +1 -0
- package/testcases/control-flow/loops/Loops.ts +21 -0
- package/testcases/control-flow/loops/config.yaml +1 -0
- package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
- package/testcases/control-flow/switch/config.yaml +1 -0
- package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
- package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
- package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
- package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
- package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
- package/testcases/edge-cases/shadowing/config.yaml +1 -0
- package/testcases/functions/arrow/ArrowFunction.ts +5 -0
- package/testcases/functions/arrow/config.yaml +1 -0
- package/testcases/functions/basic/Greet.ts +3 -0
- package/testcases/functions/basic/config.yaml +1 -0
- package/testcases/functions/closures/Closures.ts +11 -0
- package/testcases/functions/closures/config.yaml +1 -0
- package/testcases/functions/default-params/DefaultParams.ts +7 -0
- package/testcases/functions/default-params/config.yaml +1 -0
- package/testcases/functions/rest-params/RestParams.ts +7 -0
- package/testcases/functions/rest-params/config.yaml +1 -0
- package/testcases/functions/type-guards/TypeGuards.ts +52 -0
- package/testcases/functions/type-guards/config.yaml +1 -0
- package/testcases/operators/logical/LogicalOperators.ts +11 -0
- package/testcases/operators/logical/config.yaml +1 -0
- package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
- package/testcases/operators/nullish-coalescing/config.yaml +1 -0
- package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
- package/testcases/operators/optional-chaining/config.yaml +1 -0
- package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
- package/testcases/real-world/advanced-generics/config.yaml +1 -0
- package/testcases/real-world/async-ops/async-ops.ts +67 -0
- package/testcases/real-world/async-ops/config.yaml +1 -0
- package/testcases/real-world/business-logic/business-logic.ts +215 -0
- package/testcases/real-world/business-logic/config.yaml +1 -0
- package/testcases/real-world/calculator/calculator.ts +29 -0
- package/testcases/real-world/calculator/config.yaml +1 -0
- package/testcases/real-world/data-structures/config.yaml +1 -0
- package/testcases/real-world/data-structures/data-structures.ts +133 -0
- package/testcases/real-world/functional/config.yaml +1 -0
- package/testcases/real-world/functional/functional.ts +116 -0
- package/testcases/real-world/shapes/config.yaml +1 -0
- package/testcases/real-world/shapes/shapes.ts +87 -0
- package/testcases/real-world/string-utils/config.yaml +1 -0
- package/testcases/real-world/string-utils/string-utils.ts +47 -0
- package/testcases/real-world/todo-list/config.yaml +1 -0
- package/testcases/real-world/todo-list/todo-list.ts +52 -0
- package/testcases/real-world/type-guards/config.yaml +1 -0
- package/testcases/real-world/type-guards/type-guards.ts +71 -0
- package/testcases/structs/basic/Point.ts +9 -0
- package/testcases/structs/basic/config.yaml +1 -0
- package/testcases/types/conditional/ConditionalTypes.ts +35 -0
- package/testcases/types/conditional/config.yaml +1 -0
- package/testcases/types/constants/ModuleConstants.ts +6 -0
- package/testcases/types/constants/config.yaml +1 -0
- package/testcases/types/generics/Generics.ts +15 -0
- package/testcases/types/generics/config.yaml +1 -0
- package/testcases/types/interfaces/Interfaces.ts +14 -0
- package/testcases/types/interfaces/config.yaml +1 -0
- package/testcases/types/mapped/MappedTypes.ts +27 -0
- package/testcases/types/mapped/config.yaml +1 -0
- package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
- package/testcases/types/tuples-intersections/config.yaml +1 -0
- package/testcases/types/unions/UnionTypes.ts +11 -0
- package/testcases/types/unions/config.yaml +1 -0
- 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
|
+
});
|