@tsonic/frontend 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 +53 -0
- package/src/dependency-graph.ts +18 -0
- package/src/dotnet-metadata.ts +121 -0
- package/src/graph/builder.ts +81 -0
- package/src/graph/circular.ts +58 -0
- package/src/graph/extraction/exports.ts +55 -0
- package/src/graph/extraction/imports.ts +81 -0
- package/src/graph/extraction/index.ts +7 -0
- package/src/graph/extraction/orchestrator.ts +99 -0
- package/src/graph/extraction.ts +10 -0
- package/src/graph/helpers.ts +51 -0
- package/src/graph/index.ts +17 -0
- package/src/graph/types.ts +13 -0
- package/src/index.ts +80 -0
- package/src/ir/binding-resolution.test.ts +585 -0
- package/src/ir/builder/exports.ts +78 -0
- package/src/ir/builder/helpers.ts +27 -0
- package/src/ir/builder/imports.ts +153 -0
- package/src/ir/builder/index.ts +10 -0
- package/src/ir/builder/orchestrator.ts +178 -0
- package/src/ir/builder/statements.ts +55 -0
- package/src/ir/builder/types.ts +8 -0
- package/src/ir/builder/validation.ts +129 -0
- package/src/ir/builder.test.ts +581 -0
- package/src/ir/builder.ts +14 -0
- package/src/ir/converters/expressions/access.ts +99 -0
- package/src/ir/converters/expressions/calls.ts +137 -0
- package/src/ir/converters/expressions/collections.ts +84 -0
- package/src/ir/converters/expressions/functions.ts +62 -0
- package/src/ir/converters/expressions/helpers.ts +264 -0
- package/src/ir/converters/expressions/index.ts +43 -0
- package/src/ir/converters/expressions/literals.ts +22 -0
- package/src/ir/converters/expressions/operators.ts +147 -0
- package/src/ir/converters/expressions/other.ts +60 -0
- package/src/ir/converters/statements/control/blocks.ts +22 -0
- package/src/ir/converters/statements/control/conditionals.ts +67 -0
- package/src/ir/converters/statements/control/exceptions.ts +43 -0
- package/src/ir/converters/statements/control/index.ts +17 -0
- package/src/ir/converters/statements/control/loops.ts +99 -0
- package/src/ir/converters/statements/control.ts +17 -0
- package/src/ir/converters/statements/declarations/classes/constructors.ts +120 -0
- package/src/ir/converters/statements/declarations/classes/index.ts +12 -0
- package/src/ir/converters/statements/declarations/classes/methods.ts +61 -0
- package/src/ir/converters/statements/declarations/classes/orchestrator.ts +166 -0
- package/src/ir/converters/statements/declarations/classes/override-detection.ts +116 -0
- package/src/ir/converters/statements/declarations/classes/properties.ts +63 -0
- package/src/ir/converters/statements/declarations/classes.ts +6 -0
- package/src/ir/converters/statements/declarations/enums.ts +29 -0
- package/src/ir/converters/statements/declarations/functions.ts +39 -0
- package/src/ir/converters/statements/declarations/index.ts +14 -0
- package/src/ir/converters/statements/declarations/interfaces.ts +131 -0
- package/src/ir/converters/statements/declarations/registry.ts +45 -0
- package/src/ir/converters/statements/declarations/type-aliases.ts +25 -0
- package/src/ir/converters/statements/declarations/variables.ts +60 -0
- package/src/ir/converters/statements/declarations.ts +16 -0
- package/src/ir/converters/statements/helpers.ts +174 -0
- package/src/ir/converters/statements/index.ts +40 -0
- package/src/ir/expression-converter.ts +207 -0
- package/src/ir/generic-validator.ts +100 -0
- package/src/ir/hierarchical-bindings-e2e.test.ts +163 -0
- package/src/ir/index.ts +6 -0
- package/src/ir/statement-converter.ts +128 -0
- package/src/ir/type-converter/arrays.ts +20 -0
- package/src/ir/type-converter/converter.ts +10 -0
- package/src/ir/type-converter/functions.ts +22 -0
- package/src/ir/type-converter/index.ts +11 -0
- package/src/ir/type-converter/inference.ts +122 -0
- package/src/ir/type-converter/literals.ts +40 -0
- package/src/ir/type-converter/objects.ts +107 -0
- package/src/ir/type-converter/orchestrator.ts +85 -0
- package/src/ir/type-converter/patterns.ts +73 -0
- package/src/ir/type-converter/primitives.ts +57 -0
- package/src/ir/type-converter/references.ts +64 -0
- package/src/ir/type-converter/unions-intersections.ts +34 -0
- package/src/ir/type-converter.ts +13 -0
- package/src/ir/types/expressions.ts +215 -0
- package/src/ir/types/guards.ts +39 -0
- package/src/ir/types/helpers.ts +135 -0
- package/src/ir/types/index.ts +108 -0
- package/src/ir/types/ir-types.ts +96 -0
- package/src/ir/types/module.ts +57 -0
- package/src/ir/types/statements.ts +238 -0
- package/src/ir/types.ts +97 -0
- package/src/metadata/bindings-loader.test.ts +144 -0
- package/src/metadata/bindings-loader.ts +357 -0
- package/src/metadata/index.ts +15 -0
- package/src/metadata/library-loader.ts +153 -0
- package/src/metadata/loader.test.ts +156 -0
- package/src/metadata/loader.ts +382 -0
- package/src/program/bindings.test.ts +512 -0
- package/src/program/bindings.ts +253 -0
- package/src/program/config.ts +30 -0
- package/src/program/creation.ts +249 -0
- package/src/program/dependency-graph.ts +245 -0
- package/src/program/diagnostics.ts +103 -0
- package/src/program/index.ts +19 -0
- package/src/program/metadata.ts +68 -0
- package/src/program/queries.ts +18 -0
- package/src/program/types.ts +38 -0
- package/src/program.ts +13 -0
- package/src/resolver/dotnet-import-resolver.ts +226 -0
- package/src/resolver/import-resolution.ts +177 -0
- package/src/resolver/index.ts +18 -0
- package/src/resolver/namespace.test.ts +86 -0
- package/src/resolver/namespace.ts +42 -0
- package/src/resolver/naming.ts +38 -0
- package/src/resolver/path-resolution.ts +22 -0
- package/src/resolver/types.ts +15 -0
- package/src/resolver.test.ts +155 -0
- package/src/resolver.ts +14 -0
- package/src/symbol-table/builder.ts +114 -0
- package/src/symbol-table/creation.ts +42 -0
- package/src/symbol-table/helpers.ts +18 -0
- package/src/symbol-table/index.ts +13 -0
- package/src/symbol-table/queries.ts +42 -0
- package/src/symbol-table/types.ts +28 -0
- package/src/symbol-table.ts +14 -0
- package/src/types/bindings.ts +172 -0
- package/src/types/diagnostic.test.ts +164 -0
- package/src/types/diagnostic.ts +153 -0
- package/src/types/explicit-views.test.ts +113 -0
- package/src/types/explicit-views.ts +218 -0
- package/src/types/metadata.ts +229 -0
- package/src/types/module.ts +99 -0
- package/src/types/nested-types.test.ts +194 -0
- package/src/types/nested-types.ts +215 -0
- package/src/types/parameter-modifiers.ts +173 -0
- package/src/types/ref-parameters.test.ts +192 -0
- package/src/types/ref-parameters.ts +268 -0
- package/src/types/result.test.ts +157 -0
- package/src/types/result.ts +48 -0
- package/src/types/support-types.test.ts +81 -0
- package/src/types/support-types.ts +288 -0
- package/src/types/test-harness.ts +180 -0
- package/src/validation/exports.ts +98 -0
- package/src/validation/features.ts +89 -0
- package/src/validation/generics.ts +40 -0
- package/src/validation/helpers.ts +31 -0
- package/src/validation/imports.ts +97 -0
- package/src/validation/index.ts +11 -0
- package/src/validation/orchestrator.ts +51 -0
- package/src/validation/static-safety.ts +267 -0
- package/src/validator.test.ts +468 -0
- package/src/validator.ts +15 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for IR Builder
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import * as ts from "typescript";
|
|
8
|
+
import { buildIrModule } from "./builder.js";
|
|
9
|
+
import {
|
|
10
|
+
IrFunctionDeclaration,
|
|
11
|
+
IrVariableDeclaration,
|
|
12
|
+
IrClassDeclaration,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
import { DotnetMetadataRegistry } from "../dotnet-metadata.js";
|
|
15
|
+
import { BindingRegistry } from "../program/bindings.js";
|
|
16
|
+
import { createDotNetImportResolver } from "../resolver/dotnet-import-resolver.js";
|
|
17
|
+
|
|
18
|
+
describe("IR Builder", () => {
|
|
19
|
+
const createTestProgram = (source: string, fileName = "/test/test.ts") => {
|
|
20
|
+
const sourceFile = ts.createSourceFile(
|
|
21
|
+
fileName,
|
|
22
|
+
source,
|
|
23
|
+
ts.ScriptTarget.ES2022,
|
|
24
|
+
true,
|
|
25
|
+
ts.ScriptKind.TS
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const program = ts.createProgram(
|
|
29
|
+
[fileName],
|
|
30
|
+
{
|
|
31
|
+
target: ts.ScriptTarget.ES2022,
|
|
32
|
+
module: ts.ModuleKind.ES2022,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
getSourceFile: (name) => (name === fileName ? sourceFile : undefined),
|
|
36
|
+
writeFile: () => {},
|
|
37
|
+
getCurrentDirectory: () => "/test",
|
|
38
|
+
getDirectories: () => [],
|
|
39
|
+
fileExists: () => true,
|
|
40
|
+
readFile: () => source,
|
|
41
|
+
getCanonicalFileName: (f) => f,
|
|
42
|
+
useCaseSensitiveFileNames: () => true,
|
|
43
|
+
getNewLine: () => "\n",
|
|
44
|
+
getDefaultLibFileName: (_options) => "lib.d.ts",
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
program,
|
|
50
|
+
checker: program.getTypeChecker(),
|
|
51
|
+
options: { sourceRoot: "/test", rootNamespace: "TestApp", strict: true },
|
|
52
|
+
sourceFiles: [sourceFile],
|
|
53
|
+
metadata: new DotnetMetadataRegistry(),
|
|
54
|
+
bindings: new BindingRegistry(),
|
|
55
|
+
dotnetResolver: createDotNetImportResolver("/test"),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
describe("Module Structure", () => {
|
|
60
|
+
it("should create IR module with correct namespace and class name", () => {
|
|
61
|
+
const source = `
|
|
62
|
+
export function greet(name: string): string {
|
|
63
|
+
return \`Hello \${name}\`;
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const testProgram = createTestProgram(source);
|
|
68
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
69
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
70
|
+
|
|
71
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
72
|
+
sourceRoot: "/test",
|
|
73
|
+
rootNamespace: "TestApp",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(result.ok).to.equal(true);
|
|
77
|
+
if (result.ok) {
|
|
78
|
+
const module = result.value;
|
|
79
|
+
expect(module.kind).to.equal("module");
|
|
80
|
+
expect(module.namespace).to.equal("TestApp");
|
|
81
|
+
expect(module.className).to.equal("test");
|
|
82
|
+
expect(module.isStaticContainer).to.equal(true);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should detect top-level code", () => {
|
|
87
|
+
const source = `
|
|
88
|
+
console.log("Hello");
|
|
89
|
+
export const x = 42;
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const testProgram = createTestProgram(source);
|
|
93
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
94
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
95
|
+
|
|
96
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
97
|
+
sourceRoot: "/test",
|
|
98
|
+
rootNamespace: "TestApp",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(result.ok).to.equal(true);
|
|
102
|
+
if (result.ok) {
|
|
103
|
+
expect(result.value.isStaticContainer).to.equal(false);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("Import Extraction", () => {
|
|
109
|
+
it("should extract local imports", () => {
|
|
110
|
+
const source = `
|
|
111
|
+
import { User } from "./models/User.ts";
|
|
112
|
+
import * as utils from "./utils.ts";
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const testProgram = createTestProgram(source);
|
|
116
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
117
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
118
|
+
|
|
119
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
120
|
+
sourceRoot: "/test",
|
|
121
|
+
rootNamespace: "TestApp",
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(result.ok).to.equal(true);
|
|
125
|
+
if (result.ok) {
|
|
126
|
+
const imports = result.value.imports;
|
|
127
|
+
expect(imports).to.have.length(2);
|
|
128
|
+
|
|
129
|
+
const firstImport = imports[0];
|
|
130
|
+
const secondImport = imports[1];
|
|
131
|
+
if (!firstImport || !secondImport) throw new Error("Missing imports");
|
|
132
|
+
|
|
133
|
+
expect(firstImport.source).to.equal("./models/User.ts");
|
|
134
|
+
expect(firstImport.isLocal).to.equal(true);
|
|
135
|
+
expect(firstImport.isDotNet).to.equal(false);
|
|
136
|
+
|
|
137
|
+
expect(secondImport.source).to.equal("./utils.ts");
|
|
138
|
+
const firstSpec = secondImport.specifiers[0];
|
|
139
|
+
if (!firstSpec) throw new Error("Missing specifier");
|
|
140
|
+
expect(firstSpec.kind).to.equal("namespace");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should not detect bare imports as .NET without package bindings", () => {
|
|
145
|
+
// Import-driven resolution: bare imports like "System.IO" are only detected as .NET
|
|
146
|
+
// if they come from a package with bindings.json. Without an actual package,
|
|
147
|
+
// the import is not recognized as .NET.
|
|
148
|
+
const source = `
|
|
149
|
+
import { File } from "System.IO";
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const testProgram = createTestProgram(source);
|
|
153
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
154
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
155
|
+
|
|
156
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
157
|
+
sourceRoot: "/test",
|
|
158
|
+
rootNamespace: "TestApp",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(result.ok).to.equal(true);
|
|
162
|
+
if (result.ok) {
|
|
163
|
+
const imports = result.value.imports;
|
|
164
|
+
const firstImport = imports[0];
|
|
165
|
+
if (!firstImport) throw new Error("Missing import");
|
|
166
|
+
// Without an actual package with bindings.json, this is NOT detected as .NET
|
|
167
|
+
expect(firstImport.isDotNet).to.equal(false);
|
|
168
|
+
expect(firstImport.resolvedNamespace).to.equal(undefined);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("Statement Conversion", () => {
|
|
174
|
+
it("should convert function declarations", () => {
|
|
175
|
+
const source = `
|
|
176
|
+
export function add(a: number, b: number): number {
|
|
177
|
+
return a + b;
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
const testProgram = createTestProgram(source);
|
|
182
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
183
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
184
|
+
|
|
185
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
186
|
+
sourceRoot: "/test",
|
|
187
|
+
rootNamespace: "TestApp",
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(result.ok).to.equal(true);
|
|
191
|
+
if (result.ok) {
|
|
192
|
+
const body = result.value.body;
|
|
193
|
+
expect(body).to.have.length(1);
|
|
194
|
+
|
|
195
|
+
const firstItem = body[0];
|
|
196
|
+
if (!firstItem) throw new Error("Missing body item");
|
|
197
|
+
expect(firstItem.kind).to.equal("functionDeclaration");
|
|
198
|
+
|
|
199
|
+
const func = firstItem as IrFunctionDeclaration;
|
|
200
|
+
expect(func.name).to.equal("add");
|
|
201
|
+
expect(func.parameters).to.have.length(2);
|
|
202
|
+
expect(func.isExported).to.equal(true);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should convert variable declarations", () => {
|
|
207
|
+
const source = `
|
|
208
|
+
const x = 10;
|
|
209
|
+
let y: string = "hello";
|
|
210
|
+
export const z = { name: "test" };
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
const testProgram = createTestProgram(source);
|
|
214
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
215
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
216
|
+
|
|
217
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
218
|
+
sourceRoot: "/test",
|
|
219
|
+
rootNamespace: "TestApp",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result.ok).to.equal(true);
|
|
223
|
+
if (result.ok) {
|
|
224
|
+
const body = result.value.body;
|
|
225
|
+
expect(body).to.have.length(3);
|
|
226
|
+
|
|
227
|
+
const firstVar = body[0] as IrVariableDeclaration;
|
|
228
|
+
expect(firstVar.kind).to.equal("variableDeclaration");
|
|
229
|
+
expect(firstVar.declarationKind).to.equal("const");
|
|
230
|
+
|
|
231
|
+
const secondVar = body[1] as IrVariableDeclaration;
|
|
232
|
+
expect(secondVar.declarationKind).to.equal("let");
|
|
233
|
+
|
|
234
|
+
const thirdVar = body[2] as IrVariableDeclaration;
|
|
235
|
+
expect(thirdVar.isExported).to.equal(true);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should convert class declarations", () => {
|
|
240
|
+
const source = `
|
|
241
|
+
export class User {
|
|
242
|
+
private name: string;
|
|
243
|
+
constructor(name: string) {
|
|
244
|
+
this.name = name;
|
|
245
|
+
}
|
|
246
|
+
getName(): string {
|
|
247
|
+
return this.name;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
const testProgram = createTestProgram(source);
|
|
253
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
254
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
255
|
+
|
|
256
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
257
|
+
sourceRoot: "/test",
|
|
258
|
+
rootNamespace: "TestApp",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(result.ok).to.equal(true);
|
|
262
|
+
if (result.ok) {
|
|
263
|
+
const body = result.value.body;
|
|
264
|
+
expect(body).to.have.length(1);
|
|
265
|
+
|
|
266
|
+
const cls = body[0] as IrClassDeclaration;
|
|
267
|
+
expect(cls.kind).to.equal("classDeclaration");
|
|
268
|
+
expect(cls.name).to.equal("User");
|
|
269
|
+
expect(cls.isExported).to.equal(true);
|
|
270
|
+
expect(cls.members).to.have.length.greaterThan(0);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("Expression Conversion", () => {
|
|
276
|
+
it("should convert template literals", () => {
|
|
277
|
+
const source = `
|
|
278
|
+
const greeting = \`Hello \${name}\`;
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
const testProgram = createTestProgram(source);
|
|
282
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
283
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
284
|
+
|
|
285
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
286
|
+
sourceRoot: "/test",
|
|
287
|
+
rootNamespace: "TestApp",
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(result.ok).to.equal(true);
|
|
291
|
+
if (result.ok) {
|
|
292
|
+
const varDecl = result.value.body[0] as IrVariableDeclaration;
|
|
293
|
+
const init = varDecl.declarations[0]?.initializer;
|
|
294
|
+
if (init && init.kind === "templateLiteral") {
|
|
295
|
+
expect(init.kind).to.equal("templateLiteral");
|
|
296
|
+
expect(init.quasis).to.have.length(2);
|
|
297
|
+
expect(init.expressions).to.have.length(1);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should convert arrow functions", () => {
|
|
303
|
+
const source = `
|
|
304
|
+
const double = (x: number) => x * 2;
|
|
305
|
+
`;
|
|
306
|
+
|
|
307
|
+
const testProgram = createTestProgram(source);
|
|
308
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
309
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
310
|
+
|
|
311
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
312
|
+
sourceRoot: "/test",
|
|
313
|
+
rootNamespace: "TestApp",
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(result.ok).to.equal(true);
|
|
317
|
+
if (result.ok) {
|
|
318
|
+
const varDecl = result.value.body[0] as IrVariableDeclaration;
|
|
319
|
+
const init = varDecl.declarations[0]?.initializer;
|
|
320
|
+
if (init && init.kind === "arrowFunction") {
|
|
321
|
+
expect(init.kind).to.equal("arrowFunction");
|
|
322
|
+
expect(init.parameters).to.have.length(1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe("Export Handling", () => {
|
|
329
|
+
it("should handle named exports", () => {
|
|
330
|
+
const source = `
|
|
331
|
+
const a = 1;
|
|
332
|
+
const b = 2;
|
|
333
|
+
export { a, b as c };
|
|
334
|
+
`;
|
|
335
|
+
|
|
336
|
+
const testProgram = createTestProgram(source);
|
|
337
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
338
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
339
|
+
|
|
340
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
341
|
+
sourceRoot: "/test",
|
|
342
|
+
rootNamespace: "TestApp",
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(result.ok).to.equal(true);
|
|
346
|
+
if (result.ok) {
|
|
347
|
+
const exports = result.value.exports;
|
|
348
|
+
expect(exports).to.have.length(2);
|
|
349
|
+
|
|
350
|
+
const firstExport = exports[0];
|
|
351
|
+
if (!firstExport) throw new Error("Missing export");
|
|
352
|
+
expect(firstExport.kind).to.equal("named");
|
|
353
|
+
if (firstExport.kind === "named") {
|
|
354
|
+
expect(firstExport.name).to.equal("a");
|
|
355
|
+
expect(firstExport.localName).to.equal("a");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const second = exports[1];
|
|
359
|
+
if (second && second.kind === "named") {
|
|
360
|
+
expect(second.name).to.equal("c");
|
|
361
|
+
expect(second.localName).to.equal("b");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("should handle default export", () => {
|
|
367
|
+
const source = `
|
|
368
|
+
export default function main() {
|
|
369
|
+
console.log("Hello");
|
|
370
|
+
}
|
|
371
|
+
`;
|
|
372
|
+
|
|
373
|
+
const testProgram = createTestProgram(source);
|
|
374
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
375
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
376
|
+
|
|
377
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
378
|
+
sourceRoot: "/test",
|
|
379
|
+
rootNamespace: "TestApp",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(result.ok).to.equal(true);
|
|
383
|
+
if (result.ok) {
|
|
384
|
+
const exports = result.value.exports;
|
|
385
|
+
expect(exports.some((e) => e.kind === "default")).to.equal(true);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe("Struct Detection", () => {
|
|
391
|
+
it("should detect struct marker in interface", () => {
|
|
392
|
+
const source = `
|
|
393
|
+
interface struct {
|
|
394
|
+
readonly __brand: "struct";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface Point extends struct {
|
|
398
|
+
x: number;
|
|
399
|
+
y: number;
|
|
400
|
+
}
|
|
401
|
+
`;
|
|
402
|
+
|
|
403
|
+
const testProgram = createTestProgram(source);
|
|
404
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
405
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
406
|
+
|
|
407
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
408
|
+
sourceRoot: "/test",
|
|
409
|
+
rootNamespace: "TestApp",
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
expect(result.ok).to.equal(true);
|
|
413
|
+
if (result.ok) {
|
|
414
|
+
const body = result.value.body;
|
|
415
|
+
const pointInterface = body.find(
|
|
416
|
+
(stmt) =>
|
|
417
|
+
stmt.kind === "interfaceDeclaration" && stmt.name === "Point"
|
|
418
|
+
);
|
|
419
|
+
expect(pointInterface).not.to.equal(undefined);
|
|
420
|
+
if (pointInterface && pointInterface.kind === "interfaceDeclaration") {
|
|
421
|
+
expect(pointInterface.isStruct).to.equal(true);
|
|
422
|
+
// Verify __brand property is filtered out
|
|
423
|
+
expect(
|
|
424
|
+
pointInterface.members.some(
|
|
425
|
+
(m) => m.kind === "propertySignature" && m.name === "__brand"
|
|
426
|
+
)
|
|
427
|
+
).to.equal(false);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("should detect struct marker in class", () => {
|
|
433
|
+
const source = `
|
|
434
|
+
interface struct {
|
|
435
|
+
readonly __brand: "struct";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export class Vector3D implements struct {
|
|
439
|
+
x: number;
|
|
440
|
+
y: number;
|
|
441
|
+
z: number;
|
|
442
|
+
}
|
|
443
|
+
`;
|
|
444
|
+
|
|
445
|
+
const testProgram = createTestProgram(source);
|
|
446
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
447
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
448
|
+
|
|
449
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
450
|
+
sourceRoot: "/test",
|
|
451
|
+
rootNamespace: "TestApp",
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
expect(result.ok).to.equal(true);
|
|
455
|
+
if (result.ok) {
|
|
456
|
+
const body = result.value.body;
|
|
457
|
+
const vectorClass = body.find(
|
|
458
|
+
(stmt) => stmt.kind === "classDeclaration" && stmt.name === "Vector3D"
|
|
459
|
+
);
|
|
460
|
+
expect(vectorClass).not.to.equal(undefined);
|
|
461
|
+
if (vectorClass && vectorClass.kind === "classDeclaration") {
|
|
462
|
+
expect(vectorClass.isStruct).to.equal(true);
|
|
463
|
+
// Verify __brand property is filtered out
|
|
464
|
+
expect(
|
|
465
|
+
vectorClass.members.some(
|
|
466
|
+
(m) => m.kind === "propertyDeclaration" && m.name === "__brand"
|
|
467
|
+
)
|
|
468
|
+
).to.equal(false);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("should not mark regular class as struct", () => {
|
|
474
|
+
const source = `
|
|
475
|
+
export class RegularClass {
|
|
476
|
+
value: number;
|
|
477
|
+
}
|
|
478
|
+
`;
|
|
479
|
+
|
|
480
|
+
const testProgram = createTestProgram(source);
|
|
481
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
482
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
483
|
+
|
|
484
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
485
|
+
sourceRoot: "/test",
|
|
486
|
+
rootNamespace: "TestApp",
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
expect(result.ok).to.equal(true);
|
|
490
|
+
if (result.ok) {
|
|
491
|
+
const body = result.value.body;
|
|
492
|
+
const regularClass = body[0];
|
|
493
|
+
expect(regularClass).not.to.equal(undefined);
|
|
494
|
+
if (regularClass && regularClass.kind === "classDeclaration") {
|
|
495
|
+
expect(regularClass.isStruct).to.equal(false);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
describe("Interface Implements Validation (TSN7301)", () => {
|
|
502
|
+
it("should report error when class implements a nominalized interface", () => {
|
|
503
|
+
const source = `
|
|
504
|
+
interface Printable {
|
|
505
|
+
print(): void;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export class Document implements Printable {
|
|
509
|
+
print(): void {}
|
|
510
|
+
}
|
|
511
|
+
`;
|
|
512
|
+
|
|
513
|
+
const testProgram = createTestProgram(source);
|
|
514
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
515
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
516
|
+
|
|
517
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
518
|
+
sourceRoot: "/test",
|
|
519
|
+
rootNamespace: "TestApp",
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
expect(result.ok).to.equal(false);
|
|
523
|
+
if (!result.ok) {
|
|
524
|
+
expect(result.error.code).to.equal("TSN7301");
|
|
525
|
+
expect(result.error.message).to.include("Printable");
|
|
526
|
+
expect(result.error.message).to.include("nominalized");
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("should allow struct marker in implements clause", () => {
|
|
531
|
+
const source = `
|
|
532
|
+
interface struct {
|
|
533
|
+
readonly __brand: "struct";
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export class Point implements struct {
|
|
537
|
+
x: number;
|
|
538
|
+
y: number;
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
541
|
+
|
|
542
|
+
const testProgram = createTestProgram(source);
|
|
543
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
544
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
545
|
+
|
|
546
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
547
|
+
sourceRoot: "/test",
|
|
548
|
+
rootNamespace: "TestApp",
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
expect(result.ok).to.equal(true);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it("should report error when class implements a type alias", () => {
|
|
555
|
+
const source = `
|
|
556
|
+
type Serializable = {
|
|
557
|
+
serialize(): string;
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
export class Config implements Serializable {
|
|
561
|
+
serialize(): string { return "{}"; }
|
|
562
|
+
}
|
|
563
|
+
`;
|
|
564
|
+
|
|
565
|
+
const testProgram = createTestProgram(source);
|
|
566
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
567
|
+
if (!sourceFile) throw new Error("Failed to create source file");
|
|
568
|
+
|
|
569
|
+
const result = buildIrModule(sourceFile, testProgram, {
|
|
570
|
+
sourceRoot: "/test",
|
|
571
|
+
rootNamespace: "TestApp",
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
expect(result.ok).to.equal(false);
|
|
575
|
+
if (!result.ok) {
|
|
576
|
+
expect(result.error.code).to.equal("TSN7301");
|
|
577
|
+
expect(result.error.message).to.include("Serializable");
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR Builder - Main module for converting TypeScript AST to IR
|
|
3
|
+
* Main dispatcher - re-exports from builder/ subdirectory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type { IrBuildOptions } from "./builder/types.js";
|
|
7
|
+
export { buildIrModule, buildIr } from "./builder/orchestrator.js";
|
|
8
|
+
export { extractImports, extractImportSpecifiers } from "./builder/imports.js";
|
|
9
|
+
export { extractExports } from "./builder/exports.js";
|
|
10
|
+
export {
|
|
11
|
+
extractStatements,
|
|
12
|
+
isExecutableStatement,
|
|
13
|
+
} from "./builder/statements.js";
|
|
14
|
+
export { hasExportModifier, hasDefaultModifier } from "./builder/helpers.js";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Member access expression converters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as ts from "typescript";
|
|
6
|
+
import { IrMemberExpression } from "../../types.js";
|
|
7
|
+
import { getInferredType } from "./helpers.js";
|
|
8
|
+
import { convertExpression } from "../../expression-converter.js";
|
|
9
|
+
import { getBindingRegistry } from "../statements/declarations/registry.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve hierarchical binding for a member access
|
|
13
|
+
* Handles namespace.type and type.member patterns
|
|
14
|
+
*/
|
|
15
|
+
const resolveHierarchicalBinding = (
|
|
16
|
+
object: ReturnType<typeof convertExpression>,
|
|
17
|
+
propertyName: string
|
|
18
|
+
): IrMemberExpression["memberBinding"] => {
|
|
19
|
+
const registry = getBindingRegistry();
|
|
20
|
+
|
|
21
|
+
// Case 1: object is identifier → check if it's a namespace, then check if property is a type
|
|
22
|
+
if (object.kind === "identifier") {
|
|
23
|
+
const namespace = registry.getNamespace(object.name);
|
|
24
|
+
if (namespace) {
|
|
25
|
+
// Found namespace binding, check if property is a type within this namespace
|
|
26
|
+
// Note: After schema swap, we look up by alias (TS identifier)
|
|
27
|
+
const type = namespace.types.find((t) => t.alias === propertyName);
|
|
28
|
+
if (type) {
|
|
29
|
+
// This member access is namespace.type - we don't emit a member binding here
|
|
30
|
+
// because we're just accessing a type, not calling a member
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Case 2: object is member expression with a type reference → check if property is a member
|
|
37
|
+
if (object.kind === "memberAccess" && !object.isComputed) {
|
|
38
|
+
// Walk up the chain to find if this is a type reference
|
|
39
|
+
// For systemLinq.enumerable, the object is "systemLinq" and property is "enumerable"
|
|
40
|
+
if (object.object.kind === "identifier") {
|
|
41
|
+
const namespace = registry.getNamespace(object.object.name);
|
|
42
|
+
if (namespace && typeof object.property === "string") {
|
|
43
|
+
const type = namespace.types.find((t) => t.alias === object.property);
|
|
44
|
+
if (type) {
|
|
45
|
+
// The object is a type reference (namespace.type), now check if property is a member
|
|
46
|
+
const member = type.members.find((m) => m.alias === propertyName);
|
|
47
|
+
if (member) {
|
|
48
|
+
// Found a member binding!
|
|
49
|
+
return {
|
|
50
|
+
assembly: member.binding.assembly,
|
|
51
|
+
type: member.binding.type,
|
|
52
|
+
member: member.binding.member,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Convert property access or element access expression
|
|
65
|
+
*/
|
|
66
|
+
export const convertMemberExpression = (
|
|
67
|
+
node: ts.PropertyAccessExpression | ts.ElementAccessExpression,
|
|
68
|
+
checker: ts.TypeChecker
|
|
69
|
+
): IrMemberExpression => {
|
|
70
|
+
const isOptional = node.questionDotToken !== undefined;
|
|
71
|
+
const inferredType = getInferredType(node, checker);
|
|
72
|
+
|
|
73
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
74
|
+
const object = convertExpression(node.expression, checker);
|
|
75
|
+
const propertyName = node.name.text;
|
|
76
|
+
|
|
77
|
+
// Try to resolve hierarchical binding
|
|
78
|
+
const memberBinding = resolveHierarchicalBinding(object, propertyName);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
kind: "memberAccess",
|
|
82
|
+
object,
|
|
83
|
+
property: propertyName,
|
|
84
|
+
isComputed: false,
|
|
85
|
+
isOptional,
|
|
86
|
+
inferredType,
|
|
87
|
+
memberBinding,
|
|
88
|
+
};
|
|
89
|
+
} else {
|
|
90
|
+
return {
|
|
91
|
+
kind: "memberAccess",
|
|
92
|
+
object: convertExpression(node.expression, checker),
|
|
93
|
+
property: convertExpression(node.argumentExpression, checker),
|
|
94
|
+
isComputed: true,
|
|
95
|
+
isOptional,
|
|
96
|
+
inferredType,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|