@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.
Files changed (145) hide show
  1. package/package.json +53 -0
  2. package/src/dependency-graph.ts +18 -0
  3. package/src/dotnet-metadata.ts +121 -0
  4. package/src/graph/builder.ts +81 -0
  5. package/src/graph/circular.ts +58 -0
  6. package/src/graph/extraction/exports.ts +55 -0
  7. package/src/graph/extraction/imports.ts +81 -0
  8. package/src/graph/extraction/index.ts +7 -0
  9. package/src/graph/extraction/orchestrator.ts +99 -0
  10. package/src/graph/extraction.ts +10 -0
  11. package/src/graph/helpers.ts +51 -0
  12. package/src/graph/index.ts +17 -0
  13. package/src/graph/types.ts +13 -0
  14. package/src/index.ts +80 -0
  15. package/src/ir/binding-resolution.test.ts +585 -0
  16. package/src/ir/builder/exports.ts +78 -0
  17. package/src/ir/builder/helpers.ts +27 -0
  18. package/src/ir/builder/imports.ts +153 -0
  19. package/src/ir/builder/index.ts +10 -0
  20. package/src/ir/builder/orchestrator.ts +178 -0
  21. package/src/ir/builder/statements.ts +55 -0
  22. package/src/ir/builder/types.ts +8 -0
  23. package/src/ir/builder/validation.ts +129 -0
  24. package/src/ir/builder.test.ts +581 -0
  25. package/src/ir/builder.ts +14 -0
  26. package/src/ir/converters/expressions/access.ts +99 -0
  27. package/src/ir/converters/expressions/calls.ts +137 -0
  28. package/src/ir/converters/expressions/collections.ts +84 -0
  29. package/src/ir/converters/expressions/functions.ts +62 -0
  30. package/src/ir/converters/expressions/helpers.ts +264 -0
  31. package/src/ir/converters/expressions/index.ts +43 -0
  32. package/src/ir/converters/expressions/literals.ts +22 -0
  33. package/src/ir/converters/expressions/operators.ts +147 -0
  34. package/src/ir/converters/expressions/other.ts +60 -0
  35. package/src/ir/converters/statements/control/blocks.ts +22 -0
  36. package/src/ir/converters/statements/control/conditionals.ts +67 -0
  37. package/src/ir/converters/statements/control/exceptions.ts +43 -0
  38. package/src/ir/converters/statements/control/index.ts +17 -0
  39. package/src/ir/converters/statements/control/loops.ts +99 -0
  40. package/src/ir/converters/statements/control.ts +17 -0
  41. package/src/ir/converters/statements/declarations/classes/constructors.ts +120 -0
  42. package/src/ir/converters/statements/declarations/classes/index.ts +12 -0
  43. package/src/ir/converters/statements/declarations/classes/methods.ts +61 -0
  44. package/src/ir/converters/statements/declarations/classes/orchestrator.ts +166 -0
  45. package/src/ir/converters/statements/declarations/classes/override-detection.ts +116 -0
  46. package/src/ir/converters/statements/declarations/classes/properties.ts +63 -0
  47. package/src/ir/converters/statements/declarations/classes.ts +6 -0
  48. package/src/ir/converters/statements/declarations/enums.ts +29 -0
  49. package/src/ir/converters/statements/declarations/functions.ts +39 -0
  50. package/src/ir/converters/statements/declarations/index.ts +14 -0
  51. package/src/ir/converters/statements/declarations/interfaces.ts +131 -0
  52. package/src/ir/converters/statements/declarations/registry.ts +45 -0
  53. package/src/ir/converters/statements/declarations/type-aliases.ts +25 -0
  54. package/src/ir/converters/statements/declarations/variables.ts +60 -0
  55. package/src/ir/converters/statements/declarations.ts +16 -0
  56. package/src/ir/converters/statements/helpers.ts +174 -0
  57. package/src/ir/converters/statements/index.ts +40 -0
  58. package/src/ir/expression-converter.ts +207 -0
  59. package/src/ir/generic-validator.ts +100 -0
  60. package/src/ir/hierarchical-bindings-e2e.test.ts +163 -0
  61. package/src/ir/index.ts +6 -0
  62. package/src/ir/statement-converter.ts +128 -0
  63. package/src/ir/type-converter/arrays.ts +20 -0
  64. package/src/ir/type-converter/converter.ts +10 -0
  65. package/src/ir/type-converter/functions.ts +22 -0
  66. package/src/ir/type-converter/index.ts +11 -0
  67. package/src/ir/type-converter/inference.ts +122 -0
  68. package/src/ir/type-converter/literals.ts +40 -0
  69. package/src/ir/type-converter/objects.ts +107 -0
  70. package/src/ir/type-converter/orchestrator.ts +85 -0
  71. package/src/ir/type-converter/patterns.ts +73 -0
  72. package/src/ir/type-converter/primitives.ts +57 -0
  73. package/src/ir/type-converter/references.ts +64 -0
  74. package/src/ir/type-converter/unions-intersections.ts +34 -0
  75. package/src/ir/type-converter.ts +13 -0
  76. package/src/ir/types/expressions.ts +215 -0
  77. package/src/ir/types/guards.ts +39 -0
  78. package/src/ir/types/helpers.ts +135 -0
  79. package/src/ir/types/index.ts +108 -0
  80. package/src/ir/types/ir-types.ts +96 -0
  81. package/src/ir/types/module.ts +57 -0
  82. package/src/ir/types/statements.ts +238 -0
  83. package/src/ir/types.ts +97 -0
  84. package/src/metadata/bindings-loader.test.ts +144 -0
  85. package/src/metadata/bindings-loader.ts +357 -0
  86. package/src/metadata/index.ts +15 -0
  87. package/src/metadata/library-loader.ts +153 -0
  88. package/src/metadata/loader.test.ts +156 -0
  89. package/src/metadata/loader.ts +382 -0
  90. package/src/program/bindings.test.ts +512 -0
  91. package/src/program/bindings.ts +253 -0
  92. package/src/program/config.ts +30 -0
  93. package/src/program/creation.ts +249 -0
  94. package/src/program/dependency-graph.ts +245 -0
  95. package/src/program/diagnostics.ts +103 -0
  96. package/src/program/index.ts +19 -0
  97. package/src/program/metadata.ts +68 -0
  98. package/src/program/queries.ts +18 -0
  99. package/src/program/types.ts +38 -0
  100. package/src/program.ts +13 -0
  101. package/src/resolver/dotnet-import-resolver.ts +226 -0
  102. package/src/resolver/import-resolution.ts +177 -0
  103. package/src/resolver/index.ts +18 -0
  104. package/src/resolver/namespace.test.ts +86 -0
  105. package/src/resolver/namespace.ts +42 -0
  106. package/src/resolver/naming.ts +38 -0
  107. package/src/resolver/path-resolution.ts +22 -0
  108. package/src/resolver/types.ts +15 -0
  109. package/src/resolver.test.ts +155 -0
  110. package/src/resolver.ts +14 -0
  111. package/src/symbol-table/builder.ts +114 -0
  112. package/src/symbol-table/creation.ts +42 -0
  113. package/src/symbol-table/helpers.ts +18 -0
  114. package/src/symbol-table/index.ts +13 -0
  115. package/src/symbol-table/queries.ts +42 -0
  116. package/src/symbol-table/types.ts +28 -0
  117. package/src/symbol-table.ts +14 -0
  118. package/src/types/bindings.ts +172 -0
  119. package/src/types/diagnostic.test.ts +164 -0
  120. package/src/types/diagnostic.ts +153 -0
  121. package/src/types/explicit-views.test.ts +113 -0
  122. package/src/types/explicit-views.ts +218 -0
  123. package/src/types/metadata.ts +229 -0
  124. package/src/types/module.ts +99 -0
  125. package/src/types/nested-types.test.ts +194 -0
  126. package/src/types/nested-types.ts +215 -0
  127. package/src/types/parameter-modifiers.ts +173 -0
  128. package/src/types/ref-parameters.test.ts +192 -0
  129. package/src/types/ref-parameters.ts +268 -0
  130. package/src/types/result.test.ts +157 -0
  131. package/src/types/result.ts +48 -0
  132. package/src/types/support-types.test.ts +81 -0
  133. package/src/types/support-types.ts +288 -0
  134. package/src/types/test-harness.ts +180 -0
  135. package/src/validation/exports.ts +98 -0
  136. package/src/validation/features.ts +89 -0
  137. package/src/validation/generics.ts +40 -0
  138. package/src/validation/helpers.ts +31 -0
  139. package/src/validation/imports.ts +97 -0
  140. package/src/validation/index.ts +11 -0
  141. package/src/validation/orchestrator.ts +51 -0
  142. package/src/validation/static-safety.ts +267 -0
  143. package/src/validator.test.ts +468 -0
  144. package/src/validator.ts +15 -0
  145. package/tsconfig.json +13 -0
