@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,116 @@
1
+ /**
2
+ * Interface declaration emission (as C# classes)
3
+ */
4
+
5
+ import { IrStatement } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent, indent } from "../../types.js";
7
+ import { emitType, emitTypeParameters } from "../../type-emitter.js";
8
+ import {
9
+ extractInlineObjectTypes,
10
+ emitExtractedType,
11
+ emitInterfaceMemberAsProperty,
12
+ } from "../classes.js";
13
+
14
+ /**
15
+ * Emit an interface declaration (as C# class)
16
+ */
17
+ export const emitInterfaceDeclaration = (
18
+ stmt: Extract<IrStatement, { kind: "interfaceDeclaration" }>,
19
+ context: EmitterContext
20
+ ): [string, EmitterContext] => {
21
+ // Per spec/16-types-and-interfaces.md §2.1:
22
+ // TypeScript interfaces map to C# classes (not C# interfaces)
23
+ // because TS interfaces are structural and we need nominal types in C#
24
+
25
+ const ind = getIndent(context);
26
+ let currentContext = context;
27
+
28
+ // Extract inline object types and emit them as separate classes
29
+ const extractedTypes = extractInlineObjectTypes(stmt.members);
30
+ const extractedClassCodes: string[] = [];
31
+
32
+ for (const extracted of extractedTypes) {
33
+ const [classCode, newContext] = emitExtractedType(
34
+ extracted,
35
+ currentContext
36
+ );
37
+ extractedClassCodes.push(classCode);
38
+ currentContext = newContext;
39
+ }
40
+
41
+ const parts: string[] = [];
42
+
43
+ // Access modifier
44
+ const accessibility = stmt.isExported ? "public" : "internal";
45
+ parts.push(accessibility);
46
+ // Emit struct or class based on isStruct flag
47
+ parts.push(stmt.isStruct ? "struct" : "class");
48
+ parts.push(stmt.name);
49
+
50
+ // Type parameters (if any)
51
+ if (stmt.typeParameters && stmt.typeParameters.length > 0) {
52
+ const [typeParamsStr, whereClauses, typeParamContext] = emitTypeParameters(
53
+ stmt.typeParameters,
54
+ currentContext
55
+ );
56
+ parts.push(typeParamsStr);
57
+ currentContext = typeParamContext;
58
+
59
+ // Extended interfaces/classes
60
+ if (stmt.extends && stmt.extends.length > 0) {
61
+ const extended: string[] = [];
62
+ for (const ext of stmt.extends) {
63
+ const [extType, newContext] = emitType(ext, currentContext);
64
+ currentContext = newContext;
65
+ extended.push(extType);
66
+ }
67
+ parts.push(":");
68
+ parts.push(extended.join(", "));
69
+ }
70
+
71
+ // Where clauses for type parameters
72
+ if (whereClauses.length > 0) {
73
+ parts.push("\n" + ind + " " + whereClauses.join("\n" + ind + " "));
74
+ }
75
+ } else {
76
+ // Extended interfaces/classes (no generics)
77
+ if (stmt.extends && stmt.extends.length > 0) {
78
+ const extended: string[] = [];
79
+ for (const ext of stmt.extends) {
80
+ const [extType, newContext] = emitType(ext, currentContext);
81
+ currentContext = newContext;
82
+ extended.push(extType);
83
+ }
84
+ parts.push(":");
85
+ parts.push(extended.join(", "));
86
+ }
87
+ }
88
+
89
+ // Class body with auto-properties
90
+ const bodyContext = indent(currentContext);
91
+ const members: string[] = [];
92
+
93
+ for (const member of stmt.members) {
94
+ const [memberCode, newContext] = emitInterfaceMemberAsProperty(
95
+ member,
96
+ bodyContext
97
+ );
98
+ members.push(memberCode);
99
+ currentContext = newContext;
100
+ }
101
+
102
+ const signature = parts.join(" ");
103
+ const memberCode = members.join("\n\n");
104
+ const mainClassCode = `${ind}${signature}\n${ind}{\n${memberCode}\n${ind}}`;
105
+
106
+ // Combine main interface and extracted classes (extracted classes come after)
107
+ const allParts: string[] = [];
108
+ allParts.push(mainClassCode);
109
+ if (extractedClassCodes.length > 0) {
110
+ allParts.push(...extractedClassCodes);
111
+ }
112
+
113
+ const code = allParts.join("\n");
114
+
115
+ return [code, currentContext];
116
+ };
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Tests for struct emission
3
+ */
4
+
5
+ import { describe, it } from "mocha";
6
+ import { expect } from "chai";
7
+ import { emitCSharpFile } from "../../index.js";
8
+ import {
9
+ IrModule,
10
+ IrClassDeclaration,
11
+ IrInterfaceDeclaration,
12
+ } from "@tsonic/frontend";
13
+
14
+ describe("Struct Emission", () => {
15
+ it("should emit struct for class with isStruct flag", () => {
16
+ const classDecl: IrClassDeclaration = {
17
+ kind: "classDeclaration",
18
+ name: "Point",
19
+ members: [
20
+ {
21
+ kind: "propertyDeclaration",
22
+ name: "x",
23
+ type: { kind: "primitiveType", name: "number" },
24
+ accessibility: "public",
25
+ isStatic: false,
26
+ isReadonly: false,
27
+ },
28
+ {
29
+ kind: "propertyDeclaration",
30
+ name: "y",
31
+ type: { kind: "primitiveType", name: "number" },
32
+ accessibility: "public",
33
+ isStatic: false,
34
+ isReadonly: false,
35
+ },
36
+ ],
37
+ implements: [],
38
+ isExported: true,
39
+ isStruct: true,
40
+ };
41
+
42
+ const module: IrModule = {
43
+ kind: "module",
44
+ filePath: "/src/Point.ts",
45
+ namespace: "Geometry",
46
+ className: "Point",
47
+ isStaticContainer: false,
48
+ imports: [],
49
+ body: [classDecl],
50
+ exports: [],
51
+ };
52
+
53
+ const result = emitCSharpFile(module);
54
+ expect(result).to.include("public struct Point");
55
+ expect(result).to.include("public double x");
56
+ expect(result).to.include("public double y");
57
+ expect(result).not.to.include("class Point");
58
+ });
59
+
60
+ it("should emit class for class without isStruct flag", () => {
61
+ const classDecl: IrClassDeclaration = {
62
+ kind: "classDeclaration",
63
+ name: "RegularClass",
64
+ members: [
65
+ {
66
+ kind: "propertyDeclaration",
67
+ name: "value",
68
+ type: { kind: "primitiveType", name: "number" },
69
+ accessibility: "public",
70
+ isStatic: false,
71
+ isReadonly: false,
72
+ },
73
+ ],
74
+ implements: [],
75
+ isExported: true,
76
+ isStruct: false,
77
+ };
78
+
79
+ const module: IrModule = {
80
+ kind: "module",
81
+ filePath: "/src/RegularClass.ts",
82
+ namespace: "App",
83
+ className: "RegularClass",
84
+ isStaticContainer: false,
85
+ imports: [],
86
+ body: [classDecl],
87
+ exports: [],
88
+ };
89
+
90
+ const result = emitCSharpFile(module);
91
+ expect(result).to.include("public class RegularClass");
92
+ expect(result).not.to.include("struct RegularClass");
93
+ });
94
+
95
+ it("should emit struct for interface with isStruct flag", () => {
96
+ const interfaceDecl: IrInterfaceDeclaration = {
97
+ kind: "interfaceDeclaration",
98
+ name: "Vector3D",
99
+ members: [
100
+ {
101
+ kind: "propertySignature",
102
+ name: "x",
103
+ type: { kind: "primitiveType", name: "number" },
104
+ isOptional: false,
105
+ isReadonly: false,
106
+ },
107
+ {
108
+ kind: "propertySignature",
109
+ name: "y",
110
+ type: { kind: "primitiveType", name: "number" },
111
+ isOptional: false,
112
+ isReadonly: false,
113
+ },
114
+ {
115
+ kind: "propertySignature",
116
+ name: "z",
117
+ type: { kind: "primitiveType", name: "number" },
118
+ isOptional: false,
119
+ isReadonly: false,
120
+ },
121
+ ],
122
+ extends: [],
123
+ isExported: true,
124
+ isStruct: true,
125
+ };
126
+
127
+ const module: IrModule = {
128
+ kind: "module",
129
+ filePath: "/src/Vector3D.ts",
130
+ namespace: "Geometry",
131
+ className: "Vector3D",
132
+ isStaticContainer: false,
133
+ imports: [],
134
+ body: [interfaceDecl],
135
+ exports: [],
136
+ };
137
+
138
+ const result = emitCSharpFile(module);
139
+ expect(result).to.include("public struct Vector3D");
140
+ expect(result).to.include("public double x");
141
+ expect(result).to.include("public double y");
142
+ expect(result).to.include("public double z");
143
+ expect(result).not.to.include("class Vector3D");
144
+ });
145
+
146
+ it("should not emit __brand property for struct", () => {
147
+ const classDecl: IrClassDeclaration = {
148
+ kind: "classDeclaration",
149
+ name: "Coord",
150
+ members: [
151
+ {
152
+ kind: "propertyDeclaration",
153
+ name: "x",
154
+ type: { kind: "primitiveType", name: "number" },
155
+ accessibility: "public",
156
+ isStatic: false,
157
+ isReadonly: false,
158
+ },
159
+ // __brand should already be filtered by the IR builder,
160
+ // but test that emitter handles it gracefully
161
+ ],
162
+ implements: [],
163
+ isExported: true,
164
+ isStruct: true,
165
+ };
166
+
167
+ const module: IrModule = {
168
+ kind: "module",
169
+ filePath: "/src/Coord.ts",
170
+ namespace: "Geometry",
171
+ className: "Coord",
172
+ isStaticContainer: false,
173
+ imports: [],
174
+ body: [classDecl],
175
+ exports: [],
176
+ };
177
+
178
+ const result = emitCSharpFile(module);
179
+ expect(result).to.include("public struct Coord");
180
+ expect(result).not.to.include("__brand");
181
+ });
182
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Type alias declaration emission
3
+ */
4
+
5
+ import { IrStatement } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent, indent } from "../../types.js";
7
+ import { emitType, emitTypeParameters } from "../../type-emitter.js";
8
+
9
+ /**
10
+ * Emit a type alias declaration
11
+ */
12
+ export const emitTypeAliasDeclaration = (
13
+ stmt: Extract<IrStatement, { kind: "typeAliasDeclaration" }>,
14
+ context: EmitterContext
15
+ ): [string, EmitterContext] => {
16
+ // Per spec/16-types-and-interfaces.md §3:
17
+ // - Structural type aliases generate C# classes with __Alias suffix
18
+ // - Simple aliases (primitives, references) emit as comments or using aliases
19
+
20
+ const ind = getIndent(context);
21
+ let currentContext = context;
22
+
23
+ // Check if this is a structural (object) type alias
24
+ if (stmt.type.kind === "objectType") {
25
+ // Generate a sealed class (or struct) for structural type alias
26
+ const parts: string[] = [];
27
+
28
+ const accessibility = stmt.isExported ? "public" : "internal";
29
+ parts.push(accessibility);
30
+ // Emit struct or sealed class based on isStruct flag
31
+ if (stmt.isStruct) {
32
+ parts.push("struct");
33
+ } else {
34
+ parts.push("sealed");
35
+ parts.push("class");
36
+ }
37
+ parts.push(`${stmt.name}__Alias`); // Add __Alias suffix per spec §3.4
38
+
39
+ // Type parameters (if any)
40
+ if (stmt.typeParameters && stmt.typeParameters.length > 0) {
41
+ const [typeParamsStr, whereClauses, typeParamContext] =
42
+ emitTypeParameters(stmt.typeParameters, currentContext);
43
+ parts.push(typeParamsStr);
44
+ currentContext = typeParamContext;
45
+
46
+ if (whereClauses.length > 0) {
47
+ parts.push(
48
+ "\n" + ind + " " + whereClauses.join("\n" + ind + " ")
49
+ );
50
+ }
51
+ }
52
+
53
+ // Generate properties from object type members
54
+ const bodyContext = indent(currentContext);
55
+ const properties: string[] = [];
56
+
57
+ if (stmt.type.kind === "objectType") {
58
+ for (const member of stmt.type.members) {
59
+ if (member.kind === "propertySignature") {
60
+ const propParts: string[] = [];
61
+ propParts.push("public");
62
+
63
+ // Property type
64
+ if (member.type) {
65
+ const [propType, newContext] = emitType(
66
+ member.type,
67
+ currentContext
68
+ );
69
+ currentContext = newContext;
70
+ // Optional members become nullable
71
+ const typeStr = member.isOptional ? `${propType}?` : propType;
72
+ propParts.push(typeStr);
73
+ } else {
74
+ propParts.push(member.isOptional ? "object?" : "object");
75
+ }
76
+
77
+ propParts.push(member.name);
78
+
79
+ // Readonly uses private set
80
+ const accessors = member.isReadonly
81
+ ? "{ get; private set; }"
82
+ : "{ get; set; }";
83
+ propParts.push(accessors);
84
+
85
+ // Default initializer
86
+ propParts.push("= default!;");
87
+
88
+ properties.push(`${getIndent(bodyContext)}${propParts.join(" ")}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ const signature = parts.join(" ");
94
+ const propsCode = properties.join("\n");
95
+ const code = `${ind}${signature}\n${ind}{\n${propsCode}\n${ind}}`;
96
+
97
+ return [code, currentContext];
98
+ }
99
+
100
+ // For non-structural aliases, emit as comment (C# using aliases are limited)
101
+ const [typeName, newContext] = emitType(stmt.type, context);
102
+ const code = `${ind}// type ${stmt.name} = ${typeName}`;
103
+ return [code, newContext];
104
+ };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Variable declaration emission
3
+ */
4
+
5
+ import { IrStatement, IrArrayPattern } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent } from "../../types.js";
7
+ import { emitExpression } from "../../expression-emitter.js";
8
+ import { emitType } from "../../type-emitter.js";
9
+
10
+ /**
11
+ * Emit a variable declaration
12
+ */
13
+ export const emitVariableDeclaration = (
14
+ stmt: Extract<IrStatement, { kind: "variableDeclaration" }>,
15
+ context: EmitterContext
16
+ ): [string, EmitterContext] => {
17
+ const ind = getIndent(context);
18
+ let currentContext = context;
19
+ const declarations: string[] = [];
20
+
21
+ for (const decl of stmt.declarations) {
22
+ let varDecl = "";
23
+
24
+ // In static contexts, variable declarations become fields with modifiers
25
+ if (context.isStatic && stmt.isExported) {
26
+ varDecl = "public static ";
27
+ if (stmt.declarationKind === "const") {
28
+ varDecl += "readonly ";
29
+ }
30
+ }
31
+
32
+ // Determine the C# type
33
+ // Priority: 1) Explicit/inferred IR type, 2) Arrow function inference, 3) var
34
+ if (
35
+ decl.type &&
36
+ !(decl.type.kind === "functionType" && !context.isStatic)
37
+ ) {
38
+ // Emit explicit type UNLESS it's a function type in a non-static context
39
+ // (let C# infer lambda types in local contexts)
40
+ // Note: For module-level exports, type is always set (from annotation or inference)
41
+ const [typeName, newContext] = emitType(decl.type, currentContext);
42
+ currentContext = newContext;
43
+ varDecl += `${typeName} `;
44
+ } else if (
45
+ decl.initializer &&
46
+ decl.initializer.kind === "arrowFunction" &&
47
+ context.isStatic
48
+ ) {
49
+ // For arrow functions in static context without explicit type, infer Func<> type
50
+ const arrowFunc = decl.initializer;
51
+ const paramTypes: string[] = [];
52
+
53
+ for (const param of arrowFunc.parameters) {
54
+ if (param.type) {
55
+ const [paramType, newCtx] = emitType(param.type, currentContext);
56
+ paramTypes.push(paramType);
57
+ currentContext = newCtx;
58
+ } else {
59
+ // ICE: Frontend validation (TSN7405) should have caught this.
60
+ const paramName =
61
+ param.pattern.kind === "identifierPattern"
62
+ ? param.pattern.name
63
+ : "unknown";
64
+ throw new Error(
65
+ `ICE: Untyped parameter '${paramName}' reached emitter - validation missed TSN7405`
66
+ );
67
+ }
68
+ }
69
+
70
+ // Get return type: explicit annotation, or infer from TS checker
71
+ const arrowReturnType =
72
+ arrowFunc.returnType ??
73
+ (arrowFunc.inferredType?.kind === "functionType"
74
+ ? arrowFunc.inferredType.returnType
75
+ : undefined);
76
+
77
+ if (!arrowReturnType) {
78
+ // ICE: Neither explicit nor inferred return type available
79
+ throw new Error(
80
+ "ICE: Arrow function without return type reached emitter - neither explicit nor inferred type available"
81
+ );
82
+ }
83
+
84
+ const [returnType, retCtx] = emitType(arrowReturnType, currentContext);
85
+ currentContext = retCtx;
86
+
87
+ const allTypes = [...paramTypes, returnType];
88
+ const funcType = `global::System.Func<${allTypes.join(", ")}>`;
89
+ varDecl += `${funcType} `;
90
+ } else {
91
+ varDecl += "var ";
92
+ }
93
+
94
+ // Handle different pattern types
95
+ if (decl.name.kind === "identifierPattern") {
96
+ // Simple identifier pattern
97
+ varDecl += decl.name.name;
98
+
99
+ // Add initializer if present
100
+ if (decl.initializer) {
101
+ const [initFrag, newContext] = emitExpression(
102
+ decl.initializer,
103
+ currentContext,
104
+ decl.type // Pass expected type for contextual typing (e.g., array literals)
105
+ );
106
+ currentContext = newContext;
107
+ varDecl += ` = ${initFrag.text}`;
108
+ }
109
+
110
+ declarations.push(`${ind}${varDecl};`);
111
+ } else if (decl.name.kind === "arrayPattern") {
112
+ // Array destructuring: const [a, b] = arr; -> var a = arr[0]; var b = arr[1];
113
+ if (!decl.initializer) {
114
+ // Array destructuring requires an initializer
115
+ declarations.push(
116
+ `${ind}${varDecl}/* array destructuring without initializer */;`
117
+ );
118
+ continue;
119
+ }
120
+
121
+ const [initFrag, newContext] = emitExpression(
122
+ decl.initializer,
123
+ currentContext,
124
+ decl.type
125
+ );
126
+ currentContext = newContext;
127
+
128
+ const arrayPattern = decl.name as IrArrayPattern;
129
+ // Use global:: prefix for Tsonic.Runtime.Array static helpers
130
+ for (let i = 0; i < arrayPattern.elements.length; i++) {
131
+ const element = arrayPattern.elements[i];
132
+ if (element && element.kind === "identifierPattern") {
133
+ // Use double literal for index (JavaScript uses doubles for all numbers)
134
+ const elementVarDecl = `${varDecl}${element.name} = global::Tsonic.Runtime.Array.get(${initFrag.text}, ${i}.0);`;
135
+ declarations.push(`${ind}${elementVarDecl}`);
136
+ }
137
+ // Skip undefined elements (holes in array pattern)
138
+ }
139
+ } else {
140
+ // Object destructuring or other patterns - not yet supported
141
+ varDecl += "/* destructuring */";
142
+
143
+ // Add initializer if present
144
+ if (decl.initializer) {
145
+ const [initFrag, newContext] = emitExpression(
146
+ decl.initializer,
147
+ currentContext,
148
+ decl.type
149
+ );
150
+ currentContext = newContext;
151
+ varDecl += ` = ${initFrag.text}`;
152
+ }
153
+
154
+ declarations.push(`${ind}${varDecl};`);
155
+ }
156
+ }
157
+
158
+ return [declarations.join("\n"), currentContext];
159
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Declaration emitters (variables, functions, classes, interfaces, enums, type aliases)
3
+ * Main dispatcher - re-exports from declarations/ subdirectory
4
+ */
5
+
6
+ export {
7
+ emitVariableDeclaration,
8
+ emitFunctionDeclaration,
9
+ emitClassDeclaration,
10
+ emitInterfaceDeclaration,
11
+ emitEnumDeclaration,
12
+ emitTypeAliasDeclaration,
13
+ } from "./declarations/index.js";