@tsonic/emitter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +34 -0
- package/scripts/update-golden-tests.ts +119 -0
- package/src/adapter-generator.ts +112 -0
- package/src/array.test.ts +301 -0
- package/src/constants.ts +32 -0
- package/src/core/exports.ts +36 -0
- package/src/core/imports.test.ts +83 -0
- package/src/core/imports.ts +243 -0
- package/src/core/index.ts +9 -0
- package/src/core/module-emitter/assembly.ts +83 -0
- package/src/core/module-emitter/header.ts +19 -0
- package/src/core/module-emitter/index.ts +17 -0
- package/src/core/module-emitter/namespace.ts +39 -0
- package/src/core/module-emitter/orchestrator.ts +98 -0
- package/src/core/module-emitter/separation.ts +41 -0
- package/src/core/module-emitter/static-container.ts +75 -0
- package/src/core/module-emitter.test.ts +154 -0
- package/src/core/module-emitter.ts +6 -0
- package/src/core/module-map.ts +218 -0
- package/src/core/options.ts +16 -0
- package/src/core/type-params.ts +34 -0
- package/src/emitter-types/context.ts +91 -0
- package/src/emitter-types/core.ts +138 -0
- package/src/emitter-types/csharp-types.ts +42 -0
- package/src/emitter-types/formatting.ts +13 -0
- package/src/emitter-types/fqn.ts +81 -0
- package/src/emitter-types/index.ts +31 -0
- package/src/emitter.ts +107 -0
- package/src/expression-emitter.ts +112 -0
- package/src/expressions/access.ts +104 -0
- package/src/expressions/calls.ts +264 -0
- package/src/expressions/collections.ts +354 -0
- package/src/expressions/functions.ts +71 -0
- package/src/expressions/identifiers.ts +125 -0
- package/src/expressions/index.test.ts +515 -0
- package/src/expressions/index.ts +38 -0
- package/src/expressions/literals.ts +138 -0
- package/src/expressions/operators.ts +211 -0
- package/src/expressions/other.ts +63 -0
- package/src/generator-exchange.ts +120 -0
- package/src/generator.test.ts +193 -0
- package/src/golden-tests/config-parser.ts +67 -0
- package/src/golden-tests/discovery.ts +61 -0
- package/src/golden-tests/index.ts +10 -0
- package/src/golden-tests/registration.ts +26 -0
- package/src/golden-tests/runner.ts +131 -0
- package/src/golden-tests/tree-builder.ts +43 -0
- package/src/golden-tests/types.ts +21 -0
- package/src/golden.test.ts +40 -0
- package/src/hierarchical-bindings.test.ts +258 -0
- package/src/index.ts +14 -0
- package/src/integration.test.ts +303 -0
- package/src/specialization/call-site-rewriting.test.ts +99 -0
- package/src/specialization/collection/expressions.ts +184 -0
- package/src/specialization/collection/index.ts +7 -0
- package/src/specialization/collection/orchestrator.ts +25 -0
- package/src/specialization/collection/statements.ts +91 -0
- package/src/specialization/collection.ts +10 -0
- package/src/specialization/generation.ts +189 -0
- package/src/specialization/generic-classes.test.ts +59 -0
- package/src/specialization/generic-functions.test.ts +292 -0
- package/src/specialization/helpers.ts +39 -0
- package/src/specialization/index.ts +28 -0
- package/src/specialization/interfaces.test.ts +151 -0
- package/src/specialization/naming.ts +34 -0
- package/src/specialization/substitution.ts +186 -0
- package/src/specialization/type-aliases.test.ts +134 -0
- package/src/specialization/types.ts +19 -0
- package/src/specialization-generator.ts +23 -0
- package/src/statement-emitter.ts +117 -0
- package/src/statements/blocks.ts +115 -0
- package/src/statements/classes/helpers.ts +9 -0
- package/src/statements/classes/index.ts +13 -0
- package/src/statements/classes/inline-types.ts +79 -0
- package/src/statements/classes/members/constructors.ts +123 -0
- package/src/statements/classes/members/index.ts +8 -0
- package/src/statements/classes/members/methods.ts +137 -0
- package/src/statements/classes/members/orchestrator.ts +33 -0
- package/src/statements/classes/members/properties.ts +62 -0
- package/src/statements/classes/members.ts +6 -0
- package/src/statements/classes/parameters.ts +69 -0
- package/src/statements/classes/properties.ts +95 -0
- package/src/statements/classes.ts +14 -0
- package/src/statements/control/conditionals.ts +134 -0
- package/src/statements/control/exceptions.ts +59 -0
- package/src/statements/control/index.ts +11 -0
- package/src/statements/control/loops.ts +250 -0
- package/src/statements/control.ts +14 -0
- package/src/statements/declarations/classes.ts +89 -0
- package/src/statements/declarations/enums.ts +32 -0
- package/src/statements/declarations/functions.ts +147 -0
- package/src/statements/declarations/index.ts +10 -0
- package/src/statements/declarations/interfaces.ts +116 -0
- package/src/statements/declarations/structs.test.ts +182 -0
- package/src/statements/declarations/type-aliases.ts +104 -0
- package/src/statements/declarations/variables.ts +159 -0
- package/src/statements/declarations.ts +13 -0
- package/src/statements/index.test.ts +258 -0
- package/src/statements/index.ts +43 -0
- package/src/type-assertion.test.ts +143 -0
- package/src/type-emitter.ts +18 -0
- package/src/types/arrays.ts +21 -0
- package/src/types/dictionaries.ts +52 -0
- package/src/types/emitter.ts +76 -0
- package/src/types/functions.ts +45 -0
- package/src/types/index.test.ts +116 -0
- package/src/types/index.ts +14 -0
- package/src/types/intersections.ts +19 -0
- package/src/types/literals.ts +26 -0
- package/src/types/objects.ts +27 -0
- package/src/types/parameters.test.ts +146 -0
- package/src/types/parameters.ts +95 -0
- package/src/types/primitives.ts +24 -0
- package/src/types/references.ts +187 -0
- package/src/types/unions.test.ts +397 -0
- package/src/types/unions.ts +62 -0
- package/src/types.ts +33 -0
- package/testcases/README.md +213 -0
- package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
- package/testcases/arrays/basic/config.yaml +1 -0
- package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
- package/testcases/arrays/destructuring/config.yaml +1 -0
- package/testcases/arrays/methods/ArrayMethods.ts +6 -0
- package/testcases/arrays/methods/config.yaml +1 -0
- package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
- package/testcases/arrays/multidimensional/config.yaml +1 -0
- package/testcases/arrays/spread/ArraySpread.ts +3 -0
- package/testcases/arrays/spread/config.yaml +1 -0
- package/testcases/async/basic/AsyncFunction.ts +5 -0
- package/testcases/async/basic/config.yaml +1 -0
- package/testcases/classes/abstract/AbstractClasses.ts +53 -0
- package/testcases/classes/abstract/config.yaml +1 -0
- package/testcases/classes/basic/Person.ts +12 -0
- package/testcases/classes/basic/config.yaml +1 -0
- package/testcases/classes/constructor/User.ts +11 -0
- package/testcases/classes/constructor/config.yaml +1 -0
- package/testcases/classes/field-inference/Counter.ts +11 -0
- package/testcases/classes/field-inference/config.yaml +1 -0
- package/testcases/classes/inheritance/Inheritance.ts +24 -0
- package/testcases/classes/inheritance/config.yaml +1 -0
- package/testcases/classes/static-members/MathHelper.ts +12 -0
- package/testcases/classes/static-members/config.yaml +1 -0
- package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
- package/testcases/control-flow/error-handling/config.yaml +1 -0
- package/testcases/control-flow/loops/Loops.ts +21 -0
- package/testcases/control-flow/loops/config.yaml +1 -0
- package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
- package/testcases/control-flow/switch/config.yaml +1 -0
- package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
- package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
- package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
- package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
- package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
- package/testcases/edge-cases/shadowing/config.yaml +1 -0
- package/testcases/functions/arrow/ArrowFunction.ts +5 -0
- package/testcases/functions/arrow/config.yaml +1 -0
- package/testcases/functions/basic/Greet.ts +3 -0
- package/testcases/functions/basic/config.yaml +1 -0
- package/testcases/functions/closures/Closures.ts +11 -0
- package/testcases/functions/closures/config.yaml +1 -0
- package/testcases/functions/default-params/DefaultParams.ts +7 -0
- package/testcases/functions/default-params/config.yaml +1 -0
- package/testcases/functions/rest-params/RestParams.ts +7 -0
- package/testcases/functions/rest-params/config.yaml +1 -0
- package/testcases/functions/type-guards/TypeGuards.ts +52 -0
- package/testcases/functions/type-guards/config.yaml +1 -0
- package/testcases/operators/logical/LogicalOperators.ts +11 -0
- package/testcases/operators/logical/config.yaml +1 -0
- package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
- package/testcases/operators/nullish-coalescing/config.yaml +1 -0
- package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
- package/testcases/operators/optional-chaining/config.yaml +1 -0
- package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
- package/testcases/real-world/advanced-generics/config.yaml +1 -0
- package/testcases/real-world/async-ops/async-ops.ts +67 -0
- package/testcases/real-world/async-ops/config.yaml +1 -0
- package/testcases/real-world/business-logic/business-logic.ts +215 -0
- package/testcases/real-world/business-logic/config.yaml +1 -0
- package/testcases/real-world/calculator/calculator.ts +29 -0
- package/testcases/real-world/calculator/config.yaml +1 -0
- package/testcases/real-world/data-structures/config.yaml +1 -0
- package/testcases/real-world/data-structures/data-structures.ts +133 -0
- package/testcases/real-world/functional/config.yaml +1 -0
- package/testcases/real-world/functional/functional.ts +116 -0
- package/testcases/real-world/shapes/config.yaml +1 -0
- package/testcases/real-world/shapes/shapes.ts +87 -0
- package/testcases/real-world/string-utils/config.yaml +1 -0
- package/testcases/real-world/string-utils/string-utils.ts +47 -0
- package/testcases/real-world/todo-list/config.yaml +1 -0
- package/testcases/real-world/todo-list/todo-list.ts +52 -0
- package/testcases/real-world/type-guards/config.yaml +1 -0
- package/testcases/real-world/type-guards/type-guards.ts +71 -0
- package/testcases/structs/basic/Point.ts +9 -0
- package/testcases/structs/basic/config.yaml +1 -0
- package/testcases/types/conditional/ConditionalTypes.ts +35 -0
- package/testcases/types/conditional/config.yaml +1 -0
- package/testcases/types/constants/ModuleConstants.ts +6 -0
- package/testcases/types/constants/config.yaml +1 -0
- package/testcases/types/generics/Generics.ts +15 -0
- package/testcases/types/generics/config.yaml +1 -0
- package/testcases/types/interfaces/Interfaces.ts +14 -0
- package/testcases/types/interfaces/config.yaml +1 -0
- package/testcases/types/mapped/MappedTypes.ts +27 -0
- package/testcases/types/mapped/config.yaml +1 -0
- package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
- package/testcases/types/tuples-intersections/config.yaml +1 -0
- package/testcases/types/unions/UnionTypes.ts +11 -0
- package/testcases/types/unions/config.yaml +1 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier and type argument emitters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrExpression, IrType } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext, CSharpFragment } from "../types.js";
|
|
7
|
+
import { emitType } from "../type-emitter.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fallback mappings for well-known runtime globals
|
|
11
|
+
* Used when binding manifests are not available (e.g., in tests)
|
|
12
|
+
* All use global:: prefix for unambiguous resolution.
|
|
13
|
+
*/
|
|
14
|
+
const RUNTIME_FALLBACKS: Record<string, string> = {
|
|
15
|
+
console: "global::Tsonic.JSRuntime.console",
|
|
16
|
+
Math: "global::Tsonic.JSRuntime.Math",
|
|
17
|
+
JSON: "global::Tsonic.JSRuntime.JSON",
|
|
18
|
+
parseInt: "global::Tsonic.JSRuntime.Globals.parseInt",
|
|
19
|
+
parseFloat: "global::Tsonic.JSRuntime.Globals.parseFloat",
|
|
20
|
+
isNaN: "global::Tsonic.JSRuntime.Globals.isNaN",
|
|
21
|
+
isFinite: "global::Tsonic.JSRuntime.Globals.isFinite",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Emit an identifier, using resolved binding info if available
|
|
26
|
+
*/
|
|
27
|
+
export const emitIdentifier = (
|
|
28
|
+
expr: Extract<IrExpression, { kind: "identifier" }>,
|
|
29
|
+
context: EmitterContext
|
|
30
|
+
): [CSharpFragment, EmitterContext] => {
|
|
31
|
+
// Special case for undefined -> default
|
|
32
|
+
if (expr.name === "undefined") {
|
|
33
|
+
return [{ text: "default" }, context];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if this identifier is from an import
|
|
37
|
+
if (context.importBindings) {
|
|
38
|
+
const binding = context.importBindings.get(expr.name);
|
|
39
|
+
if (binding) {
|
|
40
|
+
// Imported identifier - always use fully-qualified reference
|
|
41
|
+
// Use pre-computed clrName directly (all resolution done when building binding)
|
|
42
|
+
if (binding.member) {
|
|
43
|
+
// Value import with member - Container.member
|
|
44
|
+
return [{ text: `${binding.clrName}.${binding.member}` }, context];
|
|
45
|
+
}
|
|
46
|
+
// Type, namespace, or default import - use clrName directly
|
|
47
|
+
return [{ text: binding.clrName }, context];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use custom C# name from binding if specified (with global:: prefix)
|
|
52
|
+
if (expr.csharpName && expr.resolvedAssembly) {
|
|
53
|
+
const fqn = `global::${expr.resolvedAssembly}.${expr.csharpName}`;
|
|
54
|
+
return [{ text: fqn }, context];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Use resolved binding if available (from binding manifest) with global:: prefix
|
|
58
|
+
// resolvedClrType is already the full CLR type name, just add global::
|
|
59
|
+
if (expr.resolvedClrType) {
|
|
60
|
+
const fqn = `global::${expr.resolvedClrType}`;
|
|
61
|
+
return [{ text: fqn }, context];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fallback for well-known runtime globals (only in js mode)
|
|
65
|
+
// In dotnet mode, there is no JS emulation - these globals don't exist
|
|
66
|
+
const runtime = context.options.runtime ?? "js";
|
|
67
|
+
if (runtime === "js") {
|
|
68
|
+
const fallback = RUNTIME_FALLBACKS[expr.name];
|
|
69
|
+
if (fallback) {
|
|
70
|
+
// RUNTIME_FALLBACKS already have global:: prefix
|
|
71
|
+
return [{ text: fallback }, context];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback: use identifier as-is
|
|
76
|
+
return [{ text: expr.name }, context];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Emit type arguments as C# generic type parameters
|
|
81
|
+
* Example: [string, number] → <string, double>
|
|
82
|
+
*/
|
|
83
|
+
export const emitTypeArguments = (
|
|
84
|
+
typeArgs: readonly IrType[],
|
|
85
|
+
context: EmitterContext
|
|
86
|
+
): [string, EmitterContext] => {
|
|
87
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
88
|
+
return ["", context];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let currentContext = context;
|
|
92
|
+
const typeStrings: string[] = [];
|
|
93
|
+
|
|
94
|
+
for (const typeArg of typeArgs) {
|
|
95
|
+
const [typeStr, newContext] = emitType(typeArg, currentContext);
|
|
96
|
+
currentContext = newContext;
|
|
97
|
+
typeStrings.push(typeStr);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return [`<${typeStrings.join(", ")}>`, currentContext];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generate specialized method/class name from type arguments
|
|
105
|
+
* Example: process with [string, number] → process__string__double
|
|
106
|
+
*/
|
|
107
|
+
export const generateSpecializedName = (
|
|
108
|
+
baseName: string,
|
|
109
|
+
typeArgs: readonly IrType[],
|
|
110
|
+
context: EmitterContext
|
|
111
|
+
): [string, EmitterContext] => {
|
|
112
|
+
let currentContext = context;
|
|
113
|
+
const typeNames: string[] = [];
|
|
114
|
+
|
|
115
|
+
for (const typeArg of typeArgs) {
|
|
116
|
+
const [typeName, newContext] = emitType(typeArg, currentContext);
|
|
117
|
+
currentContext = newContext;
|
|
118
|
+
// Sanitize type name for use in identifier (remove <>, ?, etc.)
|
|
119
|
+
const sanitized = typeName.replace(/[<>?,\s]/g, "_").replace(/\./g, "_");
|
|
120
|
+
typeNames.push(sanitized);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const specializedName = `${baseName}__${typeNames.join("__")}`;
|
|
124
|
+
return [specializedName, currentContext];
|
|
125
|
+
};
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Expression Emission
|
|
3
|
+
* Tests emission of literals, arrays, and template literals
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it } from "mocha";
|
|
7
|
+
import { expect } from "chai";
|
|
8
|
+
import { emitModule } from "../emitter.js";
|
|
9
|
+
import { IrModule } from "@tsonic/frontend";
|
|
10
|
+
|
|
11
|
+
describe("Expression Emission", () => {
|
|
12
|
+
it("should emit literals correctly", () => {
|
|
13
|
+
const module: IrModule = {
|
|
14
|
+
kind: "module",
|
|
15
|
+
filePath: "/src/test.ts",
|
|
16
|
+
namespace: "MyApp",
|
|
17
|
+
className: "test",
|
|
18
|
+
isStaticContainer: true,
|
|
19
|
+
imports: [],
|
|
20
|
+
body: [
|
|
21
|
+
{
|
|
22
|
+
kind: "variableDeclaration",
|
|
23
|
+
declarationKind: "const",
|
|
24
|
+
isExported: false,
|
|
25
|
+
declarations: [
|
|
26
|
+
{
|
|
27
|
+
kind: "variableDeclarator",
|
|
28
|
+
name: { kind: "identifierPattern", name: "str" },
|
|
29
|
+
initializer: { kind: "literal", value: "hello" },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
kind: "variableDeclarator",
|
|
33
|
+
name: { kind: "identifierPattern", name: "num" },
|
|
34
|
+
initializer: { kind: "literal", value: 42 },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
kind: "variableDeclarator",
|
|
38
|
+
name: { kind: "identifierPattern", name: "bool" },
|
|
39
|
+
initializer: { kind: "literal", value: true },
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
exports: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const result = emitModule(module);
|
|
48
|
+
|
|
49
|
+
expect(result).to.include('"hello"');
|
|
50
|
+
expect(result).to.include("42"); // C# handles implicit conversion
|
|
51
|
+
expect(result).to.include("true");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should emit array expressions", () => {
|
|
55
|
+
const module: IrModule = {
|
|
56
|
+
kind: "module",
|
|
57
|
+
filePath: "/src/test.ts",
|
|
58
|
+
namespace: "MyApp",
|
|
59
|
+
className: "test",
|
|
60
|
+
isStaticContainer: true,
|
|
61
|
+
imports: [],
|
|
62
|
+
body: [
|
|
63
|
+
{
|
|
64
|
+
kind: "variableDeclaration",
|
|
65
|
+
declarationKind: "const",
|
|
66
|
+
isExported: false,
|
|
67
|
+
declarations: [
|
|
68
|
+
{
|
|
69
|
+
kind: "variableDeclarator",
|
|
70
|
+
name: { kind: "identifierPattern", name: "arr" },
|
|
71
|
+
initializer: {
|
|
72
|
+
kind: "array",
|
|
73
|
+
elements: [
|
|
74
|
+
{ kind: "literal", value: 1 },
|
|
75
|
+
{ kind: "literal", value: 2 },
|
|
76
|
+
{ kind: "literal", value: 3 },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
exports: [],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const result = emitModule(module);
|
|
87
|
+
|
|
88
|
+
expect(result).to.include("new global::System.Collections.Generic.List<");
|
|
89
|
+
expect(result).to.include("1, 2, 3"); // C# handles implicit conversion
|
|
90
|
+
// No using statements - uses global:: FQN
|
|
91
|
+
expect(result).not.to.include("using System.Collections.Generic");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should emit template literals", () => {
|
|
95
|
+
const module: IrModule = {
|
|
96
|
+
kind: "module",
|
|
97
|
+
filePath: "/src/test.ts",
|
|
98
|
+
namespace: "MyApp",
|
|
99
|
+
className: "test",
|
|
100
|
+
isStaticContainer: true,
|
|
101
|
+
imports: [],
|
|
102
|
+
body: [
|
|
103
|
+
{
|
|
104
|
+
kind: "variableDeclaration",
|
|
105
|
+
declarationKind: "const",
|
|
106
|
+
isExported: false,
|
|
107
|
+
declarations: [
|
|
108
|
+
{
|
|
109
|
+
kind: "variableDeclarator",
|
|
110
|
+
name: { kind: "identifierPattern", name: "greeting" },
|
|
111
|
+
initializer: {
|
|
112
|
+
kind: "templateLiteral",
|
|
113
|
+
quasis: ["Hello ", "!"],
|
|
114
|
+
expressions: [{ kind: "identifier", name: "name" }],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
exports: [],
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = emitModule(module);
|
|
124
|
+
|
|
125
|
+
expect(result).to.include('$"Hello {name}!"');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should use csharpName for identifiers when provided", () => {
|
|
129
|
+
const module: IrModule = {
|
|
130
|
+
kind: "module",
|
|
131
|
+
filePath: "/src/test.ts",
|
|
132
|
+
namespace: "MyApp",
|
|
133
|
+
className: "test",
|
|
134
|
+
isStaticContainer: true,
|
|
135
|
+
imports: [],
|
|
136
|
+
body: [
|
|
137
|
+
{
|
|
138
|
+
kind: "expressionStatement",
|
|
139
|
+
expression: {
|
|
140
|
+
kind: "call",
|
|
141
|
+
callee: {
|
|
142
|
+
kind: "memberAccess",
|
|
143
|
+
object: {
|
|
144
|
+
kind: "identifier",
|
|
145
|
+
name: "console",
|
|
146
|
+
resolvedClrType: "System.Console",
|
|
147
|
+
resolvedAssembly: "System",
|
|
148
|
+
csharpName: "Console", // Custom C# name
|
|
149
|
+
},
|
|
150
|
+
property: "log",
|
|
151
|
+
isComputed: false,
|
|
152
|
+
isOptional: false,
|
|
153
|
+
},
|
|
154
|
+
arguments: [{ kind: "literal", value: "Hello with csharpName" }],
|
|
155
|
+
isOptional: false,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
exports: [],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = emitModule(module);
|
|
163
|
+
|
|
164
|
+
// Should use global:: prefixed assembly + csharpName
|
|
165
|
+
expect(result).to.include("global::System.Console.log");
|
|
166
|
+
// No using statements
|
|
167
|
+
expect(result).not.to.include("using System");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should use resolvedClrType when csharpName is not provided", () => {
|
|
171
|
+
const module: IrModule = {
|
|
172
|
+
kind: "module",
|
|
173
|
+
filePath: "/src/test.ts",
|
|
174
|
+
namespace: "MyApp",
|
|
175
|
+
className: "test",
|
|
176
|
+
isStaticContainer: true,
|
|
177
|
+
imports: [],
|
|
178
|
+
body: [
|
|
179
|
+
{
|
|
180
|
+
kind: "expressionStatement",
|
|
181
|
+
expression: {
|
|
182
|
+
kind: "call",
|
|
183
|
+
callee: {
|
|
184
|
+
kind: "memberAccess",
|
|
185
|
+
object: {
|
|
186
|
+
kind: "identifier",
|
|
187
|
+
name: "Math",
|
|
188
|
+
resolvedClrType: "Tsonic.JSRuntime.Math",
|
|
189
|
+
resolvedAssembly: "Tsonic.JSRuntime",
|
|
190
|
+
// No csharpName specified
|
|
191
|
+
},
|
|
192
|
+
property: "sqrt",
|
|
193
|
+
isComputed: false,
|
|
194
|
+
isOptional: false,
|
|
195
|
+
},
|
|
196
|
+
arguments: [{ kind: "literal", value: 16 }],
|
|
197
|
+
isOptional: false,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
exports: [],
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = emitModule(module);
|
|
205
|
+
|
|
206
|
+
// Should use global:: prefixed full type name when no csharpName
|
|
207
|
+
// resolvedClrType already contains full type name, just add global::
|
|
208
|
+
expect(result).to.include("global::Tsonic.JSRuntime.Math.sqrt");
|
|
209
|
+
// No using statements
|
|
210
|
+
expect(result).not.to.include("using Tsonic.JSRuntime");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should emit hierarchical member bindings correctly", () => {
|
|
214
|
+
const module: IrModule = {
|
|
215
|
+
kind: "module",
|
|
216
|
+
filePath: "/src/test.ts",
|
|
217
|
+
namespace: "MyApp",
|
|
218
|
+
className: "test",
|
|
219
|
+
isStaticContainer: true,
|
|
220
|
+
imports: [],
|
|
221
|
+
body: [
|
|
222
|
+
{
|
|
223
|
+
kind: "expressionStatement",
|
|
224
|
+
expression: {
|
|
225
|
+
kind: "call",
|
|
226
|
+
callee: {
|
|
227
|
+
kind: "memberAccess",
|
|
228
|
+
object: {
|
|
229
|
+
kind: "memberAccess",
|
|
230
|
+
object: { kind: "identifier", name: "systemLinq" },
|
|
231
|
+
property: "enumerable",
|
|
232
|
+
isComputed: false,
|
|
233
|
+
isOptional: false,
|
|
234
|
+
},
|
|
235
|
+
property: "selectMany",
|
|
236
|
+
isComputed: false,
|
|
237
|
+
isOptional: false,
|
|
238
|
+
// Hierarchical member binding from manifest
|
|
239
|
+
memberBinding: {
|
|
240
|
+
assembly: "System.Linq",
|
|
241
|
+
type: "System.Linq.Enumerable",
|
|
242
|
+
member: "SelectMany",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
arguments: [
|
|
246
|
+
{ kind: "array", elements: [{ kind: "literal", value: 1 }] },
|
|
247
|
+
],
|
|
248
|
+
isOptional: false,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
exports: [],
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const result = emitModule(module);
|
|
256
|
+
|
|
257
|
+
// Should emit full CLR type and member from binding with global:: prefix
|
|
258
|
+
expect(result).to.include(
|
|
259
|
+
"global::System.Linq.System.Linq.Enumerable.SelectMany"
|
|
260
|
+
);
|
|
261
|
+
// No using statements
|
|
262
|
+
expect(result).not.to.include("using System.Linq");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should emit hierarchical member bindings without emitting intermediate objects", () => {
|
|
266
|
+
const module: IrModule = {
|
|
267
|
+
kind: "module",
|
|
268
|
+
filePath: "/src/test.ts",
|
|
269
|
+
namespace: "MyApp",
|
|
270
|
+
className: "test",
|
|
271
|
+
isStaticContainer: true,
|
|
272
|
+
imports: [],
|
|
273
|
+
body: [
|
|
274
|
+
{
|
|
275
|
+
kind: "variableDeclaration",
|
|
276
|
+
declarationKind: "const",
|
|
277
|
+
isExported: false,
|
|
278
|
+
declarations: [
|
|
279
|
+
{
|
|
280
|
+
kind: "variableDeclarator",
|
|
281
|
+
name: { kind: "identifierPattern", name: "result" },
|
|
282
|
+
initializer: {
|
|
283
|
+
kind: "call",
|
|
284
|
+
callee: {
|
|
285
|
+
kind: "memberAccess",
|
|
286
|
+
object: {
|
|
287
|
+
kind: "memberAccess",
|
|
288
|
+
object: { kind: "identifier", name: "myLib" },
|
|
289
|
+
property: "math",
|
|
290
|
+
isComputed: false,
|
|
291
|
+
isOptional: false,
|
|
292
|
+
},
|
|
293
|
+
property: "add",
|
|
294
|
+
isComputed: false,
|
|
295
|
+
isOptional: false,
|
|
296
|
+
memberBinding: {
|
|
297
|
+
assembly: "MyLib",
|
|
298
|
+
type: "MyLib.Math",
|
|
299
|
+
member: "Add",
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
arguments: [
|
|
303
|
+
{ kind: "literal", value: 1 },
|
|
304
|
+
{ kind: "literal", value: 2 },
|
|
305
|
+
],
|
|
306
|
+
isOptional: false,
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
exports: [],
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const result = emitModule(module);
|
|
316
|
+
|
|
317
|
+
// Should emit global::MyLib.MyLib.Math.Add directly
|
|
318
|
+
expect(result).to.include("global::MyLib.MyLib.Math.Add");
|
|
319
|
+
// Should NOT include myLib.math (intermediate objects shouldn't appear)
|
|
320
|
+
expect(result).not.to.include("myLib.math");
|
|
321
|
+
// No using statements
|
|
322
|
+
expect(result).not.to.include("using MyLib");
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should handle member access without binding (regular property access)", () => {
|
|
326
|
+
const module: IrModule = {
|
|
327
|
+
kind: "module",
|
|
328
|
+
filePath: "/src/test.ts",
|
|
329
|
+
namespace: "MyApp",
|
|
330
|
+
className: "test",
|
|
331
|
+
isStaticContainer: true,
|
|
332
|
+
imports: [],
|
|
333
|
+
body: [
|
|
334
|
+
{
|
|
335
|
+
kind: "expressionStatement",
|
|
336
|
+
expression: {
|
|
337
|
+
kind: "memberAccess",
|
|
338
|
+
object: { kind: "identifier", name: "obj" },
|
|
339
|
+
property: "property",
|
|
340
|
+
isComputed: false,
|
|
341
|
+
isOptional: false,
|
|
342
|
+
// No memberBinding - regular property access
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
exports: [],
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const result = emitModule(module);
|
|
350
|
+
|
|
351
|
+
// Should emit regular property access
|
|
352
|
+
expect(result).to.include("obj.property");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("should escape special characters in dictionary keys", () => {
|
|
356
|
+
const module: IrModule = {
|
|
357
|
+
kind: "module",
|
|
358
|
+
filePath: "/src/test.ts",
|
|
359
|
+
namespace: "MyApp",
|
|
360
|
+
className: "test",
|
|
361
|
+
isStaticContainer: true,
|
|
362
|
+
imports: [],
|
|
363
|
+
body: [
|
|
364
|
+
{
|
|
365
|
+
kind: "variableDeclaration",
|
|
366
|
+
declarationKind: "const",
|
|
367
|
+
isExported: false,
|
|
368
|
+
declarations: [
|
|
369
|
+
{
|
|
370
|
+
kind: "variableDeclarator",
|
|
371
|
+
name: { kind: "identifierPattern", name: "dict" },
|
|
372
|
+
type: {
|
|
373
|
+
kind: "dictionaryType",
|
|
374
|
+
keyType: { kind: "primitiveType", name: "string" },
|
|
375
|
+
valueType: { kind: "primitiveType", name: "number" },
|
|
376
|
+
},
|
|
377
|
+
initializer: {
|
|
378
|
+
kind: "object",
|
|
379
|
+
properties: [
|
|
380
|
+
{
|
|
381
|
+
kind: "property",
|
|
382
|
+
key: 'key"with"quotes',
|
|
383
|
+
value: { kind: "literal", value: 1 },
|
|
384
|
+
shorthand: false,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
kind: "property",
|
|
388
|
+
key: "key\\with\\backslashes",
|
|
389
|
+
value: { kind: "literal", value: 2 },
|
|
390
|
+
shorthand: false,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
kind: "property",
|
|
394
|
+
key: "key\nwith\nnewlines",
|
|
395
|
+
value: { kind: "literal", value: 3 },
|
|
396
|
+
shorthand: false,
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
contextualType: {
|
|
400
|
+
kind: "dictionaryType",
|
|
401
|
+
keyType: { kind: "primitiveType", name: "string" },
|
|
402
|
+
valueType: { kind: "primitiveType", name: "number" },
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
exports: [],
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const result = emitModule(module);
|
|
413
|
+
|
|
414
|
+
// Should escape quotes
|
|
415
|
+
expect(result).to.include('["key\\"with\\"quotes"]');
|
|
416
|
+
// Should escape backslashes
|
|
417
|
+
expect(result).to.include('["key\\\\with\\\\backslashes"]');
|
|
418
|
+
// Should escape newlines
|
|
419
|
+
expect(result).to.include('["key\\nwith\\nnewlines"]');
|
|
420
|
+
// Should be a Dictionary with global:: prefix
|
|
421
|
+
expect(result).to.include(
|
|
422
|
+
"new global::System.Collections.Generic.Dictionary<string, double>"
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("should infer arrow function return type from inferredType", () => {
|
|
427
|
+
const module: IrModule = {
|
|
428
|
+
kind: "module",
|
|
429
|
+
filePath: "/src/test.ts",
|
|
430
|
+
namespace: "MyApp",
|
|
431
|
+
className: "test",
|
|
432
|
+
isStaticContainer: true,
|
|
433
|
+
imports: [],
|
|
434
|
+
body: [
|
|
435
|
+
{
|
|
436
|
+
kind: "variableDeclaration",
|
|
437
|
+
declarationKind: "const",
|
|
438
|
+
isExported: true,
|
|
439
|
+
declarations: [
|
|
440
|
+
{
|
|
441
|
+
kind: "variableDeclarator",
|
|
442
|
+
name: { kind: "identifierPattern", name: "add" },
|
|
443
|
+
// No explicit type annotation
|
|
444
|
+
initializer: {
|
|
445
|
+
kind: "arrowFunction",
|
|
446
|
+
parameters: [
|
|
447
|
+
{
|
|
448
|
+
kind: "parameter",
|
|
449
|
+
pattern: { kind: "identifierPattern", name: "a" },
|
|
450
|
+
type: { kind: "primitiveType", name: "number" },
|
|
451
|
+
isOptional: false,
|
|
452
|
+
isRest: false,
|
|
453
|
+
passing: "value",
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
kind: "parameter",
|
|
457
|
+
pattern: { kind: "identifierPattern", name: "b" },
|
|
458
|
+
type: { kind: "primitiveType", name: "number" },
|
|
459
|
+
isOptional: false,
|
|
460
|
+
isRest: false,
|
|
461
|
+
passing: "value",
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
// No explicit returnType
|
|
465
|
+
body: {
|
|
466
|
+
kind: "binary",
|
|
467
|
+
operator: "+",
|
|
468
|
+
left: { kind: "identifier", name: "a" },
|
|
469
|
+
right: { kind: "identifier", name: "b" },
|
|
470
|
+
},
|
|
471
|
+
isAsync: false,
|
|
472
|
+
// TypeScript inferred type
|
|
473
|
+
inferredType: {
|
|
474
|
+
kind: "functionType",
|
|
475
|
+
parameters: [
|
|
476
|
+
{
|
|
477
|
+
kind: "parameter",
|
|
478
|
+
pattern: { kind: "identifierPattern", name: "a" },
|
|
479
|
+
type: { kind: "primitiveType", name: "number" },
|
|
480
|
+
isOptional: false,
|
|
481
|
+
isRest: false,
|
|
482
|
+
passing: "value",
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
kind: "parameter",
|
|
486
|
+
pattern: { kind: "identifierPattern", name: "b" },
|
|
487
|
+
type: { kind: "primitiveType", name: "number" },
|
|
488
|
+
isOptional: false,
|
|
489
|
+
isRest: false,
|
|
490
|
+
passing: "value",
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
returnType: { kind: "primitiveType", name: "number" },
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
exports: [
|
|
501
|
+
{
|
|
502
|
+
kind: "named",
|
|
503
|
+
name: "add",
|
|
504
|
+
localName: "add",
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const result = emitModule(module);
|
|
510
|
+
|
|
511
|
+
// Should infer Func<double, double, double> from inferredType with global:: prefix
|
|
512
|
+
expect(result).to.include("global::System.Func<double, double, double>");
|
|
513
|
+
expect(result).to.include("public static");
|
|
514
|
+
});
|
|
515
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression emitters barrel exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Literals
|
|
6
|
+
export { emitLiteral } from "./literals.js";
|
|
7
|
+
|
|
8
|
+
// Identifiers and type helpers
|
|
9
|
+
export {
|
|
10
|
+
emitIdentifier,
|
|
11
|
+
emitTypeArguments,
|
|
12
|
+
generateSpecializedName,
|
|
13
|
+
} from "./identifiers.js";
|
|
14
|
+
|
|
15
|
+
// Collections
|
|
16
|
+
export { emitArray, emitObject } from "./collections.js";
|
|
17
|
+
|
|
18
|
+
// Member access
|
|
19
|
+
export { emitMemberAccess } from "./access.js";
|
|
20
|
+
|
|
21
|
+
// Calls
|
|
22
|
+
export { emitCall, emitNew } from "./calls.js";
|
|
23
|
+
|
|
24
|
+
// Operators
|
|
25
|
+
export {
|
|
26
|
+
emitBinary,
|
|
27
|
+
emitLogical,
|
|
28
|
+
emitUnary,
|
|
29
|
+
emitUpdate,
|
|
30
|
+
emitAssignment,
|
|
31
|
+
emitConditional,
|
|
32
|
+
} from "./operators.js";
|
|
33
|
+
|
|
34
|
+
// Functions
|
|
35
|
+
export { emitFunctionExpression, emitArrowFunction } from "./functions.js";
|
|
36
|
+
|
|
37
|
+
// Other
|
|
38
|
+
export { emitTemplateLiteral, emitSpread, emitAwait } from "./other.js";
|