@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,75 @@
1
+ /**
2
+ * Static container class emission
3
+ */
4
+
5
+ import { IrModule, IrStatement } from "@tsonic/frontend";
6
+ import { EmitterContext, indent, getIndent, withStatic } from "../../types.js";
7
+ import { emitStatement } from "../../statement-emitter.js";
8
+ import { emitExport } from "../exports.js";
9
+
10
+ export type StaticContainerResult = {
11
+ readonly code: string;
12
+ readonly context: EmitterContext;
13
+ };
14
+
15
+ /**
16
+ * Check if there's a namespace-level class with the same name as the module
17
+ */
18
+ export const hasMatchingClassName = (
19
+ declarations: readonly IrStatement[],
20
+ className: string
21
+ ): boolean => {
22
+ return declarations.some(
23
+ (decl) =>
24
+ (decl.kind === "classDeclaration" ||
25
+ decl.kind === "interfaceDeclaration") &&
26
+ decl.name === className
27
+ );
28
+ };
29
+
30
+ /**
31
+ * Emit static container class for module-level members
32
+ */
33
+ export const emitStaticContainer = (
34
+ module: IrModule,
35
+ members: readonly IrStatement[],
36
+ baseContext: EmitterContext,
37
+ hasInheritance: boolean
38
+ ): StaticContainerResult => {
39
+ const classContext = withStatic(indent(baseContext), true);
40
+ const bodyContext = indent(classContext);
41
+ const ind = getIndent(classContext);
42
+
43
+ const containerParts: string[] = [];
44
+ containerParts.push(`${ind}public static class ${module.className}`);
45
+ containerParts.push(`${ind}{`);
46
+
47
+ const bodyParts: string[] = [];
48
+ let bodyCurrentContext = bodyContext;
49
+
50
+ for (const stmt of members) {
51
+ const [code, newContext] = emitStatement(stmt, bodyCurrentContext);
52
+ bodyParts.push(code);
53
+ bodyCurrentContext = newContext;
54
+ }
55
+
56
+ // Handle explicit exports
57
+ for (const exp of module.exports) {
58
+ const exportCode = emitExport(exp, bodyCurrentContext);
59
+ if (exportCode[0]) {
60
+ bodyParts.push(exportCode[0]);
61
+ bodyCurrentContext = exportCode[1];
62
+ }
63
+ }
64
+
65
+ if (bodyParts.length > 0) {
66
+ containerParts.push(bodyParts.join("\n\n"));
67
+ }
68
+
69
+ containerParts.push(`${ind}}`);
70
+
71
+ return {
72
+ code: containerParts.join("\n"),
73
+ context: { ...bodyCurrentContext, hasInheritance },
74
+ };
75
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Tests for Module Generation
3
+ * Tests emission of static containers and regular classes
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("Module Generation", () => {
12
+ it("should emit a static container class", () => {
13
+ const module: IrModule = {
14
+ kind: "module",
15
+ filePath: "/src/math.ts",
16
+ namespace: "MyApp",
17
+ className: "math",
18
+ isStaticContainer: true,
19
+ imports: [],
20
+ body: [
21
+ {
22
+ kind: "variableDeclaration",
23
+ declarationKind: "const",
24
+ declarations: [
25
+ {
26
+ kind: "variableDeclarator",
27
+ name: { kind: "identifierPattern", name: "PI" },
28
+ initializer: { kind: "literal", value: 3.14159 },
29
+ },
30
+ ],
31
+ isExported: true,
32
+ },
33
+ {
34
+ kind: "functionDeclaration",
35
+ name: "add",
36
+ parameters: [
37
+ {
38
+ kind: "parameter",
39
+ pattern: { kind: "identifierPattern", name: "a" },
40
+ type: { kind: "primitiveType", name: "number" },
41
+ isOptional: false,
42
+ isRest: false,
43
+ passing: "value",
44
+ },
45
+ {
46
+ kind: "parameter",
47
+ pattern: { kind: "identifierPattern", name: "b" },
48
+ type: { kind: "primitiveType", name: "number" },
49
+ isOptional: false,
50
+ isRest: false,
51
+ passing: "value",
52
+ },
53
+ ],
54
+ returnType: { kind: "primitiveType", name: "number" },
55
+ body: {
56
+ kind: "blockStatement",
57
+ statements: [
58
+ {
59
+ kind: "returnStatement",
60
+ expression: {
61
+ kind: "binary",
62
+ operator: "+",
63
+ left: { kind: "identifier", name: "a" },
64
+ right: { kind: "identifier", name: "b" },
65
+ },
66
+ },
67
+ ],
68
+ },
69
+ isExported: true,
70
+ isAsync: false,
71
+ isGenerator: false,
72
+ },
73
+ ],
74
+ exports: [],
75
+ };
76
+
77
+ const result = emitModule(module);
78
+
79
+ expect(result).to.include("public static class math");
80
+ expect(result).to.include("var PI = 3.14159");
81
+ expect(result).to.include("public static double add(double a, double b)");
82
+ expect(result).to.include("return a + b");
83
+ expect(result).to.include("namespace MyApp");
84
+ });
85
+
86
+ it("should emit a regular class", () => {
87
+ const module: IrModule = {
88
+ kind: "module",
89
+ filePath: "/src/User.ts",
90
+ namespace: "MyApp",
91
+ className: "User",
92
+ isStaticContainer: false,
93
+ imports: [],
94
+ body: [
95
+ {
96
+ kind: "classDeclaration",
97
+ name: "User",
98
+ members: [
99
+ {
100
+ kind: "propertyDeclaration",
101
+ name: "name",
102
+ type: { kind: "primitiveType", name: "string" },
103
+ accessibility: "public",
104
+ isStatic: false,
105
+ isReadonly: false,
106
+ },
107
+ {
108
+ kind: "methodDeclaration",
109
+ name: "greet",
110
+ parameters: [],
111
+ returnType: { kind: "primitiveType", name: "string" },
112
+ body: {
113
+ kind: "blockStatement",
114
+ statements: [
115
+ {
116
+ kind: "returnStatement",
117
+ expression: {
118
+ kind: "templateLiteral",
119
+ quasis: ["Hello, I'm ", ""],
120
+ expressions: [
121
+ {
122
+ kind: "memberAccess",
123
+ object: { kind: "this" },
124
+ property: "name",
125
+ isComputed: false,
126
+ isOptional: false,
127
+ },
128
+ ],
129
+ },
130
+ },
131
+ ],
132
+ },
133
+ accessibility: "public",
134
+ isStatic: false,
135
+ isAsync: false,
136
+ isGenerator: false,
137
+ },
138
+ ],
139
+ isStruct: false,
140
+ isExported: true,
141
+ implements: [],
142
+ },
143
+ ],
144
+ exports: [],
145
+ };
146
+
147
+ const result = emitModule(module);
148
+
149
+ expect(result).to.include("public class User");
150
+ expect(result).to.include("public string name;");
151
+ expect(result).to.include("public string greet()");
152
+ expect(result).to.include('$"Hello, I\'m {this.name}"');
153
+ });
154
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Main module emission logic
3
+ * Main dispatcher - re-exports from module-emitter/ subdirectory
4
+ */
5
+
6
+ export { emitModule } from "./module-emitter/index.js";
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Module map for resolving cross-file imports
3
+ */
4
+
5
+ import { IrModule, Diagnostic } from "@tsonic/frontend";
6
+ import type {
7
+ ModuleIdentity,
8
+ ModuleMap,
9
+ ExportSource,
10
+ ExportMap,
11
+ } from "../emitter-types/core.js";
12
+
13
+ // Re-export types for backward compatibility
14
+ export type { ModuleIdentity, ModuleMap, ExportSource, ExportMap };
15
+
16
+ /**
17
+ * Normalize a file path for use as module map key
18
+ * - Convert backslashes to forward slashes
19
+ * - Remove .ts extension if present
20
+ * - Normalize . and .. segments
21
+ */
22
+ export const canonicalizeFilePath = (filePath: string): string => {
23
+ // Normalize slashes
24
+ let normalized = filePath.replace(/\\/g, "/");
25
+
26
+ // Remove .ts extension
27
+ if (normalized.endsWith(".ts")) {
28
+ normalized = normalized.slice(0, -3);
29
+ }
30
+
31
+ // Split into segments and resolve . and ..
32
+ const segments: string[] = [];
33
+ for (const segment of normalized.split("/")) {
34
+ if (segment === "" || segment === ".") {
35
+ continue; // Skip empty and current directory
36
+ } else if (segment === "..") {
37
+ segments.pop(); // Go up one directory
38
+ } else {
39
+ segments.push(segment);
40
+ }
41
+ }
42
+
43
+ return segments.join("/");
44
+ };
45
+
46
+ /**
47
+ * Result of building module map
48
+ */
49
+ export type ModuleMapResult =
50
+ | {
51
+ readonly ok: true;
52
+ readonly value: ModuleMap;
53
+ readonly exportMap: ExportMap;
54
+ }
55
+ | { readonly ok: false; readonly errors: readonly Diagnostic[] };
56
+
57
+ /**
58
+ * Build module map from IR modules.
59
+ * Returns an error if any two files in the same namespace have the same
60
+ * normalized class name (e.g., api-client.ts and apiclient.ts both map to "apiclient").
61
+ */
62
+ export const buildModuleMap = (
63
+ modules: readonly IrModule[]
64
+ ): ModuleMapResult => {
65
+ const map = new Map<string, ModuleIdentity>();
66
+ const errors: Diagnostic[] = [];
67
+
68
+ // Group modules by namespace to detect class name collisions
69
+ const byNamespace = new Map<string, IrModule[]>();
70
+ for (const module of modules) {
71
+ const existing = byNamespace.get(module.namespace) ?? [];
72
+ byNamespace.set(module.namespace, [...existing, module]);
73
+ }
74
+
75
+ // Check for collisions within each namespace
76
+ for (const [namespace, nsModules] of byNamespace) {
77
+ const byClassName = new Map<string, IrModule[]>();
78
+ for (const module of nsModules) {
79
+ const existing = byClassName.get(module.className) ?? [];
80
+ byClassName.set(module.className, [...existing, module]);
81
+ }
82
+
83
+ // Report collisions
84
+ for (const [className, colliding] of byClassName) {
85
+ if (colliding.length > 1) {
86
+ const fileNames = colliding
87
+ .map((m) => `'${m.filePath.split("/").pop()}'`)
88
+ .join(" and ");
89
+ errors.push({
90
+ code: "TSN9001",
91
+ message: `File name collision after normalization: ${fileNames} both map to class '${className}' in namespace '${namespace}'. Rename one file.`,
92
+ severity: "error",
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ if (errors.length > 0) {
99
+ return { ok: false, errors };
100
+ }
101
+
102
+ // Build the map
103
+ for (const module of modules) {
104
+ const canonicalPath = canonicalizeFilePath(module.filePath);
105
+ map.set(canonicalPath, {
106
+ namespace: module.namespace,
107
+ className: module.className,
108
+ filePath: canonicalPath,
109
+ });
110
+ }
111
+
112
+ // Build the export map
113
+ const exportMap = buildExportMap(modules);
114
+
115
+ return { ok: true, value: map, exportMap };
116
+ };
117
+
118
+ /**
119
+ * Build export map from IR modules.
120
+ * Maps (modulePath, exportName) -> actual source for re-exports.
121
+ */
122
+ const buildExportMap = (modules: readonly IrModule[]): ExportMap => {
123
+ const exportMap = new Map<string, ExportSource>();
124
+
125
+ // First pass: collect all re-exports
126
+ for (const module of modules) {
127
+ const modulePath = canonicalizeFilePath(module.filePath);
128
+
129
+ for (const exp of module.exports) {
130
+ if (exp.kind === "reexport") {
131
+ // Resolve the source module path
132
+ const sourcePath = resolveImportPath(module.filePath, exp.fromModule);
133
+ const key = `${modulePath}:${exp.name}`;
134
+ exportMap.set(key, {
135
+ sourceFile: sourcePath,
136
+ sourceName: exp.originalName,
137
+ });
138
+ }
139
+ }
140
+ }
141
+
142
+ // Second pass: resolve transitive re-exports
143
+ // Keep resolving until no changes (handles chains like A re-exports from B re-exports from C)
144
+ const resolveTransitive = (): boolean => {
145
+ let changed = false;
146
+
147
+ for (const [key, source] of exportMap) {
148
+ const transitiveKey = `${source.sourceFile}:${source.sourceName}`;
149
+ const transitiveSource = exportMap.get(transitiveKey);
150
+
151
+ if (transitiveSource) {
152
+ // This is a transitive re-export - update to point to the actual source
153
+ exportMap.set(key, transitiveSource);
154
+ changed = true;
155
+ }
156
+ }
157
+
158
+ return changed;
159
+ };
160
+
161
+ // Resolve transitive re-exports (max 10 iterations to prevent infinite loops)
162
+ for (let i = 0; i < 10 && resolveTransitive(); i++) {
163
+ // Keep resolving
164
+ }
165
+
166
+ return exportMap;
167
+ };
168
+
169
+ /**
170
+ * Resolve a relative import path to a canonical file path
171
+ */
172
+ export const resolveImportPath = (
173
+ currentFilePath: string,
174
+ importSource: string
175
+ ): string => {
176
+ // Normalize current file path
177
+ const currentCanonical = canonicalizeFilePath(currentFilePath);
178
+
179
+ // Get directory of current file
180
+ const lastSlash = currentCanonical.lastIndexOf("/");
181
+ const currentDir = lastSlash >= 0 ? currentCanonical.slice(0, lastSlash) : "";
182
+
183
+ // Normalize import source
184
+ let source = importSource.replace(/\\/g, "/");
185
+
186
+ // Remove .ts extension if present
187
+ if (source.endsWith(".ts")) {
188
+ source = source.slice(0, -3);
189
+ }
190
+
191
+ // Resolve relative path
192
+ let resolvedPath: string;
193
+ if (source.startsWith("./")) {
194
+ // Same directory or subdirectory
195
+ resolvedPath = currentDir
196
+ ? `${currentDir}/${source.slice(2)}`
197
+ : source.slice(2);
198
+ } else if (source.startsWith("../")) {
199
+ // Parent directory
200
+ const parts = currentDir.split("/");
201
+ let remaining = source;
202
+ while (remaining.startsWith("../")) {
203
+ parts.pop();
204
+ remaining = remaining.slice(3);
205
+ }
206
+ resolvedPath =
207
+ parts.length > 0 ? `${parts.join("/")}/${remaining}` : remaining;
208
+ } else if (source.startsWith("/")) {
209
+ // Absolute path (remove leading slash)
210
+ resolvedPath = source.slice(1);
211
+ } else {
212
+ // No ./ or ../, treat as same directory
213
+ resolvedPath = currentDir ? `${currentDir}/${source}` : source;
214
+ }
215
+
216
+ // Canonicalize the result
217
+ return canonicalizeFilePath(resolvedPath);
218
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Emitter options and defaults
3
+ */
4
+
5
+ import { EmitterOptions } from "../types.js";
6
+
7
+ /**
8
+ * Default emitter options
9
+ */
10
+ export const defaultOptions: EmitterOptions = {
11
+ rootNamespace: "MyApp",
12
+ includeSourceMaps: false,
13
+ indent: 4,
14
+ maxLineLength: 120,
15
+ includeTimestamp: true,
16
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Type parameter collection
3
+ */
4
+
5
+ import { IrModule, IrTypeParameter } from "@tsonic/frontend";
6
+
7
+ /**
8
+ * Collect all type parameters from declarations in a module
9
+ */
10
+ export const collectTypeParameters = (
11
+ module: IrModule
12
+ ): readonly IrTypeParameter[] => {
13
+ const typeParams: IrTypeParameter[] = [];
14
+
15
+ for (const stmt of module.body) {
16
+ if (stmt.kind === "functionDeclaration" && stmt.typeParameters) {
17
+ typeParams.push(...stmt.typeParameters);
18
+ } else if (stmt.kind === "classDeclaration" && stmt.typeParameters) {
19
+ typeParams.push(...stmt.typeParameters);
20
+ // Also collect from class members
21
+ for (const member of stmt.members) {
22
+ if (member.kind === "methodDeclaration" && member.typeParameters) {
23
+ typeParams.push(...member.typeParameters);
24
+ }
25
+ }
26
+ } else if (stmt.kind === "interfaceDeclaration" && stmt.typeParameters) {
27
+ typeParams.push(...stmt.typeParameters);
28
+ } else if (stmt.kind === "typeAliasDeclaration" && stmt.typeParameters) {
29
+ typeParams.push(...stmt.typeParameters);
30
+ }
31
+ }
32
+
33
+ return typeParams;
34
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Context creation and manipulation functions
3
+ */
4
+
5
+ import { EmitterOptions, EmitterContext } from "./core.js";
6
+ import {
7
+ loadLibraries,
8
+ buildBindingsRegistry,
9
+ } from "@tsonic/frontend/metadata/index.js";
10
+
11
+ /**
12
+ * Create a new emitter context with default values
13
+ */
14
+ export const createContext = (options: EmitterOptions): EmitterContext => {
15
+ // Load metadata and bindings from library directories
16
+ let metadata: EmitterContext["metadata"] = undefined;
17
+ let bindingsRegistry: EmitterContext["bindingsRegistry"] = undefined;
18
+
19
+ if (options.libraries && options.libraries.length > 0) {
20
+ const librariesResult = loadLibraries(options.libraries);
21
+ if (librariesResult.ok) {
22
+ metadata = librariesResult.value.metadata;
23
+ bindingsRegistry = buildBindingsRegistry(librariesResult.value.bindings);
24
+ } else {
25
+ // TODO: Report diagnostics from librariesResult.error
26
+ // Need to integrate diagnostic reporting infrastructure
27
+ console.warn(
28
+ "[Tsonic] Failed to load libraries:",
29
+ librariesResult.error.map((d) => d.message).join(", ")
30
+ );
31
+ }
32
+ }
33
+
34
+ return {
35
+ indentLevel: 0,
36
+ options,
37
+ isStatic: false,
38
+ isAsync: false,
39
+ metadata,
40
+ bindingsRegistry,
41
+ };
42
+ };
43
+
44
+ /**
45
+ * Increase indentation level
46
+ */
47
+ export const indent = (context: EmitterContext): EmitterContext => ({
48
+ ...context,
49
+ indentLevel: context.indentLevel + 1,
50
+ });
51
+
52
+ /**
53
+ * Decrease indentation level
54
+ */
55
+ export const dedent = (context: EmitterContext): EmitterContext => ({
56
+ ...context,
57
+ indentLevel: Math.max(0, context.indentLevel - 1),
58
+ });
59
+
60
+ /**
61
+ * Set static context flag
62
+ */
63
+ export const withStatic = (
64
+ context: EmitterContext,
65
+ isStatic: boolean
66
+ ): EmitterContext => ({
67
+ ...context,
68
+ isStatic,
69
+ });
70
+
71
+ /**
72
+ * Set async context flag
73
+ */
74
+ export const withAsync = (
75
+ context: EmitterContext,
76
+ isAsync: boolean
77
+ ): EmitterContext => ({
78
+ ...context,
79
+ isAsync,
80
+ });
81
+
82
+ /**
83
+ * Set current class name in context
84
+ */
85
+ export const withClassName = (
86
+ context: EmitterContext,
87
+ className: string
88
+ ): EmitterContext => ({
89
+ ...context,
90
+ className,
91
+ });