@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
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@tsonic/emitter",
3
+ "version": "0.0.1",
4
+ "description": "C# code generator for Tsonic compiler",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc -b",
16
+ "clean": "rm -rf dist *.tsbuildinfo",
17
+ "test": "mocha 'dist/**/*.test.js'",
18
+ "test:watch": "mocha 'dist/**/*.test.js' --watch"
19
+ },
20
+ "dependencies": {
21
+ "@tsonic/frontend": "0.0.1"
22
+ },
23
+ "devDependencies": {
24
+ "@types/chai": "5.2.2",
25
+ "@types/mocha": "10.0.10",
26
+ "@types/node": "20.14.0",
27
+ "chai": "5.3.3",
28
+ "mocha": "11.7.2",
29
+ "yaml": "^2.8.1"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Update golden test expected files
3
+ * Run with: npx tsx scripts/update-golden-tests.ts
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { compile, buildIr } from "@tsonic/frontend";
10
+ import { emitCSharpFiles } from "../src/emitter.js";
11
+ import { parseConfigYaml } from "../src/golden-tests/config-parser.js";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const TESTCASES_DIR = path.join(__dirname, "../testcases");
16
+
17
+ interface TestEntry {
18
+ input: string;
19
+ title: string;
20
+ }
21
+
22
+ const walkDir = (dir: string, pathParts: string[] = []): void => {
23
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
24
+ const hasConfig = entries.some((e) => e.name === "config.yaml");
25
+
26
+ if (hasConfig) {
27
+ const configPath = path.join(dir, "config.yaml");
28
+ const configContent = fs.readFileSync(configPath, "utf-8");
29
+ const testEntries = parseConfigYaml(configContent);
30
+
31
+ for (const entry of testEntries) {
32
+ const inputPath = path.join(dir, entry.input);
33
+ const baseName = path.basename(entry.input, ".ts");
34
+ const expectedPath = path.join(dir, `${baseName}.cs`);
35
+
36
+ console.log(`Updating: ${pathParts.join("/")}/${baseName}`);
37
+
38
+ try {
39
+ // Build namespace from path parts
40
+ const namespaceParts = pathParts.map((part) => part.replace(/-/g, ""));
41
+ const rootNamespace = ["TestCases", ...namespaceParts].join(".");
42
+ const sourceRoot = path.dirname(inputPath);
43
+
44
+ // Compile
45
+ const compileResult = compile([inputPath], {
46
+ sourceRoot,
47
+ rootNamespace,
48
+ useStandardLib: true,
49
+ });
50
+
51
+ if (!compileResult.ok) {
52
+ console.error(` ERROR: Compilation failed`);
53
+ continue;
54
+ }
55
+
56
+ // Build IR
57
+ const irResult = buildIr(compileResult.value.program, {
58
+ sourceRoot,
59
+ rootNamespace,
60
+ });
61
+
62
+ if (!irResult.ok) {
63
+ console.error(` ERROR: IR build failed`);
64
+ continue;
65
+ }
66
+
67
+ // Emit C#
68
+ const emitResult = emitCSharpFiles(irResult.value, { rootNamespace });
69
+
70
+ if (!emitResult.ok) {
71
+ console.error(` ERROR: Emit failed`);
72
+ for (const err of emitResult.errors) {
73
+ console.error(` ${err.code}: ${err.message}`);
74
+ }
75
+ continue;
76
+ }
77
+
78
+ const csharpFiles = emitResult.files;
79
+
80
+ // Find the generated file
81
+ const className = baseName;
82
+ const generatedKey = Array.from(csharpFiles.keys()).find((key) =>
83
+ key.endsWith(`${className}.cs`)
84
+ );
85
+
86
+ if (!generatedKey) {
87
+ console.error(` ERROR: No generated file found`);
88
+ continue;
89
+ }
90
+
91
+ const fullOutput = csharpFiles.get(generatedKey)!;
92
+
93
+ // Strip the header (first 4 lines: Generated from, Generated at, WARNING, blank)
94
+ const lines = fullOutput.split("\n");
95
+ const bodyStartIndex = lines.findIndex(
96
+ (line, i) => i > 0 && line.startsWith("using")
97
+ );
98
+ const body = lines.slice(bodyStartIndex).join("\n");
99
+
100
+ // Write to expected file
101
+ fs.writeFileSync(expectedPath, body);
102
+ console.log(` OK: ${expectedPath}`);
103
+ } catch (err) {
104
+ console.error(` ERROR: ${err}`);
105
+ }
106
+ }
107
+ }
108
+
109
+ // Recurse into subdirectories
110
+ for (const entry of entries) {
111
+ if (entry.isDirectory()) {
112
+ walkDir(path.join(dir, entry.name), [...pathParts, entry.name]);
113
+ }
114
+ }
115
+ };
116
+
117
+ console.log("Updating golden test expected files...\n");
118
+ walkDir(TESTCASES_DIR);
119
+ console.log("\nDone!");
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Adapter Generator - Generate C# adapters for structural constraints
3
+ * Per spec/15-generics.md §4 - Structural Constraints & Adapters
4
+ */
5
+
6
+ import { IrTypeParameter } from "@tsonic/frontend";
7
+ import { EmitterContext, getIndent, indent } from "./types.js";
8
+ import { emitType } from "./type-emitter.js";
9
+
10
+ /**
11
+ * Generate adapter interface and wrapper class for a structural constraint
12
+ *
13
+ * Example:
14
+ * Input: T extends { id: number; name: string }
15
+ * Output:
16
+ * interface __Constraint_T {
17
+ * double id { get; }
18
+ * string name { get; }
19
+ * }
20
+ *
21
+ * sealed class __Wrapper_T : __Constraint_T {
22
+ * public double id { get; set; }
23
+ * public string name { get; set; }
24
+ * }
25
+ */
26
+ export const generateStructuralAdapter = (
27
+ typeParam: IrTypeParameter,
28
+ context: EmitterContext
29
+ ): [string, EmitterContext] => {
30
+ if (!typeParam.isStructuralConstraint || !typeParam.structuralMembers) {
31
+ return ["", context];
32
+ }
33
+
34
+ const ind = getIndent(context);
35
+ const bodyInd = getIndent(indent(context));
36
+ const parts: string[] = [];
37
+ let currentContext = context;
38
+
39
+ const interfaceName = `__Constraint_${typeParam.name}`;
40
+ const wrapperName = `__Wrapper_${typeParam.name}`;
41
+
42
+ // Generate interface
43
+ parts.push(`${ind}public interface ${interfaceName}`);
44
+ parts.push(`${ind}{`);
45
+
46
+ for (const member of typeParam.structuralMembers) {
47
+ if (member.kind === "propertySignature") {
48
+ const [memberType, newContext] = emitType(member.type, currentContext);
49
+ currentContext = newContext;
50
+
51
+ const optional = member.isOptional ? "?" : "";
52
+ parts.push(`${bodyInd}${memberType}${optional} ${member.name} { get; }`);
53
+ }
54
+ }
55
+
56
+ parts.push(`${ind}}`);
57
+ parts.push(""); // Blank line
58
+
59
+ // Generate wrapper class
60
+ parts.push(`${ind}public sealed class ${wrapperName} : ${interfaceName}`);
61
+ parts.push(`${ind}{`);
62
+
63
+ for (const member of typeParam.structuralMembers) {
64
+ if (member.kind === "propertySignature") {
65
+ const [memberType, newContext] = emitType(member.type, currentContext);
66
+ currentContext = newContext;
67
+
68
+ const optional = member.isOptional ? "?" : "";
69
+ parts.push(
70
+ `${bodyInd}public ${memberType}${optional} ${member.name} { get; set; }`
71
+ );
72
+ }
73
+ }
74
+
75
+ parts.push(`${ind}}`);
76
+
77
+ return [parts.join("\n"), currentContext];
78
+ };
79
+
80
+ /**
81
+ * Generate all structural adapters for a set of type parameters
82
+ */
83
+ export const generateStructuralAdapters = (
84
+ typeParams: readonly IrTypeParameter[] | undefined,
85
+ context: EmitterContext
86
+ ): [string, EmitterContext] => {
87
+ if (!typeParams || typeParams.length === 0) {
88
+ return ["", context];
89
+ }
90
+
91
+ const adapters: string[] = [];
92
+ let currentContext = context;
93
+
94
+ for (const tp of typeParams) {
95
+ if (tp.isStructuralConstraint && tp.structuralMembers) {
96
+ const [adapterCode, newContext] = generateStructuralAdapter(
97
+ tp,
98
+ currentContext
99
+ );
100
+ if (adapterCode) {
101
+ adapters.push(adapterCode);
102
+ currentContext = newContext;
103
+ }
104
+ }
105
+ }
106
+
107
+ if (adapters.length === 0) {
108
+ return ["", context];
109
+ }
110
+
111
+ return [adapters.join("\n\n"), currentContext];
112
+ };
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Tests for array emission
3
+ * Verifies Tsonic.JSRuntime extension methods usage and JavaScript semantics
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("Array Emission", () => {
12
+ it("should emit basic array literal with correct type", () => {
13
+ const module: IrModule = {
14
+ kind: "module",
15
+ filePath: "/test/arrays.ts",
16
+ namespace: "Test",
17
+ className: "arrays",
18
+ isStaticContainer: true,
19
+ imports: [],
20
+ exports: [],
21
+ body: [
22
+ {
23
+ kind: "variableDeclaration",
24
+ declarationKind: "const",
25
+ isExported: false,
26
+ declarations: [
27
+ {
28
+ kind: "variableDeclarator",
29
+ name: { kind: "identifierPattern", name: "numbers" },
30
+ type: {
31
+ kind: "referenceType",
32
+ name: "Array",
33
+ typeArguments: [{ kind: "primitiveType", name: "number" }],
34
+ },
35
+ initializer: {
36
+ kind: "array",
37
+ elements: [
38
+ { kind: "literal", value: 1 },
39
+ { kind: "literal", value: 2 },
40
+ { kind: "literal", value: 3 },
41
+ ],
42
+ },
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ };
48
+
49
+ const code = emitModule(module);
50
+
51
+ // Integer literals create List<int> - C# infers type from literal values
52
+ // Uses global:: FQN for unambiguous resolution
53
+ expect(code).to.include(
54
+ "new global::System.Collections.Generic.List<int> { 1, 2, 3 }"
55
+ );
56
+ // No using statements - all types use global:: FQN
57
+ expect(code).not.to.include("using System.Collections.Generic");
58
+ });
59
+
60
+ it("should emit sparse array with holes", () => {
61
+ const module: IrModule = {
62
+ kind: "module",
63
+ filePath: "/test/sparse.ts",
64
+ namespace: "Test",
65
+ className: "sparse",
66
+ isStaticContainer: true,
67
+ imports: [],
68
+ exports: [],
69
+ body: [
70
+ {
71
+ kind: "variableDeclaration",
72
+ declarationKind: "const",
73
+ isExported: false,
74
+ declarations: [
75
+ {
76
+ kind: "variableDeclarator",
77
+ name: { kind: "identifierPattern", name: "sparse" },
78
+ type: {
79
+ kind: "referenceType",
80
+ name: "Array",
81
+ typeArguments: [{ kind: "primitiveType", name: "number" }],
82
+ },
83
+ initializer: {
84
+ kind: "array",
85
+ elements: [
86
+ { kind: "literal", value: 1 },
87
+ undefined, // hole
88
+ { kind: "literal", value: 3 },
89
+ ],
90
+ },
91
+ },
92
+ ],
93
+ },
94
+ ],
95
+ };
96
+
97
+ const code = emitModule(module);
98
+
99
+ // Should handle sparse array with default - integer literals create List<int>
100
+ expect(code).to.include(
101
+ "new global::System.Collections.Generic.List<int> { 1, default, 3 }"
102
+ );
103
+ });
104
+
105
+ it("should emit array with string elements", () => {
106
+ const module: IrModule = {
107
+ kind: "module",
108
+ filePath: "/test/strings.ts",
109
+ namespace: "Test",
110
+ className: "strings",
111
+ isStaticContainer: true,
112
+ imports: [],
113
+ exports: [],
114
+ body: [
115
+ {
116
+ kind: "variableDeclaration",
117
+ declarationKind: "const",
118
+ isExported: false,
119
+ declarations: [
120
+ {
121
+ kind: "variableDeclarator",
122
+ name: { kind: "identifierPattern", name: "words" },
123
+ type: {
124
+ kind: "referenceType",
125
+ name: "Array",
126
+ typeArguments: [{ kind: "primitiveType", name: "string" }],
127
+ },
128
+ initializer: {
129
+ kind: "array",
130
+ elements: [
131
+ { kind: "literal", value: "hello" },
132
+ { kind: "literal", value: "world" },
133
+ ],
134
+ },
135
+ },
136
+ ],
137
+ },
138
+ ],
139
+ };
140
+
141
+ const code = emitModule(module);
142
+
143
+ // Should use string type parameter
144
+ expect(code).to.include(
145
+ "new global::System.Collections.Generic.List<string>"
146
+ );
147
+ expect(code).to.include('"hello", "world"');
148
+ });
149
+
150
+ it("should emit empty array", () => {
151
+ const module: IrModule = {
152
+ kind: "module",
153
+ filePath: "/test/empty.ts",
154
+ namespace: "Test",
155
+ className: "empty",
156
+ isStaticContainer: true,
157
+ imports: [],
158
+ exports: [],
159
+ body: [
160
+ {
161
+ kind: "variableDeclaration",
162
+ declarationKind: "const",
163
+ isExported: false,
164
+ declarations: [
165
+ {
166
+ kind: "variableDeclarator",
167
+ name: { kind: "identifierPattern", name: "empty" },
168
+ type: {
169
+ kind: "referenceType",
170
+ name: "Array",
171
+ typeArguments: [{ kind: "primitiveType", name: "number" }],
172
+ },
173
+ initializer: {
174
+ kind: "array",
175
+ elements: [],
176
+ },
177
+ },
178
+ ],
179
+ },
180
+ ],
181
+ };
182
+
183
+ const code = emitModule(module);
184
+
185
+ // Should create empty array
186
+ expect(code).to.include(
187
+ "new global::System.Collections.Generic.List<double>()"
188
+ );
189
+ });
190
+
191
+ it("should emit array method calls correctly", () => {
192
+ const module: IrModule = {
193
+ kind: "module",
194
+ filePath: "/test/methods.ts",
195
+ namespace: "Test",
196
+ className: "methods",
197
+ isStaticContainer: true,
198
+ imports: [],
199
+ exports: [],
200
+ body: [
201
+ {
202
+ kind: "variableDeclaration",
203
+ declarationKind: "const",
204
+ isExported: false,
205
+ declarations: [
206
+ {
207
+ kind: "variableDeclarator",
208
+ name: { kind: "identifierPattern", name: "arr" },
209
+ initializer: {
210
+ kind: "array",
211
+ elements: [
212
+ { kind: "literal", value: 1 },
213
+ { kind: "literal", value: 2 },
214
+ ],
215
+ },
216
+ },
217
+ ],
218
+ },
219
+ {
220
+ kind: "expressionStatement",
221
+ expression: {
222
+ kind: "call",
223
+ callee: {
224
+ kind: "memberAccess",
225
+ object: { kind: "identifier", name: "arr" },
226
+ property: "push",
227
+ isComputed: false,
228
+ isOptional: false,
229
+ },
230
+ arguments: [{ kind: "literal", value: 3 }],
231
+ isOptional: false,
232
+ },
233
+ },
234
+ ],
235
+ };
236
+
237
+ const code = emitModule(module);
238
+
239
+ // Should call push method on array instance - C# implicitly converts int to double
240
+ expect(code).to.include("arr.push(3)");
241
+ });
242
+
243
+ it("should handle array element access", () => {
244
+ const module: IrModule = {
245
+ kind: "module",
246
+ filePath: "/test/access.ts",
247
+ namespace: "Test",
248
+ className: "access",
249
+ isStaticContainer: true,
250
+ imports: [],
251
+ exports: [],
252
+ body: [
253
+ {
254
+ kind: "variableDeclaration",
255
+ declarationKind: "const",
256
+ isExported: false,
257
+ declarations: [
258
+ {
259
+ kind: "variableDeclarator",
260
+ name: { kind: "identifierPattern", name: "arr" },
261
+ initializer: {
262
+ kind: "array",
263
+ elements: [{ kind: "literal", value: 10 }],
264
+ },
265
+ },
266
+ ],
267
+ },
268
+ {
269
+ kind: "variableDeclaration",
270
+ declarationKind: "const",
271
+ isExported: false,
272
+ declarations: [
273
+ {
274
+ kind: "variableDeclarator",
275
+ name: { kind: "identifierPattern", name: "first" },
276
+ initializer: {
277
+ kind: "memberAccess",
278
+ object: {
279
+ kind: "identifier",
280
+ name: "arr",
281
+ inferredType: {
282
+ kind: "arrayType",
283
+ elementType: { kind: "primitiveType", name: "number" },
284
+ },
285
+ },
286
+ property: { kind: "literal", value: 0 },
287
+ isComputed: true,
288
+ isOptional: false,
289
+ },
290
+ },
291
+ ],
292
+ },
293
+ ],
294
+ };
295
+
296
+ const code = emitModule(module);
297
+
298
+ // Should use static helper for array access with global:: prefix
299
+ expect(code).to.include("global::Tsonic.Runtime.Array.get(arr, 0)");
300
+ });
301
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared constants for the Tsonic emitter
3
+ */
4
+
5
+ /**
6
+ * Generate standard file header for emitted C# files
7
+ *
8
+ * @param filePath - Source TypeScript file path
9
+ * @param options - Header generation options
10
+ * @returns Multi-line header string with trailing newline
11
+ */
12
+ export const generateFileHeader = (
13
+ filePath: string,
14
+ options: {
15
+ readonly includeTimestamp?: boolean;
16
+ readonly timestamp?: string;
17
+ } = {}
18
+ ): string => {
19
+ const lines: string[] = [];
20
+
21
+ lines.push(`// Generated from: ${filePath}`);
22
+
23
+ if (options.includeTimestamp ?? true) {
24
+ const timestamp = options.timestamp ?? new Date().toISOString();
25
+ lines.push(`// Generated at: ${timestamp}`);
26
+ }
27
+
28
+ lines.push("// WARNING: Do not modify this file manually");
29
+ lines.push("");
30
+
31
+ return lines.join("\n");
32
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Export handling
3
+ */
4
+
5
+ import { IrExport } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent } from "../types.js";
7
+ import { emitExpression } from "../expression-emitter.js";
8
+
9
+ /**
10
+ * Emit an export declaration
11
+ */
12
+ export const emitExport = (
13
+ exp: IrExport,
14
+ context: EmitterContext
15
+ ): [string | null, EmitterContext] => {
16
+ switch (exp.kind) {
17
+ case "named":
18
+ // Named exports are handled by marking declarations as public
19
+ return [null, context];
20
+
21
+ case "default": {
22
+ // Default exports need special handling
23
+ // For MVP, we'll emit a comment
24
+ const [exprFrag, newContext] = emitExpression(exp.expression, context);
25
+ const ind = getIndent(context);
26
+ return [`${ind}// Default export: ${exprFrag.text}`, newContext];
27
+ }
28
+
29
+ case "declaration":
30
+ // Export declarations are already handled in the body
31
+ return [null, context];
32
+
33
+ default:
34
+ return [null, context];
35
+ }
36
+ };