@tsonic/frontend 0.0.67 → 0.0.69

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 (200) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/generic-function-values.d.ts.map +1 -1
  3. package/dist/generic-function-values.js +4 -2
  4. package/dist/generic-function-values.js.map +1 -1
  5. package/dist/graph/extraction/imports.d.ts.map +1 -1
  6. package/dist/graph/extraction/imports.js +5 -1
  7. package/dist/graph/extraction/imports.js.map +1 -1
  8. package/dist/ir/binding/binding-factory.d.ts.map +1 -1
  9. package/dist/ir/binding/binding-factory.js +239 -26
  10. package/dist/ir/binding/binding-factory.js.map +1 -1
  11. package/dist/ir/binding/binding-types.d.ts +1 -0
  12. package/dist/ir/binding/binding-types.d.ts.map +1 -1
  13. package/dist/ir/binding-resolution.test.js +171 -0
  14. package/dist/ir/binding-resolution.test.js.map +1 -1
  15. package/dist/ir/builder/imports.d.ts.map +1 -1
  16. package/dist/ir/builder/imports.js +13 -1
  17. package/dist/ir/builder/imports.js.map +1 -1
  18. package/dist/ir/builder.test.js +1347 -4
  19. package/dist/ir/builder.test.js.map +1 -1
  20. package/dist/ir/converters/anonymous-synthesis.d.ts +3 -2
  21. package/dist/ir/converters/anonymous-synthesis.d.ts.map +1 -1
  22. package/dist/ir/converters/anonymous-synthesis.js +88 -67
  23. package/dist/ir/converters/anonymous-synthesis.js.map +1 -1
  24. package/dist/ir/converters/expressions/access/access-converter.d.ts.map +1 -1
  25. package/dist/ir/converters/expressions/access/access-converter.js +44 -36
  26. package/dist/ir/converters/expressions/access/access-converter.js.map +1 -1
  27. package/dist/ir/converters/expressions/access/binding-resolution.d.ts.map +1 -1
  28. package/dist/ir/converters/expressions/access/binding-resolution.js.map +1 -1
  29. package/dist/ir/converters/expressions/access/member-resolution.d.ts.map +1 -1
  30. package/dist/ir/converters/expressions/access/member-resolution.js +15 -3
  31. package/dist/ir/converters/expressions/access/member-resolution.js.map +1 -1
  32. package/dist/ir/converters/expressions/calls/call-converter.d.ts +2 -2
  33. package/dist/ir/converters/expressions/calls/call-converter.d.ts.map +1 -1
  34. package/dist/ir/converters/expressions/calls/call-converter.js +108 -4
  35. package/dist/ir/converters/expressions/calls/call-converter.js.map +1 -1
  36. package/dist/ir/converters/expressions/calls/new-converter.d.ts +2 -1
  37. package/dist/ir/converters/expressions/calls/new-converter.d.ts.map +1 -1
  38. package/dist/ir/converters/expressions/calls/new-converter.js +4 -1
  39. package/dist/ir/converters/expressions/calls/new-converter.js.map +1 -1
  40. package/dist/ir/converters/expressions/collections.d.ts.map +1 -1
  41. package/dist/ir/converters/expressions/collections.js +425 -121
  42. package/dist/ir/converters/expressions/collections.js.map +1 -1
  43. package/dist/ir/converters/expressions/dynamic-import.d.ts +6 -0
  44. package/dist/ir/converters/expressions/dynamic-import.d.ts.map +1 -0
  45. package/dist/ir/converters/expressions/dynamic-import.js +121 -0
  46. package/dist/ir/converters/expressions/dynamic-import.js.map +1 -0
  47. package/dist/ir/converters/expressions/functions.d.ts.map +1 -1
  48. package/dist/ir/converters/expressions/functions.js +28 -13
  49. package/dist/ir/converters/expressions/functions.js.map +1 -1
  50. package/dist/ir/converters/expressions/import-meta.d.ts +9 -0
  51. package/dist/ir/converters/expressions/import-meta.d.ts.map +1 -0
  52. package/dist/ir/converters/expressions/import-meta.js +93 -0
  53. package/dist/ir/converters/expressions/import-meta.js.map +1 -0
  54. package/dist/ir/converters/expressions/literals.d.ts +2 -1
  55. package/dist/ir/converters/expressions/literals.d.ts.map +1 -1
  56. package/dist/ir/converters/expressions/literals.js +73 -0
  57. package/dist/ir/converters/expressions/literals.js.map +1 -1
  58. package/dist/ir/converters/flow-narrowing.d.ts.map +1 -1
  59. package/dist/ir/converters/flow-narrowing.js +100 -0
  60. package/dist/ir/converters/flow-narrowing.js.map +1 -1
  61. package/dist/ir/converters/statements/control/loops.d.ts.map +1 -1
  62. package/dist/ir/converters/statements/control/loops.js +72 -8
  63. package/dist/ir/converters/statements/control/loops.js.map +1 -1
  64. package/dist/ir/converters/statements/declarations/variables.d.ts.map +1 -1
  65. package/dist/ir/converters/statements/declarations/variables.js +22 -6
  66. package/dist/ir/converters/statements/declarations/variables.js.map +1 -1
  67. package/dist/ir/expression-converter.d.ts.map +1 -1
  68. package/dist/ir/expression-converter.js +13 -4
  69. package/dist/ir/expression-converter.js.map +1 -1
  70. package/dist/ir/program-context.d.ts +27 -0
  71. package/dist/ir/program-context.d.ts.map +1 -1
  72. package/dist/ir/program-context.js +12 -0
  73. package/dist/ir/program-context.js.map +1 -1
  74. package/dist/ir/type-system/internal/handle-types.d.ts +1 -0
  75. package/dist/ir/type-system/internal/handle-types.d.ts.map +1 -1
  76. package/dist/ir/type-system/internal/type-converter/references.d.ts.map +1 -1
  77. package/dist/ir/type-system/internal/type-converter/references.js +37 -1
  78. package/dist/ir/type-system/internal/type-converter/references.js.map +1 -1
  79. package/dist/ir/type-system/type-system-call-resolution.d.ts +1 -1
  80. package/dist/ir/type-system/type-system-call-resolution.d.ts.map +1 -1
  81. package/dist/ir/type-system/type-system-call-resolution.js +158 -26
  82. package/dist/ir/type-system/type-system-call-resolution.js.map +1 -1
  83. package/dist/ir/type-system/type-system-inference.d.ts +1 -1
  84. package/dist/ir/type-system/type-system-inference.d.ts.map +1 -1
  85. package/dist/ir/type-system/type-system-inference.js +189 -31
  86. package/dist/ir/type-system/type-system-inference.js.map +1 -1
  87. package/dist/ir/type-system/type-system-state.d.ts +14 -0
  88. package/dist/ir/type-system/type-system-state.d.ts.map +1 -1
  89. package/dist/ir/type-system/type-system-state.js +1 -0
  90. package/dist/ir/type-system/type-system-state.js.map +1 -1
  91. package/dist/ir/type-system/type-system.d.ts +5 -1
  92. package/dist/ir/type-system/type-system.d.ts.map +1 -1
  93. package/dist/ir/type-system/type-system.js +5 -2
  94. package/dist/ir/type-system/type-system.js.map +1 -1
  95. package/dist/ir/types/expressions.d.ts +55 -1
  96. package/dist/ir/types/expressions.d.ts.map +1 -1
  97. package/dist/ir/types/index.d.ts +1 -1
  98. package/dist/ir/types/index.d.ts.map +1 -1
  99. package/dist/ir/types/index.js.map +1 -1
  100. package/dist/ir/types/module.d.ts +2 -0
  101. package/dist/ir/types/module.d.ts.map +1 -1
  102. package/dist/ir/types/type-ops.d.ts.map +1 -1
  103. package/dist/ir/types/type-ops.js +1 -0
  104. package/dist/ir/types/type-ops.js.map +1 -1
  105. package/dist/ir/types.d.ts +1 -1
  106. package/dist/ir/types.d.ts.map +1 -1
  107. package/dist/ir/types.js.map +1 -1
  108. package/dist/ir/validation/anonymous-type-lowering-pass.d.ts.map +1 -1
  109. package/dist/ir/validation/anonymous-type-lowering-pass.js +97 -9
  110. package/dist/ir/validation/anonymous-type-lowering-pass.js.map +1 -1
  111. package/dist/ir/validation/arrow-return-finalization-pass.js +3 -0
  112. package/dist/ir/validation/arrow-return-finalization-pass.js.map +1 -1
  113. package/dist/ir/validation/char-validation-pass.d.ts.map +1 -1
  114. package/dist/ir/validation/char-validation-pass.js +10 -0
  115. package/dist/ir/validation/char-validation-pass.js.map +1 -1
  116. package/dist/ir/validation/numeric-coercion-pass.d.ts.map +1 -1
  117. package/dist/ir/validation/numeric-coercion-pass.js +6 -0
  118. package/dist/ir/validation/numeric-coercion-pass.js.map +1 -1
  119. package/dist/ir/validation/numeric-invariants.test.js +180 -0
  120. package/dist/ir/validation/numeric-invariants.test.js.map +1 -1
  121. package/dist/ir/validation/numeric-proof-pass.d.ts.map +1 -1
  122. package/dist/ir/validation/numeric-proof-pass.js +188 -2
  123. package/dist/ir/validation/numeric-proof-pass.js.map +1 -1
  124. package/dist/ir/validation/soundness-gate.d.ts.map +1 -1
  125. package/dist/ir/validation/soundness-gate.js +29 -10
  126. package/dist/ir/validation/soundness-gate.js.map +1 -1
  127. package/dist/ir/validation/soundness-gate.test.js +127 -4
  128. package/dist/ir/validation/soundness-gate.test.js.map +1 -1
  129. package/dist/ir/validation/yield-lowering-pass.d.ts.map +1 -1
  130. package/dist/ir/validation/yield-lowering-pass.js +26 -2
  131. package/dist/ir/validation/yield-lowering-pass.js.map +1 -1
  132. package/dist/object-literal-method-runtime.d.ts +15 -0
  133. package/dist/object-literal-method-runtime.d.ts.map +1 -0
  134. package/dist/object-literal-method-runtime.js +279 -0
  135. package/dist/object-literal-method-runtime.js.map +1 -0
  136. package/dist/program/bindings.test.js +44 -0
  137. package/dist/program/bindings.test.js.map +1 -1
  138. package/dist/program/creation.d.ts.map +1 -1
  139. package/dist/program/creation.js +141 -18
  140. package/dist/program/creation.js.map +1 -1
  141. package/dist/program/creation.test.js +312 -2
  142. package/dist/program/creation.test.js.map +1 -1
  143. package/dist/program/dependency-graph.d.ts.map +1 -1
  144. package/dist/program/dependency-graph.js +49 -22
  145. package/dist/program/dependency-graph.js.map +1 -1
  146. package/dist/program/dependency-graph.test.d.ts +2 -0
  147. package/dist/program/dependency-graph.test.d.ts.map +1 -0
  148. package/dist/program/dependency-graph.test.js +118 -0
  149. package/dist/program/dependency-graph.test.js.map +1 -0
  150. package/dist/resolver/dynamic-import.d.ts +33 -0
  151. package/dist/resolver/dynamic-import.d.ts.map +1 -0
  152. package/dist/resolver/dynamic-import.js +142 -0
  153. package/dist/resolver/dynamic-import.js.map +1 -0
  154. package/dist/resolver/dynamic-import.test.d.ts +2 -0
  155. package/dist/resolver/dynamic-import.test.d.ts.map +1 -0
  156. package/dist/resolver/dynamic-import.test.js +150 -0
  157. package/dist/resolver/dynamic-import.test.js.map +1 -0
  158. package/dist/resolver/import-resolution.d.ts +2 -0
  159. package/dist/resolver/import-resolution.d.ts.map +1 -1
  160. package/dist/resolver/import-resolution.js +20 -3
  161. package/dist/resolver/import-resolution.js.map +1 -1
  162. package/dist/resolver/source-package-resolution.d.ts +11 -0
  163. package/dist/resolver/source-package-resolution.d.ts.map +1 -0
  164. package/dist/resolver/source-package-resolution.js +188 -0
  165. package/dist/resolver/source-package-resolution.js.map +1 -0
  166. package/dist/resolver/source-package-resolution.test.d.ts +2 -0
  167. package/dist/resolver/source-package-resolution.test.d.ts.map +1 -0
  168. package/dist/resolver/source-package-resolution.test.js +77 -0
  169. package/dist/resolver/source-package-resolution.test.js.map +1 -0
  170. package/dist/resolver/types.d.ts +1 -0
  171. package/dist/resolver/types.d.ts.map +1 -1
  172. package/dist/resolver.test.js +83 -0
  173. package/dist/resolver.test.js.map +1 -1
  174. package/dist/surface/profiles.d.ts +1 -0
  175. package/dist/surface/profiles.d.ts.map +1 -1
  176. package/dist/surface/profiles.js +4 -2
  177. package/dist/surface/profiles.js.map +1 -1
  178. package/dist/surface/profiles.test.js +10 -0
  179. package/dist/surface/profiles.test.js.map +1 -1
  180. package/dist/types/module.d.ts +1 -1
  181. package/dist/types/module.d.ts.map +1 -1
  182. package/dist/validation/features.d.ts +1 -1
  183. package/dist/validation/features.d.ts.map +1 -1
  184. package/dist/validation/features.js +37 -6
  185. package/dist/validation/features.js.map +1 -1
  186. package/dist/validation/features.test.js +137 -23
  187. package/dist/validation/features.test.js.map +1 -1
  188. package/dist/validation/imports.d.ts.map +1 -1
  189. package/dist/validation/imports.js +2 -8
  190. package/dist/validation/imports.js.map +1 -1
  191. package/dist/validation/imports.test.js +6 -9
  192. package/dist/validation/imports.test.js.map +1 -1
  193. package/dist/validation/static-safety.d.ts.map +1 -1
  194. package/dist/validation/static-safety.js +118 -67
  195. package/dist/validation/static-safety.js.map +1 -1
  196. package/dist/validator.maximus.test.js +225 -33
  197. package/dist/validator.maximus.test.js.map +1 -1
  198. package/dist/validator.test.js +87 -6
  199. package/dist/validator.test.js.map +1 -1
  200. package/package.json +1 -1