package/src/index.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Tsonic Frontend - TypeScript parser and IR builder
3
+ */
4
+
5
+ export {
6
+ type DiagnosticSeverity,
7
+ type DiagnosticCode,
8
+ type SourceLocation,
9
+ type Diagnostic,
10
+ type DiagnosticsCollector,
11
+ createDiagnostic,
12
+ formatDiagnostic,
13
+ createDiagnosticsCollector,
14
+ addDiagnostic,
15
+ mergeDiagnostics,
16
+ isError as isDiagnosticError,
17
+ } from "./types/diagnostic.js";
18
+
19
+ export * from "./types/module.js";
20
+ export * from "./types/result.js";
21
+
22
+ export * from "./program.js";
23
+ export * from "./resolver.js";
24
+ export * from "./validator.js";
25
+ export * from "./symbol-table.js";
26
+ export * from "./dependency-graph.js";
27
+ export * from "./ir/index.js";
28
+ export * from "./dotnet-metadata.js";
29
+
30
+ import { createProgram, TsonicProgram, CompilerOptions } from "./program.js";
31
+ import { validateProgram } from "./validator.js";
32
+ import {
33
+ buildDependencyGraph,
34
+ DependencyAnalysis,
35
+ } from "./dependency-graph.js";
36
+ import { DiagnosticsCollector, mergeDiagnostics } from "./types/diagnostic.js";
37
+ import { Result, ok, error } from "./types/result.js";
38
+
39
+ export type CompileResult = {
40
+ readonly program: TsonicProgram;
41
+ readonly analysis: DependencyAnalysis;
42
+ };
43
+
44
+ /**
45
+ * Main entry point for compiling TypeScript files
46
+ */
47
+ export const compile = (
48
+ filePaths: readonly string[],
49
+ options: CompilerOptions
50
+ ): Result<CompileResult, DiagnosticsCollector> => {
51
+ // Create TypeScript program
52
+ const programResult = createProgram(filePaths, options);
53
+
54
+ if (!programResult.ok) {
55
+ return programResult;
56
+ }
57
+
58
+ const program = programResult.value;
59
+
60
+ // Validate ESM rules and TypeScript constraints
61
+ const validationDiagnostics = validateProgram(program);
62
+
63
+ // Build dependency graph and symbol table
64
+ const analysis = buildDependencyGraph(program, filePaths);
65
+
66
+ // Merge all diagnostics
67
+ const allDiagnostics = mergeDiagnostics(
68
+ mergeDiagnostics(validationDiagnostics, analysis.diagnostics),
69
+ validationDiagnostics
70
+ );
71
+
72
+ if (allDiagnostics.hasErrors) {
73
+ return error(allDiagnostics);
74
+ }
75
+
76
+ return ok({
77
+ program,
78
+ analysis,
79
+ });
80
+ };
@@ -0,0 +1,585 @@
1
+ /**
2
+ * Tests for binding resolution in IR conversion
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 { DotnetMetadataRegistry } from "../dotnet-metadata.js";
10
+ import { BindingRegistry } from "../program/bindings.js";
11
+ import { IrIdentifierExpression } from "./types.js";
12
+ import { createDotNetImportResolver } from "../resolver/dotnet-import-resolver.js";
13
+
14
+ describe("Binding Resolution in IR", () => {
15
+ const createTestProgram = (
16
+ source: string,
17
+ bindings?: BindingRegistry,
18
+ fileName = "/test/sample.ts"
19
+ ) => {
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: bindings || new BindingRegistry(),
55
+ dotnetResolver: createDotNetImportResolver("/test"),
56
+ };
57
+ };
58
+
59
+ describe("Global Identifier Resolution", () => {
60
+ it("should resolve console to CLR type when binding exists", () => {
61
+ const source = `
62
+ export function test() {
63
+ console.log("hello");
64
+ }
65
+ `;
66
+
67
+ const bindings = new BindingRegistry();
68
+ bindings.addBindings("/test/runtime.json", {
69
+ bindings: {
70
+ console: {
71
+ kind: "global",
72
+ assembly: "Tsonic.Runtime",
73
+ type: "Tsonic.Runtime.console",
74
+ },
75
+ },
76
+ });
77
+
78
+ const testProgram = createTestProgram(source, bindings);
79
+ const sourceFile = testProgram.sourceFiles[0];
80
+ if (!sourceFile) throw new Error("Failed to create source file");
81
+
82
+ const result = buildIrModule(sourceFile, testProgram, {
83
+ sourceRoot: "/test",
84
+ rootNamespace: "TestApp",
85
+ });
86
+
87
+ expect(result.ok).to.equal(true);
88
+ if (!result.ok) return;
89
+
90
+ const module = result.value;
91
+ const funcDecl = module.body[0];
92
+ expect(funcDecl?.kind).to.equal("functionDeclaration");
93
+
94
+ if (funcDecl?.kind !== "functionDeclaration") return;
95
+
96
+ // Find the console.log call in the function body
97
+ const exprStmt = funcDecl.body.statements[0];
98
+ expect(exprStmt?.kind).to.equal("expressionStatement");
99
+
100
+ if (exprStmt?.kind !== "expressionStatement") return;
101
+
102
+ const callExpr = exprStmt.expression;
103
+ expect(callExpr.kind).to.equal("call");
104
+
105
+ if (callExpr.kind !== "call") return;
106
+
107
+ const memberExpr = callExpr.callee;
108
+ expect(memberExpr.kind).to.equal("memberAccess");
109
+
110
+ if (memberExpr.kind !== "memberAccess") return;
111
+
112
+ const consoleExpr = memberExpr.object as IrIdentifierExpression;
113
+ expect(consoleExpr.kind).to.equal("identifier");
114
+ expect(consoleExpr.name).to.equal("console");
115
+ expect(consoleExpr.resolvedClrType).to.equal("Tsonic.Runtime.console");
116
+ expect(consoleExpr.resolvedAssembly).to.equal("Tsonic.Runtime");
117
+ });
118
+
119
+ it("should not resolve identifiers without bindings", () => {
120
+ const source = `
121
+ export function test() {
122
+ customGlobal.method();
123
+ }
124
+ `;
125
+
126
+ const testProgram = createTestProgram(source);
127
+ const sourceFile = testProgram.sourceFiles[0];
128
+ if (!sourceFile) throw new Error("Failed to create source file");
129
+
130
+ const result = buildIrModule(sourceFile, testProgram, {
131
+ sourceRoot: "/test",
132
+ rootNamespace: "TestApp",
133
+ });
134
+
135
+ expect(result.ok).to.equal(true);
136
+ if (!result.ok) return;
137
+
138
+ const module = result.value;
139
+ const funcDecl = module.body[0];
140
+ if (funcDecl?.kind !== "functionDeclaration") return;
141
+
142
+ const exprStmt = funcDecl.body.statements[0];
143
+ if (exprStmt?.kind !== "expressionStatement") return;
144
+
145
+ const callExpr = exprStmt.expression;
146
+ if (callExpr.kind !== "call") return;
147
+
148
+ const memberExpr = callExpr.callee;
149
+ if (memberExpr.kind !== "memberAccess") return;
150
+
151
+ const globalExpr = memberExpr.object as IrIdentifierExpression;
152
+ expect(globalExpr.kind).to.equal("identifier");
153
+ expect(globalExpr.name).to.equal("customGlobal");
154
+ expect(globalExpr.resolvedClrType).to.equal(undefined);
155
+ expect(globalExpr.resolvedAssembly).to.equal(undefined);
156
+ });
157
+
158
+ it("should resolve Math to CLR type", () => {
159
+ const source = `
160
+ export function test() {
161
+ return Math.sqrt(16);
162
+ }
163
+ `;
164
+
165
+ const bindings = new BindingRegistry();
166
+ bindings.addBindings("/test/runtime.json", {
167
+ bindings: {
168
+ Math: {
169
+ kind: "global",
170
+ assembly: "Tsonic.Runtime",
171
+ type: "Tsonic.Runtime.Math",
172
+ },
173
+ },
174
+ });
175
+
176
+ const testProgram = createTestProgram(source, bindings);
177
+ const sourceFile = testProgram.sourceFiles[0];
178
+ if (!sourceFile) throw new Error("Failed to create source file");
179
+
180
+ const result = buildIrModule(sourceFile, testProgram, {
181
+ sourceRoot: "/test",
182
+ rootNamespace: "TestApp",
183
+ });
184
+
185
+ expect(result.ok).to.equal(true);
186
+ if (!result.ok) return;
187
+
188
+ const module = result.value;
189
+ const funcDecl = module.body[0];
190
+ if (funcDecl?.kind !== "functionDeclaration") return;
191
+
192
+ const returnStmt = funcDecl.body.statements[0];
193
+ if (returnStmt?.kind !== "returnStatement" || !returnStmt.expression)
194
+ return;
195
+
196
+ const callExpr = returnStmt.expression;
197
+ if (callExpr.kind !== "call") return;
198
+
199
+ const memberExpr = callExpr.callee;
200
+ if (memberExpr.kind !== "memberAccess") return;
201
+
202
+ const mathExpr = memberExpr.object as IrIdentifierExpression;
203
+ expect(mathExpr.kind).to.equal("identifier");
204
+ expect(mathExpr.name).to.equal("Math");
205
+ expect(mathExpr.resolvedClrType).to.equal("Tsonic.Runtime.Math");
206
+ expect(mathExpr.resolvedAssembly).to.equal("Tsonic.Runtime");
207
+ });
208
+ });
209
+
210
+ describe("Module Import Resolution", () => {
211
+ it("should mark module imports with resolved CLR types in import extraction", () => {
212
+ const source = `
213
+ import { readFileSync } from "fs";
214
+ `;
215
+
216
+ const bindings = new BindingRegistry();
217
+ bindings.addBindings("/test/node.json", {
218
+ bindings: {
219
+ fs: {
220
+ kind: "module",
221
+ assembly: "Tsonic.NodeApi",
222
+ type: "Tsonic.NodeApi.fs",
223
+ },
224
+ },
225
+ });
226
+
227
+ const testProgram = createTestProgram(source, bindings);
228
+ const sourceFile = testProgram.sourceFiles[0];
229
+ if (!sourceFile) throw new Error("Failed to create source file");
230
+
231
+ const result = buildIrModule(sourceFile, testProgram, {
232
+ sourceRoot: "/test",
233
+ rootNamespace: "TestApp",
234
+ });
235
+
236
+ // May fail due to unresolved import, but we can still check the IR structure
237
+ if (!result.ok) {
238
+ // This is expected for bound modules that don't actually exist
239
+ return;
240
+ }
241
+
242
+ const module = result.value;
243
+ expect(module.imports).to.have.lengthOf(1);
244
+
245
+ const fsImport = module.imports[0];
246
+ if (!fsImport) throw new Error("No import found");
247
+ expect(fsImport.source).to.equal("fs");
248
+ expect(fsImport.resolvedClrType).to.equal("Tsonic.NodeApi.fs");
249
+ expect(fsImport.resolvedAssembly).to.equal("Tsonic.NodeApi");
250
+ });
251
+
252
+ it("should handle imports without bindings", () => {
253
+ const source = `
254
+ export const x = 42;
255
+ `;
256
+
257
+ const bindings = new BindingRegistry();
258
+ // No bindings added
259
+
260
+ const testProgram = createTestProgram(source, bindings);
261
+ const sourceFile = testProgram.sourceFiles[0];
262
+ if (!sourceFile) throw new Error("Failed to create source file");
263
+
264
+ const result = buildIrModule(sourceFile, testProgram, {
265
+ sourceRoot: "/test",
266
+ rootNamespace: "TestApp",
267
+ });
268
+
269
+ expect(result.ok).to.equal(true);
270
+ if (!result.ok) return;
271
+
272
+ const module = result.value;
273
+ // No imports in this test
274
+ expect(module.imports).to.have.lengthOf(0);
275
+ });
276
+ });
277
+
278
+ describe("Identifier Renaming with csharpName", () => {
279
+ it("should use csharpName when provided in binding", () => {
280
+ const source = `
281
+ export function test() {
282
+ console.log("hello");
283
+ }
284
+ `;
285
+
286
+ const bindings = new BindingRegistry();
287
+ bindings.addBindings("/test/runtime.json", {
288
+ bindings: {
289
+ console: {
290
+ kind: "global",
291
+ assembly: "System",
292
+ type: "System.Console",
293
+ csharpName: "Console",
294
+ },
295
+ },
296
+ });
297
+
298
+ const testProgram = createTestProgram(source, bindings);
299
+ const sourceFile = testProgram.sourceFiles[0];
300
+ if (!sourceFile) throw new Error("Failed to create source file");
301
+
302
+ const result = buildIrModule(sourceFile, testProgram, {
303
+ sourceRoot: "/test",
304
+ rootNamespace: "TestApp",
305
+ });
306
+
307
+ expect(result.ok).to.equal(true);
308
+ if (!result.ok) return;
309
+
310
+ const module = result.value;
311
+ const funcDecl = module.body[0];
312
+ expect(funcDecl?.kind).to.equal("functionDeclaration");
313
+
314
+ if (funcDecl?.kind !== "functionDeclaration") return;
315
+
316
+ // Find the console.log call
317
+ const exprStmt = funcDecl.body.statements[0];
318
+ expect(exprStmt?.kind).to.equal("expressionStatement");
319
+
320
+ if (exprStmt?.kind !== "expressionStatement") return;
321
+
322
+ const callExpr = exprStmt.expression;
323
+ expect(callExpr.kind).to.equal("call");
324
+
325
+ if (callExpr.kind !== "call") return;
326
+
327
+ const memberExpr = callExpr.callee;
328
+ expect(memberExpr.kind).to.equal("memberAccess");
329
+
330
+ if (memberExpr.kind !== "memberAccess") return;
331
+
332
+ // Check that the identifier has csharpName set
333
+ const consoleExpr = memberExpr.object as IrIdentifierExpression;
334
+ expect(consoleExpr.kind).to.equal("identifier");
335
+ expect(consoleExpr.name).to.equal("console");
336
+ expect(consoleExpr.csharpName).to.equal("Console");
337
+ expect(consoleExpr.resolvedClrType).to.equal("System.Console");
338
+ expect(consoleExpr.resolvedAssembly).to.equal("System");
339
+ });
340
+
341
+ it("should work without csharpName (use resolvedClrType)", () => {
342
+ const source = `
343
+ export function test() {
344
+ Math.sqrt(4);
345
+ }
346
+ `;
347
+
348
+ const bindings = new BindingRegistry();
349
+ bindings.addBindings("/test/runtime.json", {
350
+ bindings: {
351
+ Math: {
352
+ kind: "global",
353
+ assembly: "Tsonic.Runtime",
354
+ type: "Tsonic.Runtime.Math",
355
+ // No csharpName specified
356
+ },
357
+ },
358
+ });
359
+
360
+ const testProgram = createTestProgram(source, bindings);
361
+ const sourceFile = testProgram.sourceFiles[0];
362
+ if (!sourceFile) throw new Error("Failed to create source file");
363
+
364
+ const result = buildIrModule(sourceFile, testProgram, {
365
+ sourceRoot: "/test",
366
+ rootNamespace: "TestApp",
367
+ });
368
+
369
+ expect(result.ok).to.equal(true);
370
+ if (!result.ok) return;
371
+
372
+ const module = result.value;
373
+ const funcDecl = module.body[0];
374
+ if (funcDecl?.kind !== "functionDeclaration") return;
375
+
376
+ const exprStmt = funcDecl.body.statements[0];
377
+ if (exprStmt?.kind !== "expressionStatement") return;
378
+
379
+ const callExpr = exprStmt.expression;
380
+ if (callExpr.kind !== "call") return;
381
+
382
+ const memberExpr = callExpr.callee;
383
+ if (memberExpr.kind !== "memberAccess") return;
384
+
385
+ const mathExpr = memberExpr.object as IrIdentifierExpression;
386
+ expect(mathExpr.kind).to.equal("identifier");
387
+ expect(mathExpr.name).to.equal("Math");
388
+ expect(mathExpr.csharpName).to.equal(undefined); // No csharpName
389
+ expect(mathExpr.resolvedClrType).to.equal("Tsonic.Runtime.Math");
390
+ expect(mathExpr.resolvedAssembly).to.equal("Tsonic.Runtime");
391
+ });
392
+ });
393
+
394
+ describe("Hierarchical Binding Resolution", () => {
395
+ it("should resolve namespace.type.member hierarchical bindings", () => {
396
+ const source = `
397
+ import { systemLinq } from "system-linq";
398
+ export function test() {
399
+ return systemLinq.enumerable.selectMany([1, 2], x => [x, x * 2]);
400
+ }
401
+ `;
402
+
403
+ const bindings = new BindingRegistry();
404
+ bindings.addBindings("/test/system-linq.json", {
405
+ assembly: "System.Linq",
406
+ namespaces: [
407
+ {
408
+ name: "System.Linq",
409
+ alias: "systemLinq",
410
+ types: [
411
+ {
412
+ name: "Enumerable",
413
+ alias: "enumerable",
414
+ kind: "class",
415
+ members: [
416
+ {
417
+ kind: "method",
418
+ name: "SelectMany",
419
+ alias: "selectMany",
420
+ binding: {
421
+ assembly: "System.Linq",
422
+ type: "System.Linq.Enumerable",
423
+ member: "SelectMany",
424
+ },
425
+ },
426
+ ],
427
+ },
428
+ ],
429
+ },
430
+ ],
431
+ });
432
+
433
+ const testProgram = createTestProgram(source, bindings);
434
+ const sourceFile = testProgram.sourceFiles[0];
435
+ if (!sourceFile) throw new Error("Failed to create source file");
436
+
437
+ const result = buildIrModule(sourceFile, testProgram, {
438
+ sourceRoot: "/test",
439
+ rootNamespace: "TestApp",
440
+ });
441
+
442
+ // May fail due to unresolved import, but we can check the IR if it succeeds
443
+ if (!result.ok) {
444
+ // Expected for unresolved imports
445
+ return;
446
+ }
447
+
448
+ const module = result.value;
449
+ const funcDecl = module.body[0];
450
+ if (funcDecl?.kind !== "functionDeclaration") return;
451
+
452
+ const returnStmt = funcDecl.body.statements[0];
453
+ if (returnStmt?.kind !== "returnStatement" || !returnStmt.expression)
454
+ return;
455
+
456
+ const callExpr = returnStmt.expression;
457
+ if (callExpr.kind !== "call") return;
458
+
459
+ const memberExpr = callExpr.callee;
460
+ if (memberExpr.kind !== "memberAccess") return;
461
+
462
+ // Check that the member access has the hierarchical binding resolved
463
+ expect(memberExpr.memberBinding).to.not.equal(undefined);
464
+ expect(memberExpr.memberBinding?.assembly).to.equal("System.Linq");
465
+ expect(memberExpr.memberBinding?.type).to.equal("System.Linq.Enumerable");
466
+ expect(memberExpr.memberBinding?.member).to.equal("SelectMany");
467
+ });
468
+
469
+ it("should not resolve member bindings for non-matching patterns", () => {
470
+ const source = `
471
+ export function test() {
472
+ const obj = { prop: "value" };
473
+ return obj.prop;
474
+ }
475
+ `;
476
+
477
+ const bindings = new BindingRegistry();
478
+ // Add some bindings that won't match
479
+ bindings.addBindings("/test/unrelated.json", {
480
+ assembly: "Unrelated",
481
+ namespaces: [
482
+ {
483
+ name: "unrelated",
484
+ alias: "Unrelated",
485
+ types: [],
486
+ },
487
+ ],
488
+ });
489
+
490
+ const testProgram = createTestProgram(source, bindings);
491
+ const sourceFile = testProgram.sourceFiles[0];
492
+ if (!sourceFile) throw new Error("Failed to create source file");
493
+
494
+ const result = buildIrModule(sourceFile, testProgram, {
495
+ sourceRoot: "/test",
496
+ rootNamespace: "TestApp",
497
+ });
498
+
499
+ expect(result.ok).to.equal(true);
500
+ if (!result.ok) return;
501
+
502
+ const module = result.value;
503
+ const funcDecl = module.body[0];
504
+ if (funcDecl?.kind !== "functionDeclaration") return;
505
+
506
+ const returnStmt = funcDecl.body.statements[0];
507
+ if (returnStmt?.kind !== "returnStatement" || !returnStmt.expression)
508
+ return;
509
+
510
+ const memberExpr = returnStmt.expression;
511
+ if (memberExpr.kind !== "memberAccess") return;
512
+
513
+ // Should NOT have member binding for regular object property access
514
+ expect(memberExpr.memberBinding).to.equal(undefined);
515
+ });
516
+
517
+ it("should handle nested member access with partial binding matches", () => {
518
+ const source = `
519
+ import { myLib } from "my-lib";
520
+ export function test() {
521
+ // myLib.typeA is recognized, but .unknownMember is not in bindings
522
+ return myLib.typeA.unknownMember;
523
+ }
524
+ `;
525
+
526
+ const bindings = new BindingRegistry();
527
+ bindings.addBindings("/test/my-lib.json", {
528
+ assembly: "MyLib",
529
+ namespaces: [
530
+ {
531
+ name: "MyLib",
532
+ alias: "myLib",
533
+ types: [
534
+ {
535
+ name: "TypeA",
536
+ alias: "typeA",
537
+ kind: "class",
538
+ members: [
539
+ {
540
+ kind: "method",
541
+ name: "KnownMember",
542
+ alias: "knownMember",
543
+ binding: {
544
+ assembly: "MyLib",
545
+ type: "MyLib.TypeA",
546
+ member: "KnownMember",
547
+ },
548
+ },
549
+ ],
550
+ },
551
+ ],
552
+ },
553
+ ],
554
+ });
555
+
556
+ const testProgram = createTestProgram(source, bindings);
557
+ const sourceFile = testProgram.sourceFiles[0];
558
+ if (!sourceFile) throw new Error("Failed to create source file");
559
+
560
+ const result = buildIrModule(sourceFile, testProgram, {
561
+ sourceRoot: "/test",
562
+ rootNamespace: "TestApp",
563
+ });
564
+
565
+ // May fail due to unresolved import
566
+ if (!result.ok) {
567
+ return;
568
+ }
569
+
570
+ const module = result.value;
571
+ const funcDecl = module.body[0];
572
+ if (funcDecl?.kind !== "functionDeclaration") return;
573
+
574
+ const returnStmt = funcDecl.body.statements[0];
575
+ if (returnStmt?.kind !== "returnStatement" || !returnStmt.expression)
576
+ return;
577
+
578
+ const memberExpr = returnStmt.expression;
579
+ if (memberExpr.kind !== "memberAccess") return;
580
+
581
+ // unknownMember is not in the bindings, so memberBinding should be undefined
582
+ expect(memberExpr.memberBinding).to.equal(undefined);
583
+ });
584
+ });
585
+ });