@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.
Files changed (209) hide show
  1. package/package.json +34 -0
  2. package/scripts/update-golden-tests.ts +119 -0
  3. package/src/adapter-generator.ts +112 -0
  4. package/src/array.test.ts +301 -0
  5. package/src/constants.ts +32 -0
  6. package/src/core/exports.ts +36 -0
  7. package/src/core/imports.test.ts +83 -0
  8. package/src/core/imports.ts +243 -0
  9. package/src/core/index.ts +9 -0
  10. package/src/core/module-emitter/assembly.ts +83 -0
  11. package/src/core/module-emitter/header.ts +19 -0
  12. package/src/core/module-emitter/index.ts +17 -0
  13. package/src/core/module-emitter/namespace.ts +39 -0
  14. package/src/core/module-emitter/orchestrator.ts +98 -0
  15. package/src/core/module-emitter/separation.ts +41 -0
  16. package/src/core/module-emitter/static-container.ts +75 -0
  17. package/src/core/module-emitter.test.ts +154 -0
  18. package/src/core/module-emitter.ts +6 -0
  19. package/src/core/module-map.ts +218 -0
  20. package/src/core/options.ts +16 -0
  21. package/src/core/type-params.ts +34 -0
  22. package/src/emitter-types/context.ts +91 -0
  23. package/src/emitter-types/core.ts +138 -0
  24. package/src/emitter-types/csharp-types.ts +42 -0
  25. package/src/emitter-types/formatting.ts +13 -0
  26. package/src/emitter-types/fqn.ts +81 -0
  27. package/src/emitter-types/index.ts +31 -0
  28. package/src/emitter.ts +107 -0
  29. package/src/expression-emitter.ts +112 -0
  30. package/src/expressions/access.ts +104 -0
  31. package/src/expressions/calls.ts +264 -0
  32. package/src/expressions/collections.ts +354 -0
  33. package/src/expressions/functions.ts +71 -0
  34. package/src/expressions/identifiers.ts +125 -0
  35. package/src/expressions/index.test.ts +515 -0
  36. package/src/expressions/index.ts +38 -0
  37. package/src/expressions/literals.ts +138 -0
  38. package/src/expressions/operators.ts +211 -0
  39. package/src/expressions/other.ts +63 -0
  40. package/src/generator-exchange.ts +120 -0
  41. package/src/generator.test.ts +193 -0
  42. package/src/golden-tests/config-parser.ts +67 -0
  43. package/src/golden-tests/discovery.ts +61 -0
  44. package/src/golden-tests/index.ts +10 -0
  45. package/src/golden-tests/registration.ts +26 -0
  46. package/src/golden-tests/runner.ts +131 -0
  47. package/src/golden-tests/tree-builder.ts +43 -0
  48. package/src/golden-tests/types.ts +21 -0
  49. package/src/golden.test.ts +40 -0
  50. package/src/hierarchical-bindings.test.ts +258 -0
  51. package/src/index.ts +14 -0
  52. package/src/integration.test.ts +303 -0
  53. package/src/specialization/call-site-rewriting.test.ts +99 -0
  54. package/src/specialization/collection/expressions.ts +184 -0
  55. package/src/specialization/collection/index.ts +7 -0
  56. package/src/specialization/collection/orchestrator.ts +25 -0
  57. package/src/specialization/collection/statements.ts +91 -0
  58. package/src/specialization/collection.ts +10 -0
  59. package/src/specialization/generation.ts +189 -0
  60. package/src/specialization/generic-classes.test.ts +59 -0
  61. package/src/specialization/generic-functions.test.ts +292 -0
  62. package/src/specialization/helpers.ts +39 -0
  63. package/src/specialization/index.ts +28 -0
  64. package/src/specialization/interfaces.test.ts +151 -0
  65. package/src/specialization/naming.ts +34 -0
  66. package/src/specialization/substitution.ts +186 -0
  67. package/src/specialization/type-aliases.test.ts +134 -0
  68. package/src/specialization/types.ts +19 -0
  69. package/src/specialization-generator.ts +23 -0
  70. package/src/statement-emitter.ts +117 -0
  71. package/src/statements/blocks.ts +115 -0
  72. package/src/statements/classes/helpers.ts +9 -0
  73. package/src/statements/classes/index.ts +13 -0
  74. package/src/statements/classes/inline-types.ts +79 -0
  75. package/src/statements/classes/members/constructors.ts +123 -0
  76. package/src/statements/classes/members/index.ts +8 -0
  77. package/src/statements/classes/members/methods.ts +137 -0
  78. package/src/statements/classes/members/orchestrator.ts +33 -0
  79. package/src/statements/classes/members/properties.ts +62 -0
  80. package/src/statements/classes/members.ts +6 -0
  81. package/src/statements/classes/parameters.ts +69 -0
  82. package/src/statements/classes/properties.ts +95 -0
  83. package/src/statements/classes.ts +14 -0
  84. package/src/statements/control/conditionals.ts +134 -0
  85. package/src/statements/control/exceptions.ts +59 -0
  86. package/src/statements/control/index.ts +11 -0
  87. package/src/statements/control/loops.ts +250 -0
  88. package/src/statements/control.ts +14 -0
  89. package/src/statements/declarations/classes.ts +89 -0
  90. package/src/statements/declarations/enums.ts +32 -0
  91. package/src/statements/declarations/functions.ts +147 -0
  92. package/src/statements/declarations/index.ts +10 -0
  93. package/src/statements/declarations/interfaces.ts +116 -0
  94. package/src/statements/declarations/structs.test.ts +182 -0
  95. package/src/statements/declarations/type-aliases.ts +104 -0
  96. package/src/statements/declarations/variables.ts +159 -0
  97. package/src/statements/declarations.ts +13 -0
  98. package/src/statements/index.test.ts +258 -0
  99. package/src/statements/index.ts +43 -0
  100. package/src/type-assertion.test.ts +143 -0
  101. package/src/type-emitter.ts +18 -0
  102. package/src/types/arrays.ts +21 -0
  103. package/src/types/dictionaries.ts +52 -0
  104. package/src/types/emitter.ts +76 -0
  105. package/src/types/functions.ts +45 -0
  106. package/src/types/index.test.ts +116 -0
  107. package/src/types/index.ts +14 -0
  108. package/src/types/intersections.ts +19 -0
  109. package/src/types/literals.ts +26 -0
  110. package/src/types/objects.ts +27 -0
  111. package/src/types/parameters.test.ts +146 -0
  112. package/src/types/parameters.ts +95 -0
  113. package/src/types/primitives.ts +24 -0
  114. package/src/types/references.ts +187 -0
  115. package/src/types/unions.test.ts +397 -0
  116. package/src/types/unions.ts +62 -0
  117. package/src/types.ts +33 -0
  118. package/testcases/README.md +213 -0
  119. package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
  120. package/testcases/arrays/basic/config.yaml +1 -0
  121. package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
  122. package/testcases/arrays/destructuring/config.yaml +1 -0
  123. package/testcases/arrays/methods/ArrayMethods.ts +6 -0
  124. package/testcases/arrays/methods/config.yaml +1 -0
  125. package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
  126. package/testcases/arrays/multidimensional/config.yaml +1 -0
  127. package/testcases/arrays/spread/ArraySpread.ts +3 -0
  128. package/testcases/arrays/spread/config.yaml +1 -0
  129. package/testcases/async/basic/AsyncFunction.ts +5 -0
  130. package/testcases/async/basic/config.yaml +1 -0
  131. package/testcases/classes/abstract/AbstractClasses.ts +53 -0
  132. package/testcases/classes/abstract/config.yaml +1 -0
  133. package/testcases/classes/basic/Person.ts +12 -0
  134. package/testcases/classes/basic/config.yaml +1 -0
  135. package/testcases/classes/constructor/User.ts +11 -0
  136. package/testcases/classes/constructor/config.yaml +1 -0
  137. package/testcases/classes/field-inference/Counter.ts +11 -0
  138. package/testcases/classes/field-inference/config.yaml +1 -0
  139. package/testcases/classes/inheritance/Inheritance.ts +24 -0
  140. package/testcases/classes/inheritance/config.yaml +1 -0
  141. package/testcases/classes/static-members/MathHelper.ts +12 -0
  142. package/testcases/classes/static-members/config.yaml +1 -0
  143. package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
  144. package/testcases/control-flow/error-handling/config.yaml +1 -0
  145. package/testcases/control-flow/loops/Loops.ts +21 -0
  146. package/testcases/control-flow/loops/config.yaml +1 -0
  147. package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
  148. package/testcases/control-flow/switch/config.yaml +1 -0
  149. package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
  150. package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
  151. package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
  152. package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
  153. package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
  154. package/testcases/edge-cases/shadowing/config.yaml +1 -0
  155. package/testcases/functions/arrow/ArrowFunction.ts +5 -0
  156. package/testcases/functions/arrow/config.yaml +1 -0
  157. package/testcases/functions/basic/Greet.ts +3 -0
  158. package/testcases/functions/basic/config.yaml +1 -0
  159. package/testcases/functions/closures/Closures.ts +11 -0
  160. package/testcases/functions/closures/config.yaml +1 -0
  161. package/testcases/functions/default-params/DefaultParams.ts +7 -0
  162. package/testcases/functions/default-params/config.yaml +1 -0
  163. package/testcases/functions/rest-params/RestParams.ts +7 -0
  164. package/testcases/functions/rest-params/config.yaml +1 -0
  165. package/testcases/functions/type-guards/TypeGuards.ts +52 -0
  166. package/testcases/functions/type-guards/config.yaml +1 -0
  167. package/testcases/operators/logical/LogicalOperators.ts +11 -0
  168. package/testcases/operators/logical/config.yaml +1 -0
  169. package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
  170. package/testcases/operators/nullish-coalescing/config.yaml +1 -0
  171. package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
  172. package/testcases/operators/optional-chaining/config.yaml +1 -0
  173. package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
  174. package/testcases/real-world/advanced-generics/config.yaml +1 -0
  175. package/testcases/real-world/async-ops/async-ops.ts +67 -0
  176. package/testcases/real-world/async-ops/config.yaml +1 -0
  177. package/testcases/real-world/business-logic/business-logic.ts +215 -0
  178. package/testcases/real-world/business-logic/config.yaml +1 -0
  179. package/testcases/real-world/calculator/calculator.ts +29 -0
  180. package/testcases/real-world/calculator/config.yaml +1 -0
  181. package/testcases/real-world/data-structures/config.yaml +1 -0
  182. package/testcases/real-world/data-structures/data-structures.ts +133 -0
  183. package/testcases/real-world/functional/config.yaml +1 -0
  184. package/testcases/real-world/functional/functional.ts +116 -0
  185. package/testcases/real-world/shapes/config.yaml +1 -0
  186. package/testcases/real-world/shapes/shapes.ts +87 -0
  187. package/testcases/real-world/string-utils/config.yaml +1 -0
  188. package/testcases/real-world/string-utils/string-utils.ts +47 -0
  189. package/testcases/real-world/todo-list/config.yaml +1 -0
  190. package/testcases/real-world/todo-list/todo-list.ts +52 -0
  191. package/testcases/real-world/type-guards/config.yaml +1 -0
  192. package/testcases/real-world/type-guards/type-guards.ts +71 -0
  193. package/testcases/structs/basic/Point.ts +9 -0
  194. package/testcases/structs/basic/config.yaml +1 -0
  195. package/testcases/types/conditional/ConditionalTypes.ts +35 -0
  196. package/testcases/types/conditional/config.yaml +1 -0
  197. package/testcases/types/constants/ModuleConstants.ts +6 -0
  198. package/testcases/types/constants/config.yaml +1 -0
  199. package/testcases/types/generics/Generics.ts +15 -0
  200. package/testcases/types/generics/config.yaml +1 -0
  201. package/testcases/types/interfaces/Interfaces.ts +14 -0
  202. package/testcases/types/interfaces/config.yaml +1 -0
  203. package/testcases/types/mapped/MappedTypes.ts +27 -0
  204. package/testcases/types/mapped/config.yaml +1 -0
  205. package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
  206. package/testcases/types/tuples-intersections/config.yaml +1 -0
  207. package/testcases/types/unions/UnionTypes.ts +11 -0
  208. package/testcases/types/unions/config.yaml +1 -0
  209. 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";