@@ -9,6 +9,7 @@ import * as os from "node:os";
9
9
  import * as path from "node:path";
10
10
  import { buildIrModule } from "./builder.js";
11
11
  import { createProgramContext } from "./program-context.js";
12
+ import { createProgram } from "../program/creation.js";
12
13
  import { DotnetMetadataRegistry } from "../dotnet-metadata.js";
13
14
  import { BindingRegistry } from "../program/bindings.js";
14
15
  import { createClrBindingsResolver } from "../resolver/clr-bindings-resolver.js";
@@ -90,6 +91,489 @@ describe("IR Builder", () => {
90
91
  }
91
92
  });
92
93
  });
94
+ describe("Promise callback typing", () => {
95
+ it("should not poison Promise.then callbacks to void before generic resolution settles", () => {
96
+ const source = `
97
+ declare class Promise<T> {
98
+ then<TResult1 = T, TResult2 = never>(
99
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
100
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
101
+ ): Promise<TResult1 | TResult2>;
102
+ }
103
+
104
+ interface PromiseLike<T> {
105
+ then<TResult1 = T, TResult2 = never>(
106
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
107
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
108
+ ): PromiseLike<TResult1 | TResult2>;
109
+ }
110
+
111
+ export function chainScore(seed: Promise<number>): Promise<number> {
112
+ return seed.then((value) => value + 1);
113
+ }
114
+ `;
115
+ const { testProgram, ctx, options } = createTestProgram(source);
116
+ const sourceFile = testProgram.sourceFiles[0];
117
+ if (!sourceFile)
118
+ throw new Error("Failed to create source file");
119
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
120
+ expect(result.ok).to.equal(true);
121
+ if (!result.ok)
122
+ return;
123
+ const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "chainScore");
124
+ expect(fn).to.not.equal(undefined);
125
+ if (!fn)
126
+ return;
127
+ const returnStmt = fn.body.statements[0];
128
+ expect(returnStmt?.kind).to.equal("returnStatement");
129
+ if (!returnStmt || returnStmt.kind !== "returnStatement")
130
+ return;
131
+ const call = returnStmt.expression;
132
+ expect(call?.kind).to.equal("call");
133
+ if (!call || call.kind !== "call")
134
+ return;
135
+ const callback = call.arguments[0];
136
+ expect(callback?.kind).to.equal("arrowFunction");
137
+ if (!callback || callback.kind !== "arrowFunction")
138
+ return;
139
+ expect(callback.parameters[0]?.type).to.deep.equal({
140
+ kind: "primitiveType",
141
+ name: "number",
142
+ });
143
+ expect(callback.inferredType).to.deep.equal({
144
+ kind: "functionType",
145
+ parameters: callback.parameters,
146
+ returnType: {
147
+ kind: "primitiveType",
148
+ name: "number",
149
+ },
150
+ });
151
+ });
152
+ it("infers Promise constructor generic from contextual return type", () => {
153
+ const source = `
154
+ declare function setTimeout(fn: () => void, ms: number): void;
155
+
156
+ declare class PromiseLike<T> {
157
+ then<TResult1 = T, TResult2 = never>(
158
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
159
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null
160
+ ): PromiseLike<TResult1 | TResult2>;
161
+ }
162
+
163
+ declare class Promise<T> {
164
+ constructor(
165
+ executor: (
166
+ resolve: (value: T | PromiseLike<T>) => void,
167
+ reject: (reason: unknown) => void
168
+ ) => void
169
+ );
170
+ }
171
+
172
+ export function delay(ms: number): Promise<void> {
173
+ return new Promise((resolve) => {
174
+ setTimeout(() => resolve(), ms);
175
+ });
176
+ }
177
+ `;
178
+ const { testProgram, ctx, options } = createTestProgram(source);
179
+ const sourceFile = testProgram.sourceFiles[0];
180
+ if (!sourceFile)
181
+ throw new Error("Failed to create source file");
182
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
183
+ expect(result.ok).to.equal(true);
184
+ if (!result.ok)
185
+ return;
186
+ const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "delay");
187
+ expect(fn).to.not.equal(undefined);
188
+ if (!fn)
189
+ return;
190
+ const returnStmt = fn.body.statements[0];
191
+ expect(returnStmt?.kind).to.equal("returnStatement");
192
+ if (!returnStmt || returnStmt.kind !== "returnStatement")
193
+ return;
194
+ const ctor = returnStmt.expression;
195
+ expect(ctor?.kind).to.equal("new");
196
+ if (!ctor || ctor.kind !== "new")
197
+ return;
198
+ expect(ctor.inferredType).to.deep.equal({
199
+ kind: "referenceType",
200
+ name: "Promise",
201
+ typeArguments: [{ kind: "voidType" }],
202
+ });
203
+ const executor = ctor.arguments[0];
204
+ expect(executor?.kind).to.equal("arrowFunction");
205
+ if (!executor || executor.kind !== "arrowFunction")
206
+ return;
207
+ expect(executor.parameters[0]?.type).to.not.equal(undefined);
208
+ expect(executor.parameters[0]?.type?.kind).to.equal("functionType");
209
+ });
210
+ it("infers Promise.all element type from async wrapper array arguments", () => {
211
+ const source = `
212
+ interface PromiseLike<T> {
213
+ then<TResult1 = T, TResult2 = never>(
214
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
215
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null
216
+ ): PromiseLike<TResult1 | TResult2>;
217
+ }
218
+
219
+ declare class Promise<T> {
220
+ static all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
221
+ }
222
+
223
+ async function runWorker(name: string): Promise<number> {
224
+ return 1;
225
+ }
226
+
227
+ export async function main(): Promise<void> {
228
+ const results = await Promise.all([
229
+ runWorker("a"),
230
+ runWorker("b"),
231
+ runWorker("c"),
232
+ ]);
233
+ void results;
234
+ }
235
+ `;
236
+ const { testProgram, ctx, options } = createTestProgram(source);
237
+ const sourceFile = testProgram.sourceFiles[0];
238
+ if (!sourceFile)
239
+ throw new Error("Failed to create source file");
240
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
241
+ expect(result.ok).to.equal(true);
242
+ if (!result.ok)
243
+ return;
244
+ const fn = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "main");
245
+ expect(fn).to.not.equal(undefined);
246
+ if (!fn)
247
+ return;
248
+ const decl = fn.body.statements[0];
249
+ expect(decl?.kind).to.equal("variableDeclaration");
250
+ if (!decl || decl.kind !== "variableDeclaration")
251
+ return;
252
+ const initializer = decl.declarations[0]?.initializer;
253
+ expect(initializer?.kind).to.equal("await");
254
+ if (!initializer || initializer.kind !== "await")
255
+ return;
256
+ const call = initializer.expression;
257
+ expect(call?.kind).to.equal("call");
258
+ if (!call || call.kind !== "call")
259
+ return;
260
+ expect(call.inferredType).to.deep.include({
261
+ kind: "referenceType",
262
+ name: "Promise",
263
+ typeArguments: [
264
+ {
265
+ kind: "arrayType",
266
+ elementType: {
267
+ kind: "primitiveType",
268
+ name: "number",
269
+ },
270
+ origin: "explicit",
271
+ },
272
+ ],
273
+ });
274
+ expect(initializer.inferredType).to.deep.equal({
275
+ kind: "arrayType",
276
+ elementType: {
277
+ kind: "primitiveType",
278
+ name: "number",
279
+ },
280
+ origin: "explicit",
281
+ });
282
+ });
283
+ it("preserves ambient generic receiver substitutions for js-surface method calls", () => {
284
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-map-keys-"));
285
+ try {
286
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
287
+ const srcDir = path.join(tempDir, "src");
288
+ fs.mkdirSync(srcDir, { recursive: true });
289
+ const entryPath = path.join(srcDir, "index.ts");
290
+ fs.writeFileSync(entryPath, [
291
+ "const counts = new Map<string, number>();",
292
+ 'counts.set("alpha", 1);',
293
+ "export const keys = Array.from(counts.keys());",
294
+ ].join("\n"));
295
+ const programResult = createProgram([entryPath], {
296
+ projectRoot: tempDir,
297
+ sourceRoot: srcDir,
298
+ rootNamespace: "TestApp",
299
+ surface: "@tsonic/js",
300
+ });
301
+ expect(programResult.ok).to.equal(true);
302
+ if (!programResult.ok)
303
+ return;
304
+ const program = programResult.value;
305
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
306
+ expect(sourceFile).to.not.equal(undefined);
307
+ if (!sourceFile)
308
+ return;
309
+ const ctx = createProgramContext(program, {
310
+ sourceRoot: srcDir,
311
+ rootNamespace: "TestApp",
312
+ });
313
+ const moduleResult = buildIrModule(sourceFile, program, {
314
+ sourceRoot: srcDir,
315
+ rootNamespace: "TestApp",
316
+ }, ctx);
317
+ expect(moduleResult.ok).to.equal(true);
318
+ if (!moduleResult.ok)
319
+ return;
320
+ const keysDecl = moduleResult.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
321
+ stmt.declarations[0]?.name.kind === "identifierPattern" &&
322
+ stmt.declarations[0]?.name.name === "keys");
323
+ expect(keysDecl).to.not.equal(undefined);
324
+ if (!keysDecl)
325
+ return;
326
+ const initializer = keysDecl.declarations[0]?.initializer;
327
+ expect(initializer?.kind).to.equal("call");
328
+ if (!initializer || initializer.kind !== "call")
329
+ return;
330
+ expect(initializer.inferredType).to.deep.equal({
331
+ kind: "arrayType",
332
+ elementType: { kind: "primitiveType", name: "string" },
333
+ origin: "explicit",
334
+ });
335
+ const keysCall = initializer.arguments[0];
336
+ expect(keysCall?.kind).to.equal("call");
337
+ if (!keysCall || keysCall.kind !== "call")
338
+ return;
339
+ expect(keysCall.inferredType?.kind).to.equal("referenceType");
340
+ if (keysCall.inferredType?.kind !== "referenceType")
341
+ return;
342
+ expect(keysCall.inferredType.name).to.equal("Iterable");
343
+ expect(keysCall.inferredType.typeArguments).to.deep.equal([
344
+ { kind: "primitiveType", name: "string" },
345
+ ]);
346
+ const callee = keysCall.callee;
347
+ expect(callee.kind).to.equal("memberAccess");
348
+ if (callee.kind !== "memberAccess")
349
+ return;
350
+ expect(callee.inferredType?.kind).to.equal("functionType");
351
+ if (callee.inferredType?.kind !== "functionType")
352
+ return;
353
+ expect(callee.inferredType.parameters).to.deep.equal([]);
354
+ expect(callee.inferredType.returnType.kind).to.equal("referenceType");
355
+ if (callee.inferredType.returnType.kind !== "referenceType")
356
+ return;
357
+ expect(callee.inferredType.returnType.name).to.equal("Iterable");
358
+ expect(callee.inferredType.returnType.typeArguments).to.deep.equal([
359
+ { kind: "primitiveType", name: "string" },
360
+ ]);
361
+ }
362
+ finally {
363
+ fs.rmSync(tempDir, { recursive: true, force: true });
364
+ }
365
+ });
366
+ it("preserves imported root-namespace member types across package internals", () => {
367
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-node-date-"));
368
+ try {
369
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
370
+ const srcDir = path.join(tempDir, "src");
371
+ fs.mkdirSync(srcDir, { recursive: true });
372
+ const jsRoot = path.join(tempDir, "node_modules/@tsonic/js-temp");
373
+ fs.mkdirSync(path.join(jsRoot, "index", "internal"), {
374
+ recursive: true,
375
+ });
376
+ fs.writeFileSync(path.join(jsRoot, "package.json"), JSON.stringify({ name: "@tsonic/js-temp", version: "0.0.0", type: "module" }, null, 2));
377
+ fs.writeFileSync(path.join(jsRoot, "index", "bindings.json"), JSON.stringify({ namespace: "Acme.JsRuntime", types: [] }, null, 2));
378
+ fs.writeFileSync(path.join(jsRoot, "index.js"), "export {};\n");
379
+ fs.writeFileSync(path.join(jsRoot, "index", "internal", "index.d.ts"), [
380
+ "export interface Date$instance {",
381
+ " toISOString(): string;",
382
+ "}",
383
+ "export type Date = Date$instance;",
384
+ ].join("\n"));
385
+ const nodeRoot = path.join(tempDir, "node_modules/@tsonic/node-temp");
386
+ fs.mkdirSync(path.join(nodeRoot, "index", "internal"), {
387
+ recursive: true,
388
+ });
389
+ fs.writeFileSync(path.join(nodeRoot, "package.json"), JSON.stringify({ name: "@tsonic/node-temp", version: "0.0.0", type: "module" }, null, 2));
390
+ fs.writeFileSync(path.join(nodeRoot, "index", "bindings.json"), JSON.stringify({ namespace: "acme.node", types: [] }, null, 2));
391
+ fs.writeFileSync(path.join(nodeRoot, "index.js"), "export {};\n");
392
+ fs.writeFileSync(path.join(nodeRoot, "index", "internal", "index.d.ts"), [
393
+ 'import type { Date } from "@tsonic/js-temp/Acme.JsRuntime/internal/index.js";',
394
+ "export interface Stats$instance {",
395
+ " mtime: Date;",
396
+ "}",
397
+ "export type Stats = Stats$instance;",
398
+ ].join("\n"));
399
+ fs.writeFileSync(path.join(nodeRoot, "index.d.ts"), [
400
+ 'import type { Stats } from "./index/internal/index.js";',
401
+ 'declare module "node:fs" {',
402
+ " export const statSync: (path: string) => Stats;",
403
+ "}",
404
+ "export {};",
405
+ ].join("\n"));
406
+ const entryPath = path.join(srcDir, "index.ts");
407
+ fs.writeFileSync(entryPath, [
408
+ 'import { statSync } from "node:fs";',
409
+ "const maybeDate: Date | undefined = undefined;",
410
+ 'export const resolved = maybeDate ?? statSync("package.json").mtime;',
411
+ "export const iso = resolved.toISOString();",
412
+ ].join("\n"));
413
+ const programResult = createProgram([entryPath], {
414
+ projectRoot: tempDir,
415
+ sourceRoot: srcDir,
416
+ rootNamespace: "TestApp",
417
+ useStandardLib: true,
418
+ typeRoots: [
419
+ "node_modules/@tsonic/node-temp",
420
+ "node_modules/@tsonic/js-temp",
421
+ ],
422
+ });
423
+ expect(programResult.ok).to.equal(true);
424
+ if (!programResult.ok)
425
+ return;
426
+ const program = programResult.value;
427
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
428
+ expect(sourceFile).to.not.equal(undefined);
429
+ if (!sourceFile)
430
+ return;
431
+ const ctx = createProgramContext(program, {
432
+ sourceRoot: srcDir,
433
+ rootNamespace: "TestApp",
434
+ });
435
+ const moduleResult = buildIrModule(sourceFile, program, {
436
+ sourceRoot: srcDir,
437
+ rootNamespace: "TestApp",
438
+ }, ctx);
439
+ expect(moduleResult.ok).to.equal(true);
440
+ if (!moduleResult.ok)
441
+ return;
442
+ const resolvedDecl = moduleResult.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
443
+ stmt.declarations[0]?.name.kind === "identifierPattern" &&
444
+ stmt.declarations[0]?.name.name === "resolved");
445
+ expect(resolvedDecl).to.not.equal(undefined);
446
+ if (!resolvedDecl)
447
+ return;
448
+ const resolvedType = resolvedDecl.declarations[0]?.type;
449
+ expect(resolvedType).to.not.equal(undefined);
450
+ expect(resolvedType?.kind).to.not.equal("unknownType");
451
+ if (resolvedType?.kind === "referenceType") {
452
+ expect(["Date", "Date$instance"]).to.include(resolvedType.name);
453
+ }
454
+ if (resolvedType?.kind === "unionType") {
455
+ const memberNames = resolvedType.types
456
+ .filter((type) => !!type && type.kind === "referenceType")
457
+ .map((type) => type.name);
458
+ expect(memberNames).to.include("Date");
459
+ expect(memberNames).to.include("Date$instance");
460
+ }
461
+ const isoDecl = moduleResult.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
462
+ stmt.declarations[0]?.name.kind === "identifierPattern" &&
463
+ stmt.declarations[0]?.name.name === "iso");
464
+ expect(isoDecl).to.not.equal(undefined);
465
+ if (!isoDecl)
466
+ return;
467
+ expect(isoDecl.declarations[0]?.type).to.deep.equal({
468
+ kind: "primitiveType",
469
+ name: "string",
470
+ });
471
+ }
472
+ finally {
473
+ fs.rmSync(tempDir, { recursive: true, force: true });
474
+ }
475
+ });
476
+ it("narrows typeof checks in js-surface branches to the matching primitive type", () => {
477
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-typeof-narrowing-"));
478
+ try {
479
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
480
+ const srcDir = path.join(tempDir, "src");
481
+ fs.mkdirSync(srcDir, { recursive: true });
482
+ const entryPath = path.join(srcDir, "index.ts");
483
+ fs.writeFileSync(entryPath, [
484
+ "export function main(value: unknown): void {",
485
+ ' if (typeof value === "number") {',
486
+ " console.log(value.toString());",
487
+ ' } else if (typeof value === "string") {',
488
+ " console.log(value.toUpperCase());",
489
+ " }",
490
+ "}",
491
+ ].join("\n"));
492
+ const programResult = createProgram([entryPath], {
493
+ projectRoot: tempDir,
494
+ sourceRoot: srcDir,
495
+ rootNamespace: "TestApp",
496
+ surface: "@tsonic/js",
497
+ });
498
+ expect(programResult.ok).to.equal(true);
499
+ if (!programResult.ok)
500
+ return;
501
+ const program = programResult.value;
502
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
503
+ expect(sourceFile).to.not.equal(undefined);
504
+ if (!sourceFile)
505
+ return;
506
+ const ctx = createProgramContext(program, {
507
+ sourceRoot: srcDir,
508
+ rootNamespace: "TestApp",
509
+ });
510
+ const moduleResult = buildIrModule(sourceFile, program, {
511
+ sourceRoot: srcDir,
512
+ rootNamespace: "TestApp",
513
+ }, ctx);
514
+ expect(moduleResult.ok).to.equal(true);
515
+ if (!moduleResult.ok)
516
+ return;
517
+ const mainFn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "main");
518
+ expect(mainFn).to.not.equal(undefined);
519
+ if (!mainFn)
520
+ return;
521
+ const numberIf = mainFn.body.statements.find((stmt) => stmt.kind === "ifStatement");
522
+ expect(numberIf).to.not.equal(undefined);
523
+ if (!numberIf)
524
+ return;
525
+ const numberExprStmt = numberIf.thenStatement.kind === "blockStatement"
526
+ ? numberIf.thenStatement.statements[0]
527
+ : undefined;
528
+ expect(numberExprStmt?.kind).to.equal("expressionStatement");
529
+ if (!numberExprStmt ||
530
+ numberExprStmt.kind !== "expressionStatement" ||
531
+ numberExprStmt.expression.kind !== "call") {
532
+ return;
533
+ }
534
+ const numberToStringCall = numberExprStmt.expression.arguments[0];
535
+ expect(numberToStringCall?.kind).to.equal("call");
536
+ if (!numberToStringCall || numberToStringCall.kind !== "call")
537
+ return;
538
+ expect(numberToStringCall.callee.kind).to.equal("memberAccess");
539
+ if (numberToStringCall.callee.kind !== "memberAccess")
540
+ return;
541
+ expect(numberToStringCall.callee.object.inferredType).to.deep.equal({
542
+ kind: "primitiveType",
543
+ name: "number",
544
+ });
545
+ const stringIf = numberIf.elseStatement?.kind === "ifStatement"
546
+ ? numberIf.elseStatement
547
+ : undefined;
548
+ expect(stringIf).to.not.equal(undefined);
549
+ if (!stringIf)
550
+ return;
551
+ const stringExprStmt = stringIf.thenStatement.kind === "blockStatement"
552
+ ? stringIf.thenStatement.statements[0]
553
+ : undefined;
554
+ expect(stringExprStmt?.kind).to.equal("expressionStatement");
555
+ if (!stringExprStmt ||
556
+ stringExprStmt.kind !== "expressionStatement" ||
557
+ stringExprStmt.expression.kind !== "call") {
558
+ return;
559
+ }
560
+ const stringCall = stringExprStmt.expression.arguments[0];
561
+ expect(stringCall?.kind).to.equal("call");
562
+ if (!stringCall || stringCall.kind !== "call")
563
+ return;
564
+ expect(stringCall.callee.kind).to.equal("memberAccess");
565
+ if (stringCall.callee.kind !== "memberAccess")
566
+ return;
567
+ expect(stringCall.callee.object.inferredType).to.deep.equal({
568
+ kind: "primitiveType",
569
+ name: "string",
570
+ });
571
+ }
572
+ finally {
573
+ fs.rmSync(tempDir, { recursive: true, force: true });
574
+ }
575
+ });
576
+ });
93
577
  describe("Import Extraction", () => {
94
578
  it("should extract local imports", () => {
95
579
  const source = `
@@ -484,7 +968,8 @@ describe("IR Builder", () => {
484
968
  return;
485
969
  const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration");
486
970
  expect(decl).to.not.equal(undefined);
487
- const initializer = decl?.declarations[0]?.initializer;
971
+ const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
972
+ declaration.name.name === "ops")?.initializer;
488
973
  expect(initializer?.kind).to.equal("call");
489
974
  if (!initializer || initializer.kind !== "call")
490
975
  return;
@@ -538,12 +1023,213 @@ describe("IR Builder", () => {
538
1023
  const computedAddProp = arg0.properties.find((prop) => {
539
1024
  if (prop.kind !== "property")
540
1025
  return false;
541
- if (typeof prop.key === "string")
542
- return false;
543
- return prop.value.kind === "functionExpression";
1026
+ return prop.key === "add" && prop.value.kind === "functionExpression";
544
1027
  });
545
1028
  expect(computedAddProp).to.not.equal(undefined);
546
1029
  });
1030
+ it("rewrites object-literal method arguments.length to a fixed arity literal", () => {
1031
+ const source = `
1032
+ interface Ops {
1033
+ add: (x: number, y: number) => number;
1034
+ }
1035
+ function box<T>(x: T): T { return x; }
1036
+ export function run(): number {
1037
+ const ops = box<Ops>({
1038
+ add(x: number, y: number): number {
1039
+ return arguments.length + x + y;
1040
+ },
1041
+ });
1042
+ return ops.add(1, 2);
1043
+ }
1044
+ `;
1045
+ const { testProgram, ctx, options } = createTestProgram(source);
1046
+ const sourceFile = testProgram.sourceFiles[0];
1047
+ if (!sourceFile)
1048
+ throw new Error("Failed to create source file");
1049
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1050
+ expect(result.ok).to.equal(true);
1051
+ expect(ctx.diagnostics.some((d) => d.code === "TSN7403")).to.equal(false);
1052
+ if (!result.ok)
1053
+ return;
1054
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1055
+ expect(run).to.not.equal(undefined);
1056
+ if (!run)
1057
+ return;
1058
+ const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration");
1059
+ const initializer = decl?.declarations[0]?.initializer;
1060
+ expect(initializer?.kind).to.equal("call");
1061
+ if (!initializer || initializer.kind !== "call")
1062
+ return;
1063
+ const arg0 = initializer.arguments[0];
1064
+ expect(arg0?.kind).to.equal("object");
1065
+ if (!arg0 || arg0.kind !== "object")
1066
+ return;
1067
+ const addProp = arg0.properties.find((prop) => prop.kind === "property" && prop.key === "add");
1068
+ expect(addProp).to.not.equal(undefined);
1069
+ if (!addProp || addProp.kind !== "property")
1070
+ return;
1071
+ expect(addProp.value.kind).to.equal("functionExpression");
1072
+ if (addProp.value.kind !== "functionExpression")
1073
+ return;
1074
+ const stmt = addProp.value.body?.statements[0];
1075
+ expect(stmt?.kind).to.equal("returnStatement");
1076
+ if (!stmt || stmt.kind !== "returnStatement" || !stmt.expression)
1077
+ return;
1078
+ expect(stmt.expression.kind).to.equal("binary");
1079
+ if (stmt.expression.kind !== "binary")
1080
+ return;
1081
+ expect(stmt.expression.left.kind).to.equal("binary");
1082
+ if (stmt.expression.left.kind !== "binary")
1083
+ return;
1084
+ expect(stmt.expression.left.left.kind).to.equal("literal");
1085
+ if (stmt.expression.left.left.kind !== "literal")
1086
+ return;
1087
+ expect(stmt.expression.left.left.value).to.equal(2);
1088
+ });
1089
+ it("rewrites object-literal method arguments[n] to captured parameter temps", () => {
1090
+ const source = `
1091
+ interface Ops {
1092
+ add: (x: number, y: number) => number;
1093
+ }
1094
+ function box<T>(x: T): T { return x; }
1095
+ export function run(): number {
1096
+ const ops = box<Ops>({
1097
+ add(x: number, y: number): number {
1098
+ return (arguments[0] as number) + y;
1099
+ },
1100
+ });
1101
+ return ops.add(1, 2);
1102
+ }
1103
+ `;
1104
+ const { testProgram, ctx, options } = createTestProgram(source);
1105
+ const sourceFile = testProgram.sourceFiles[0];
1106
+ if (!sourceFile)
1107
+ throw new Error("Failed to create source file");
1108
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1109
+ expect(result.ok).to.equal(true);
1110
+ expect(ctx.diagnostics.some((d) => d.code === "TSN7403")).to.equal(false);
1111
+ if (!result.ok)
1112
+ return;
1113
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1114
+ expect(run).to.not.equal(undefined);
1115
+ if (!run)
1116
+ return;
1117
+ const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration");
1118
+ const initializer = decl?.declarations[0]?.initializer;
1119
+ expect(initializer?.kind).to.equal("call");
1120
+ if (!initializer || initializer.kind !== "call")
1121
+ return;
1122
+ const arg0 = initializer.arguments[0];
1123
+ expect(arg0?.kind).to.equal("object");
1124
+ if (!arg0 || arg0.kind !== "object")
1125
+ return;
1126
+ const addProp = arg0.properties.find((prop) => prop.kind === "property" && prop.key === "add");
1127
+ expect(addProp).to.not.equal(undefined);
1128
+ if (!addProp || addProp.kind !== "property")
1129
+ return;
1130
+ expect(addProp.value.kind).to.equal("functionExpression");
1131
+ if (addProp.value.kind !== "functionExpression" || !addProp.value.body)
1132
+ return;
1133
+ const [captureDecl, returnStmt] = addProp.value.body.statements;
1134
+ expect(captureDecl?.kind).to.equal("variableDeclaration");
1135
+ if (!captureDecl || captureDecl.kind !== "variableDeclaration")
1136
+ return;
1137
+ const captureInit = captureDecl.declarations[0]?.initializer;
1138
+ expect(captureInit?.kind).to.equal("identifier");
1139
+ if (!captureInit || captureInit.kind !== "identifier")
1140
+ return;
1141
+ expect(captureInit.name).to.equal("x");
1142
+ expect(returnStmt?.kind).to.equal("returnStatement");
1143
+ if (!returnStmt ||
1144
+ returnStmt.kind !== "returnStatement" ||
1145
+ !returnStmt.expression ||
1146
+ returnStmt.expression.kind !== "binary") {
1147
+ return;
1148
+ }
1149
+ expect(returnStmt.expression.left.kind).to.equal("typeAssertion");
1150
+ if (returnStmt.expression.left.kind !== "typeAssertion")
1151
+ return;
1152
+ expect(returnStmt.expression.left.expression.kind).to.equal("identifier");
1153
+ if (returnStmt.expression.left.expression.kind !== "identifier")
1154
+ return;
1155
+ expect(returnStmt.expression.left.expression.name).to.include("__tsonic_object_method_argument_0");
1156
+ });
1157
+ it("normalizes computed const-literal property and accessor keys during synthesis", () => {
1158
+ const source = `
1159
+ export function run(): number {
1160
+ const valueKey = "value";
1161
+ const doubledKey = "doubled";
1162
+ const obj = {
1163
+ [valueKey]: 21,
1164
+ get [doubledKey](): number {
1165
+ return this.value * 2;
1166
+ },
1167
+ };
1168
+ return obj.doubled;
1169
+ }
1170
+ `;
1171
+ const { testProgram, ctx, options } = createTestProgram(source);
1172
+ const sourceFile = testProgram.sourceFiles[0];
1173
+ if (!sourceFile)
1174
+ throw new Error("Failed to create source file");
1175
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1176
+ expect(result.ok).to.equal(true);
1177
+ expect(ctx.diagnostics.some((d) => d.code === "TSN7403")).to.equal(false);
1178
+ if (!result.ok)
1179
+ return;
1180
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1181
+ expect(run).to.not.equal(undefined);
1182
+ if (!run)
1183
+ return;
1184
+ const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
1185
+ stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
1186
+ declaration.name.name === "obj" &&
1187
+ declaration.initializer !== undefined));
1188
+ const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
1189
+ declaration.name.name === "obj")?.initializer;
1190
+ expect(initializer?.kind).to.equal("object");
1191
+ if (!initializer || initializer.kind !== "object")
1192
+ return;
1193
+ const valueProp = initializer.properties.find((prop) => prop.kind === "property" && prop.key === "value");
1194
+ expect(valueProp).to.not.equal(undefined);
1195
+ const doubledAccessor = initializer.behaviorMembers?.find((member) => member.kind === "propertyDeclaration" && member.name === "doubled");
1196
+ expect(doubledAccessor).to.not.equal(undefined);
1197
+ });
1198
+ it("normalizes computed const-literal numeric keys during synthesis", () => {
1199
+ const source = `
1200
+ export function run(): number {
1201
+ const slot = 1;
1202
+ const obj = {
1203
+ [slot]: 7,
1204
+ };
1205
+ return obj["1"];
1206
+ }
1207
+ `;
1208
+ const { testProgram, ctx, options } = createTestProgram(source);
1209
+ const sourceFile = testProgram.sourceFiles[0];
1210
+ if (!sourceFile)
1211
+ throw new Error("Failed to create source file");
1212
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1213
+ expect(result.ok).to.equal(true);
1214
+ expect(ctx.diagnostics.some((d) => d.code === "TSN7403")).to.equal(false);
1215
+ if (!result.ok)
1216
+ return;
1217
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1218
+ expect(run).to.not.equal(undefined);
1219
+ if (!run)
1220
+ return;
1221
+ const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
1222
+ stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
1223
+ declaration.name.name === "obj" &&
1224
+ declaration.initializer !== undefined));
1225
+ const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
1226
+ declaration.name.name === "obj")?.initializer;
1227
+ expect(initializer?.kind).to.equal("object");
1228
+ if (!initializer || initializer.kind !== "object")
1229
+ return;
1230
+ const slotProp = initializer.properties.find((prop) => prop.kind === "property" && prop.key === "1");
1231
+ expect(slotProp).to.not.equal(undefined);
1232
+ });
547
1233
  it("threads expected return generic context into call argument typing", () => {
548
1234
  const source = `
549
1235
  type Ok<T> = { success: true; data: T };
@@ -898,6 +1584,197 @@ describe("IR Builder", () => {
898
1584
  }
899
1585
  }
900
1586
  });
1587
+ it("should lower bare import.meta to an object literal with deterministic fields", () => {
1588
+ const source = `
1589
+ declare global {
1590
+ interface ImportMeta {
1591
+ readonly url: string;
1592
+ readonly filename: string;
1593
+ readonly dirname: string;
1594
+ }
1595
+ }
1596
+ const meta = import.meta;
1597
+ `;
1598
+ const { testProgram, ctx, options } = createTestProgram(source);
1599
+ const sourceFile = testProgram.sourceFiles[0];
1600
+ if (!sourceFile)
1601
+ throw new Error("Failed to create source file");
1602
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1603
+ expect(result.ok).to.equal(true);
1604
+ if (result.ok) {
1605
+ const varDecl = result.value.body.at(-1);
1606
+ const init = varDecl.declarations[0]?.initializer;
1607
+ expect(init?.kind).to.equal("object");
1608
+ if (init?.kind === "object") {
1609
+ expect(init.properties.filter((prop) => prop.kind === "property")).to.have.length(3);
1610
+ }
1611
+ }
1612
+ });
1613
+ it("should attach deterministic namespace objects to closed-world dynamic import values", () => {
1614
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-dynamic-import-"));
1615
+ try {
1616
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
1617
+ const srcDir = path.join(tempDir, "src");
1618
+ fs.mkdirSync(srcDir, { recursive: true });
1619
+ const entryPath = path.join(srcDir, "index.ts");
1620
+ fs.writeFileSync(entryPath, [
1621
+ "export async function load() {",
1622
+ ' const module = await import("./module.js");',
1623
+ " return module.value;",
1624
+ "}",
1625
+ ].join("\n"));
1626
+ fs.writeFileSync(path.join(srcDir, "module.ts"), "export const value = 42;\n");
1627
+ const tsProgram = ts.createProgram([entryPath, path.join(srcDir, "module.ts")], {
1628
+ target: ts.ScriptTarget.ES2022,
1629
+ module: ts.ModuleKind.NodeNext,
1630
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
1631
+ strict: true,
1632
+ noEmit: true,
1633
+ skipLibCheck: true,
1634
+ });
1635
+ const checker = tsProgram.getTypeChecker();
1636
+ const sourceFile = tsProgram.getSourceFile(entryPath);
1637
+ expect(sourceFile).to.not.equal(undefined);
1638
+ if (!sourceFile)
1639
+ return;
1640
+ const program = {
1641
+ program: tsProgram,
1642
+ checker,
1643
+ options: {
1644
+ projectRoot: tempDir,
1645
+ sourceRoot: srcDir,
1646
+ rootNamespace: "TestApp",
1647
+ strict: true,
1648
+ },
1649
+ sourceFiles: [
1650
+ sourceFile,
1651
+ tsProgram.getSourceFile(path.join(srcDir, "module.ts")),
1652
+ ],
1653
+ declarationSourceFiles: [],
1654
+ metadata: new DotnetMetadataRegistry(),
1655
+ bindings: new BindingRegistry(),
1656
+ clrResolver: createClrBindingsResolver(tempDir),
1657
+ binding: createBinding(checker),
1658
+ };
1659
+ const options = { sourceRoot: srcDir, rootNamespace: "TestApp" };
1660
+ const ctx = createProgramContext(program, options);
1661
+ const moduleResult = buildIrModule(sourceFile, program, options, ctx);
1662
+ expect(moduleResult.ok).to.equal(true);
1663
+ if (!moduleResult.ok)
1664
+ return;
1665
+ const loadFn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "load");
1666
+ expect(loadFn).to.not.equal(undefined);
1667
+ if (!loadFn)
1668
+ return;
1669
+ const declStmt = loadFn.body.statements[0];
1670
+ expect(declStmt?.kind).to.equal("variableDeclaration");
1671
+ if (!declStmt || declStmt.kind !== "variableDeclaration")
1672
+ return;
1673
+ const initializer = declStmt.declarations[0]?.initializer;
1674
+ expect(initializer?.kind).to.equal("await");
1675
+ if (!initializer || initializer.kind !== "await")
1676
+ return;
1677
+ const call = initializer.expression;
1678
+ expect(call.kind).to.equal("call");
1679
+ if (call.kind !== "call")
1680
+ return;
1681
+ expect(call.dynamicImportNamespace?.kind).to.equal("object");
1682
+ if (!call.dynamicImportNamespace)
1683
+ return;
1684
+ expect(call.dynamicImportNamespace.properties).to.have.length(1);
1685
+ const property = call.dynamicImportNamespace.properties[0];
1686
+ expect(property?.kind).to.equal("property");
1687
+ if (!property || property.kind !== "property")
1688
+ return;
1689
+ expect(property.key).to.equal("value");
1690
+ expect(property.value.kind).to.equal("memberAccess");
1691
+ expect(call.inferredType?.kind).to.equal("referenceType");
1692
+ }
1693
+ finally {
1694
+ fs.rmSync(tempDir, { recursive: true, force: true });
1695
+ }
1696
+ });
1697
+ it("should represent empty closed-world dynamic import namespaces as Promise<object>", () => {
1698
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-dynamic-import-empty-"));
1699
+ try {
1700
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
1701
+ const srcDir = path.join(tempDir, "src");
1702
+ fs.mkdirSync(srcDir, { recursive: true });
1703
+ const entryPath = path.join(srcDir, "index.ts");
1704
+ fs.writeFileSync(entryPath, [
1705
+ "export async function load(): Promise<object> {",
1706
+ ' return import("./module.js");',
1707
+ "}",
1708
+ ].join("\n"));
1709
+ fs.writeFileSync(path.join(srcDir, "module.ts"), "export type Value = { readonly ok: true };\n");
1710
+ const tsProgram = ts.createProgram([entryPath, path.join(srcDir, "module.ts")], {
1711
+ target: ts.ScriptTarget.ES2022,
1712
+ module: ts.ModuleKind.NodeNext,
1713
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
1714
+ strict: true,
1715
+ noEmit: true,
1716
+ skipLibCheck: true,
1717
+ });
1718
+ const checker = tsProgram.getTypeChecker();
1719
+ const sourceFile = tsProgram.getSourceFile(entryPath);
1720
+ expect(sourceFile).to.not.equal(undefined);
1721
+ if (!sourceFile)
1722
+ return;
1723
+ const program = {
1724
+ program: tsProgram,
1725
+ checker,
1726
+ options: {
1727
+ projectRoot: tempDir,
1728
+ sourceRoot: srcDir,
1729
+ rootNamespace: "TestApp",
1730
+ strict: true,
1731
+ },
1732
+ sourceFiles: [
1733
+ sourceFile,
1734
+ tsProgram.getSourceFile(path.join(srcDir, "module.ts")),
1735
+ ],
1736
+ declarationSourceFiles: [],
1737
+ metadata: new DotnetMetadataRegistry(),
1738
+ bindings: new BindingRegistry(),
1739
+ clrResolver: createClrBindingsResolver(tempDir),
1740
+ binding: createBinding(checker),
1741
+ };
1742
+ const options = { sourceRoot: srcDir, rootNamespace: "TestApp" };
1743
+ const ctx = createProgramContext(program, options);
1744
+ const moduleResult = buildIrModule(sourceFile, program, options, ctx);
1745
+ expect(moduleResult.ok).to.equal(true);
1746
+ if (!moduleResult.ok)
1747
+ return;
1748
+ const loadFn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "load");
1749
+ expect(loadFn).to.not.equal(undefined);
1750
+ if (!loadFn)
1751
+ return;
1752
+ const returnStmt = loadFn.body.statements[0];
1753
+ expect(returnStmt?.kind).to.equal("returnStatement");
1754
+ if (!returnStmt || returnStmt.kind !== "returnStatement")
1755
+ return;
1756
+ const call = returnStmt.expression;
1757
+ expect(call?.kind).to.equal("call");
1758
+ if (!call || call.kind !== "call")
1759
+ return;
1760
+ expect(call.dynamicImportNamespace?.kind).to.equal("object");
1761
+ if (!call.dynamicImportNamespace)
1762
+ return;
1763
+ expect(call.dynamicImportNamespace.properties).to.have.length(0);
1764
+ expect(call.dynamicImportNamespace.inferredType).to.deep.equal({
1765
+ kind: "referenceType",
1766
+ name: "object",
1767
+ });
1768
+ expect(call.inferredType).to.deep.equal({
1769
+ kind: "referenceType",
1770
+ name: "Promise",
1771
+ typeArguments: [{ kind: "referenceType", name: "object" }],
1772
+ });
1773
+ }
1774
+ finally {
1775
+ fs.rmSync(tempDir, { recursive: true, force: true });
1776
+ }
1777
+ });
901
1778
  });
