@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
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
|
+
});
|