902
1779
  describe("Export Handling", () => {
903
1780
  it("should handle named exports", () => {
@@ -1272,6 +2149,472 @@ describe("IR Builder", () => {
1272
2149
  expect(forOfStmt.isAwait).to.equal(false);
1273
2150
  }
1274
2151
  });
2152
+ it("threads Map entry tuple element types into for-of bodies", () => {
2153
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-for-of-map-entries-"));
2154
+ try {
2155
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
2156
+ const srcDir = path.join(tempDir, "src");
2157
+ fs.mkdirSync(srcDir, { recursive: true });
2158
+ const entryPath = path.join(srcDir, "index.ts");
2159
+ fs.writeFileSync(entryPath, [
2160
+ "export function process(menuBuilders: Map<string, string[]>): void {",
2161
+ " for (const [menuName, builders] of menuBuilders) {",
2162
+ " const first = builders[0];",
2163
+ " console.log(menuName, first);",
2164
+ " }",
2165
+ "}",
2166
+ ].join("\n"));
2167
+ const programResult = createProgram([entryPath], {
2168
+ projectRoot: tempDir,
2169
+ sourceRoot: srcDir,
2170
+ rootNamespace: "TestApp",
2171
+ surface: "@tsonic/js",
2172
+ });
2173
+ expect(programResult.ok).to.equal(true);
2174
+ if (!programResult.ok)
2175
+ return;
2176
+ const program = programResult.value;
2177
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
2178
+ expect(sourceFile).to.not.equal(undefined);
2179
+ if (!sourceFile)
2180
+ return;
2181
+ const ctx = createProgramContext(program, {
2182
+ sourceRoot: srcDir,
2183
+ rootNamespace: "TestApp",
2184
+ });
2185
+ const moduleResult = buildIrModule(sourceFile, program, {
2186
+ sourceRoot: srcDir,
2187
+ rootNamespace: "TestApp",
2188
+ }, ctx);
2189
+ expect(moduleResult.ok).to.equal(true);
2190
+ if (!moduleResult.ok)
2191
+ return;
2192
+ const fn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "process");
2193
+ expect(fn).to.not.equal(undefined);
2194
+ if (!fn)
2195
+ return;
2196
+ const loop = fn.body.statements[0];
2197
+ expect(loop?.kind).to.equal("forOfStatement");
2198
+ if (!loop || loop.kind !== "forOfStatement")
2199
+ return;
2200
+ expect(loop.variable.kind).to.equal("arrayPattern");
2201
+ if (loop.variable.kind !== "arrayPattern")
2202
+ return;
2203
+ const tupleElements = loop.variable.elements;
2204
+ expect(tupleElements[0]?.pattern).to.deep.equal({
2205
+ kind: "identifierPattern",
2206
+ name: "menuName",
2207
+ });
2208
+ expect(tupleElements[1]?.pattern).to.deep.equal({
2209
+ kind: "identifierPattern",
2210
+ name: "builders",
2211
+ });
2212
+ const loopBody = loop.body;
2213
+ expect(loopBody.kind).to.equal("blockStatement");
2214
+ if (loopBody.kind !== "blockStatement")
2215
+ return;
2216
+ const firstDecl = loopBody.statements[0];
2217
+ expect(firstDecl?.kind).to.equal("variableDeclaration");
2218
+ if (!firstDecl || firstDecl.kind !== "variableDeclaration")
2219
+ return;
2220
+ const initializer = firstDecl.declarations[0]?.initializer;
2221
+ expect(initializer?.kind).to.equal("memberAccess");
2222
+ if (!initializer || initializer.kind !== "memberAccess")
2223
+ return;
2224
+ expect(initializer.object.kind).to.equal("identifier");
2225
+ if (initializer.object.kind !== "identifier")
2226
+ return;
2227
+ expect(initializer.object.inferredType).to.deep.equal({
2228
+ kind: "arrayType",
2229
+ elementType: { kind: "primitiveType", name: "string" },
2230
+ origin: "explicit",
2231
+ });
2232
+ expect(initializer.accessKind).to.equal("clrIndexer");
2233
+ expect(initializer.inferredType).to.deep.equal({
2234
+ kind: "primitiveType",
2235
+ name: "string",
2236
+ });
2237
+ }
2238
+ finally {
2239
+ fs.rmSync(tempDir, { recursive: true, force: true });
2240
+ }
2241
+ });
2242
+ it("threads Iterable<T> element types from values() into for-of bodies", () => {
2243
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-for-of-iterable-values-"));
2244
+ try {
2245
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
2246
+ const srcDir = path.join(tempDir, "src");
2247
+ fs.mkdirSync(srcDir, { recursive: true });
2248
+ const entryPath = path.join(srcDir, "index.ts");
2249
+ fs.writeFileSync(entryPath, [
2250
+ "export function process(menus: Map<string, string[]>): void {",
2251
+ " for (const entries of menus.values()) {",
2252
+ " const first = entries[0];",
2253
+ " console.log(first);",
2254
+ " }",
2255
+ "}",
2256
+ ].join("\n"));
2257
+ const programResult = createProgram([entryPath], {
2258
+ projectRoot: tempDir,
2259
+ sourceRoot: srcDir,
2260
+ rootNamespace: "TestApp",
2261
+ surface: "@tsonic/js",
2262
+ });
2263
+ expect(programResult.ok).to.equal(true);
2264
+ if (!programResult.ok)
2265
+ return;
2266
+ const program = programResult.value;
2267
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
2268
+ expect(sourceFile).to.not.equal(undefined);
2269
+ if (!sourceFile)
2270
+ return;
2271
+ const ctx = createProgramContext(program, {
2272
+ sourceRoot: srcDir,
2273
+ rootNamespace: "TestApp",
2274
+ });
2275
+ const moduleResult = buildIrModule(sourceFile, program, {
2276
+ sourceRoot: srcDir,
2277
+ rootNamespace: "TestApp",
2278
+ }, ctx);
2279
+ expect(moduleResult.ok).to.equal(true);
2280
+ if (!moduleResult.ok)
2281
+ return;
2282
+ const fn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "process");
2283
+ expect(fn).to.not.equal(undefined);
2284
+ if (!fn)
2285
+ return;
2286
+ const loop = fn.body.statements[0];
2287
+ expect(loop?.kind).to.equal("forOfStatement");
2288
+ if (!loop || loop.kind !== "forOfStatement")
2289
+ return;
2290
+ expect(loop.variable).to.deep.equal({
2291
+ kind: "identifierPattern",
2292
+ name: "entries",
2293
+ });
2294
+ const loopBody = loop.body;
2295
+ expect(loopBody.kind).to.equal("blockStatement");
2296
+ if (loopBody.kind !== "blockStatement")
2297
+ return;
2298
+ const firstDecl = loopBody.statements[0];
2299
+ expect(firstDecl?.kind).to.equal("variableDeclaration");
2300
+ if (!firstDecl || firstDecl.kind !== "variableDeclaration")
2301
+ return;
2302
+ const initializer = firstDecl.declarations[0]?.initializer;
2303
+ expect(initializer?.kind).to.equal("memberAccess");
2304
+ if (!initializer || initializer.kind !== "memberAccess")
2305
+ return;
2306
+ expect(initializer.object.kind).to.equal("identifier");
2307
+ if (initializer.object.kind !== "identifier")
2308
+ return;
2309
+ expect(initializer.object.inferredType).to.deep.equal({
2310
+ kind: "arrayType",
2311
+ elementType: { kind: "primitiveType", name: "string" },
2312
+ origin: "explicit",
2313
+ });
2314
+ expect(initializer.accessKind).to.equal("clrIndexer");
2315
+ expect(initializer.inferredType).to.deep.equal({
2316
+ kind: "primitiveType",
2317
+ name: "string",
2318
+ });
2319
+ }
2320
+ finally {
2321
+ fs.rmSync(tempDir, { recursive: true, force: true });
2322
+ }
2323
+ });
2324
+ it("threads generic surface root global bindings into identifier callees", () => {
2325
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-generic-surface-globals-"));
2326
+ try {
2327
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
2328
+ const srcDir = path.join(tempDir, "src");
2329
+ fs.mkdirSync(srcDir, { recursive: true });
2330
+ const surfaceRoot = path.join(tempDir, "node_modules/@fixture/js");
2331
+ fs.mkdirSync(surfaceRoot, { recursive: true });
2332
+ fs.writeFileSync(path.join(surfaceRoot, "package.json"), JSON.stringify({ name: "@fixture/js", version: "1.0.0", type: "module" }, null, 2));
2333
+ fs.writeFileSync(path.join(surfaceRoot, "index.js"), "export {};\n");
2334
+ fs.writeFileSync(path.join(surfaceRoot, "index.d.ts"), [
2335
+ 'import type { int } from "@tsonic/core/types.js";',
2336
+ "",
2337
+ "declare global {",
2338
+ " const console: {",
2339
+ " log(...data: unknown[]): void;",
2340
+ " };",
2341
+ " function setInterval(handler: (...args: unknown[]) => void, timeout?: int, ...args: unknown[]): int;",
2342
+ " function clearInterval(id: int): void;",
2343
+ "}",
2344
+ "",
2345
+ "export {};",
2346
+ "",
2347
+ ].join("\n"));
2348
+ fs.writeFileSync(path.join(surfaceRoot, "bindings.json"), JSON.stringify({
2349
+ bindings: {
2350
+ console: {
2351
+ kind: "global",
2352
+ assembly: "Tsonic.JSRuntime",
2353
+ type: "Tsonic.JSRuntime.console",
2354
+ },
2355
+ setInterval: {
2356
+ kind: "global",
2357
+ assembly: "Tsonic.JSRuntime",
2358
+ type: "Tsonic.JSRuntime.Timers",
2359
+ csharpName: "Timers.setInterval",
2360
+ },
2361
+ clearInterval: {
2362
+ kind: "global",
2363
+ assembly: "Tsonic.JSRuntime",
2364
+ type: "Tsonic.JSRuntime.Timers",
2365
+ csharpName: "Timers.clearInterval",
2366
+ },
2367
+ },
2368
+ }, null, 2));
2369
+ fs.writeFileSync(path.join(surfaceRoot, "tsonic.surface.json"), JSON.stringify({
2370
+ schemaVersion: 1,
2371
+ id: "@fixture/js",
2372
+ extends: [],
2373
+ requiredTypeRoots: ["."],
2374
+ useStandardLib: false,
2375
+ }, null, 2));
2376
+ const entryPath = path.join(srcDir, "index.ts");
2377
+ fs.writeFileSync(entryPath, [
2378
+ "export function main(): void {",
2379
+ " const id = setInterval(() => {}, 1000);",
2380
+ " clearInterval(id);",
2381
+ ' console.log("tick");',
2382
+ "}",
2383
+ ].join("\n"));
2384
+ const programResult = createProgram([entryPath], {
2385
+ projectRoot: tempDir,
2386
+ sourceRoot: srcDir,
2387
+ rootNamespace: "TestApp",
2388
+ surface: "@fixture/js",
2389
+ useStandardLib: false,
2390
+ });
2391
+ expect(programResult.ok).to.equal(true);
2392
+ if (!programResult.ok)
2393
+ return;
2394
+ const program = programResult.value;
2395
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
2396
+ expect(sourceFile).to.not.equal(undefined);
2397
+ if (!sourceFile)
2398
+ return;
2399
+ const ctx = createProgramContext(program, {
2400
+ sourceRoot: srcDir,
2401
+ rootNamespace: "TestApp",
2402
+ });
2403
+ const moduleResult = buildIrModule(sourceFile, program, {
2404
+ sourceRoot: srcDir,
2405
+ rootNamespace: "TestApp",
2406
+ }, ctx);
2407
+ expect(moduleResult.ok).to.equal(true);
2408
+ if (!moduleResult.ok)
2409
+ return;
2410
+ const fn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "main");
2411
+ expect(fn).to.not.equal(undefined);
2412
+ if (!fn)
2413
+ return;
2414
+ const firstStmt = fn.body.statements[0];
2415
+ expect(firstStmt?.kind).to.equal("variableDeclaration");
2416
+ if (!firstStmt || firstStmt.kind !== "variableDeclaration")
2417
+ return;
2418
+ const setIntervalCall = firstStmt.declarations[0]?.initializer;
2419
+ expect(setIntervalCall?.kind).to.equal("call");
2420
+ if (!setIntervalCall || setIntervalCall.kind !== "call")
2421
+ return;
2422
+ expect(setIntervalCall.callee.kind).to.equal("identifier");
2423
+ if (setIntervalCall.callee.kind !== "identifier")
2424
+ return;
2425
+ expect(setIntervalCall.callee.name).to.equal("setInterval");
2426
+ expect(setIntervalCall.callee.resolvedClrType).to.equal("Tsonic.JSRuntime.Timers");
2427
+ expect(setIntervalCall.callee.resolvedAssembly).to.equal("Tsonic.JSRuntime");
2428
+ expect(setIntervalCall.callee.csharpName).to.equal("Timers.setInterval");
2429
+ const clearIntervalStmt = fn.body.statements[1];
2430
+ expect(clearIntervalStmt?.kind).to.equal("expressionStatement");
2431
+ if (!clearIntervalStmt ||
2432
+ clearIntervalStmt.kind !== "expressionStatement")
2433
+ return;
2434
+ const clearIntervalCall = clearIntervalStmt.expression;
2435
+ expect(clearIntervalCall.kind).to.equal("call");
2436
+ if (clearIntervalCall.kind !== "call")
2437
+ return;
2438
+ expect(clearIntervalCall.callee.kind).to.equal("identifier");
2439
+ if (clearIntervalCall.callee.kind !== "identifier")
2440
+ return;
2441
+ expect(clearIntervalCall.callee.csharpName).to.equal("Timers.clearInterval");
2442
+ }
2443
+ finally {
2444
+ fs.rmSync(tempDir, { recursive: true, force: true });
2445
+ }
2446
+ });
2447
+ it("converts regex literals into RegExp constructor expressions on js surface", () => {
2448
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-js-regex-literal-"));
2449
+ try {
2450
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
2451
+ const srcDir = path.join(tempDir, "src");
2452
+ fs.mkdirSync(srcDir, { recursive: true });
2453
+ const entryPath = path.join(srcDir, "index.ts");
2454
+ fs.writeFileSync(entryPath, [
2455
+ "export function isUpper(text: string): boolean {",
2456
+ " return /^[A-Z]+$/i.test(text);",
2457
+ "}",
2458
+ ].join("\n"));
2459
+ const programResult = createProgram([entryPath], {
2460
+ projectRoot: tempDir,
2461
+ sourceRoot: srcDir,
2462
+ rootNamespace: "TestApp",
2463
+ surface: "@tsonic/js",
2464
+ useStandardLib: false,
2465
+ });
2466
+ expect(programResult.ok).to.equal(true);
2467
+ if (!programResult.ok)
2468
+ return;
2469
+ const program = programResult.value;
2470
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
2471
+ expect(sourceFile).to.not.equal(undefined);
2472
+ if (!sourceFile)
2473
+ return;
2474
+ const ctx = createProgramContext(program, {
2475
+ sourceRoot: srcDir,
2476
+ rootNamespace: "TestApp",
2477
+ });
2478
+ const moduleResult = buildIrModule(sourceFile, program, {
2479
+ sourceRoot: srcDir,
2480
+ rootNamespace: "TestApp",
2481
+ }, ctx);
2482
+ expect(moduleResult.ok).to.equal(true);
2483
+ if (!moduleResult.ok)
2484
+ return;
2485
+ const fn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "isUpper");
2486
+ expect(fn).to.not.equal(undefined);
2487
+ if (!fn)
2488
+ return;
2489
+ const returnStmt = fn.body.statements[0];
2490
+ expect(returnStmt?.kind).to.equal("returnStatement");
2491
+ if (!returnStmt || returnStmt.kind !== "returnStatement")
2492
+ return;
2493
+ const testCall = returnStmt.expression;
2494
+ expect(testCall?.kind).to.equal("call");
2495
+ if (!testCall || testCall.kind !== "call")
2496
+ return;
2497
+ expect(testCall.callee.kind).to.equal("memberAccess");
2498
+ if (testCall.callee.kind !== "memberAccess")
2499
+ return;
2500
+ const regexCtor = testCall.callee.object;
2501
+ expect(regexCtor.kind).to.equal("new");
2502
+ if (regexCtor.kind !== "new")
2503
+ return;
2504
+ expect(regexCtor.callee.kind).to.equal("identifier");
2505
+ if (regexCtor.callee.kind !== "identifier")
2506
+ return;
2507
+ expect(regexCtor.callee.name).to.equal("RegExp");
2508
+ expect(regexCtor.callee.resolvedClrType).to.equal("Tsonic.JSRuntime.RegExp");
2509
+ expect(regexCtor.arguments).to.deep.equal([
2510
+ {
2511
+ kind: "literal",
2512
+ value: "^[A-Z]+$",
2513
+ raw: JSON.stringify("^[A-Z]+$"),
2514
+ inferredType: { kind: "primitiveType", name: "string" },
2515
+ sourceSpan: regexCtor.arguments[0]?.sourceSpan,
2516
+ },
2517
+ {
2518
+ kind: "literal",
2519
+ value: "i",
2520
+ raw: JSON.stringify("i"),
2521
+ inferredType: { kind: "primitiveType", name: "string" },
2522
+ sourceSpan: regexCtor.arguments[1]?.sourceSpan,
2523
+ },
2524
+ ]);
2525
+ }
2526
+ finally {
2527
+ fs.rmSync(tempDir, { recursive: true, force: true });
2528
+ }
2529
+ });
2530
+ it("preserves spread-only array element types on js surface", () => {
2531
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-js-spread-array-"));
2532
+ try {
2533
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
2534
+ const srcDir = path.join(tempDir, "src");
2535
+ fs.mkdirSync(srcDir, { recursive: true });
2536
+ const entryPath = path.join(srcDir, "index.ts");
2537
+ fs.writeFileSync(entryPath, [
2538
+ "type MenuEntry = { weight: number };",
2539
+ "export const sortMenuEntries = (entries: MenuEntry[]): MenuEntry[] => {",
2540
+ " return [...entries].sort((a: MenuEntry, b: MenuEntry) => a.weight - b.weight);",
2541
+ "};",
2542
+ ].join("\n"));
2543
+ const programResult = createProgram([entryPath], {
2544
+ projectRoot: tempDir,
2545
+ sourceRoot: srcDir,
2546
+ rootNamespace: "TestApp",
2547
+ surface: "@tsonic/js",
2548
+ useStandardLib: false,
2549
+ });
2550
+ expect(programResult.ok).to.equal(true);
2551
+ if (!programResult.ok)
2552
+ return;
2553
+ const program = programResult.value;
2554
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
2555
+ expect(sourceFile).to.not.equal(undefined);
2556
+ if (!sourceFile)
2557
+ return;
2558
+ const ctx = createProgramContext(program, {
2559
+ sourceRoot: srcDir,
2560
+ rootNamespace: "TestApp",
2561
+ });
2562
+ const moduleResult = buildIrModule(sourceFile, program, {
2563
+ sourceRoot: srcDir,
2564
+ rootNamespace: "TestApp",
2565
+ }, ctx);
2566
+ expect(moduleResult.ok).to.equal(true);
2567
+ if (!moduleResult.ok)
2568
+ return;
2569
+ const sortDecl = moduleResult.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
2570
+ stmt.declarations[0]?.name.kind === "identifierPattern" &&
2571
+ stmt.declarations[0]?.name.name === "sortMenuEntries");
2572
+ expect(sortDecl).to.not.equal(undefined);
2573
+ if (!sortDecl)
2574
+ return;
2575
+ const initializer = sortDecl.declarations[0]?.initializer;
2576
+ expect(initializer?.kind).to.equal("arrowFunction");
2577
+ if (!initializer || initializer.kind !== "arrowFunction")
2578
+ return;
2579
+ expect(initializer.body.kind).to.equal("blockStatement");
2580
+ if (initializer.body.kind !== "blockStatement")
2581
+ return;
2582
+ const returnStmt = initializer.body.statements[0];
2583
+ expect(returnStmt?.kind).to.equal("returnStatement");
2584
+ if (!returnStmt || returnStmt.kind !== "returnStatement")
2585
+ return;
2586
+ const sortCall = returnStmt.expression;
2587
+ expect(sortCall?.kind).to.equal("call");
2588
+ if (!sortCall || sortCall.kind !== "call")
2589
+ return;
2590
+ expect(sortCall.callee.kind).to.equal("memberAccess");
2591
+ if (sortCall.callee.kind !== "memberAccess")
2592
+ return;
2593
+ expect(sortCall.callee.object.kind).to.equal("array");
2594
+ if (sortCall.callee.object.kind !== "array")
2595
+ return;
2596
+ const inferredType = sortCall.callee.object.inferredType;
2597
+ expect(inferredType?.kind).to.equal("arrayType");
2598
+ if (!inferredType || inferredType.kind !== "arrayType")
2599
+ return;
2600
+ expect(inferredType.elementType.kind).to.equal("referenceType");
2601
+ if (inferredType.elementType.kind !== "referenceType")
2602
+ return;
2603
+ expect(inferredType.elementType.name).to.equal("MenuEntry");
2604
+ const weightMember = inferredType.elementType.structuralMembers?.find((member) => member.kind === "propertySignature" && member.name === "weight");
2605
+ expect(weightMember).to.not.equal(undefined);
2606
+ expect(weightMember?.kind).to.equal("propertySignature");
2607
+ if (!weightMember || weightMember.kind !== "propertySignature")
2608
+ return;
2609
+ expect(weightMember.type).to.deep.equal({
2610
+ kind: "primitiveType",
2611
+ name: "number",
2612
+ });
2613
+ }
2614
+ finally {
2615
+ fs.rmSync(tempDir, { recursive: true, force: true });
2616
+ }
2617
+ });
1275
2618
  });
1276
2619
  });
1277
2620
  //# sourceMappingURL=builder.test.js.map