@tsonic/emitter 0.0.64 → 0.0.66

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 (33) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/core/semantic/imports.d.ts.map +1 -1
  3. package/dist/core/semantic/imports.js +42 -22
  4. package/dist/core/semantic/imports.js.map +1 -1
  5. package/dist/core/semantic/imports.test.js +94 -0
  6. package/dist/core/semantic/imports.test.js.map +1 -1
  7. package/dist/core/semantic/type-resolution.d.ts +10 -1
  8. package/dist/core/semantic/type-resolution.d.ts.map +1 -1
  9. package/dist/core/semantic/type-resolution.js +14 -6
  10. package/dist/core/semantic/type-resolution.js.map +1 -1
  11. package/dist/emitter-types/core.d.ts +1 -1
  12. package/dist/emitter-types/core.d.ts.map +1 -1
  13. package/dist/expressions/access.d.ts.map +1 -1
  14. package/dist/expressions/access.js +107 -22
  15. package/dist/expressions/access.js.map +1 -1
  16. package/dist/expressions/calls/call-emitter.d.ts.map +1 -1
  17. package/dist/expressions/calls/call-emitter.js +30 -126
  18. package/dist/expressions/calls/call-emitter.js.map +1 -1
  19. package/dist/expressions/index.test.js +304 -129
  20. package/dist/expressions/index.test.js.map +1 -1
  21. package/dist/json-aot-generic.test.js +2 -0
  22. package/dist/json-aot-generic.test.js.map +1 -1
  23. package/dist/statements/blocks.d.ts.map +1 -1
  24. package/dist/statements/blocks.js +44 -17
  25. package/dist/statements/blocks.js.map +1 -1
  26. package/dist/statements/index.test.js +225 -0
  27. package/dist/statements/index.test.js.map +1 -1
  28. package/dist/types/references.d.ts.map +1 -1
  29. package/dist/types/references.js +27 -5
  30. package/dist/types/references.js.map +1 -1
  31. package/dist/types/references.test.js +122 -1
  32. package/dist/types/references.test.js.map +1 -1
  33. package/package.json +2 -2
@@ -217,6 +217,7 @@ describe("Expression Emission", () => {
217
217
  isOptional: false,
218
218
  // Hierarchical member binding from manifest
219
219
  memberBinding: {
220
+ kind: "method",
220
221
  assembly: "System.Linq",
221
222
  type: "System.Linq.Enumerable",
222
223
  member: "SelectMany",
@@ -237,6 +238,54 @@ describe("Expression Emission", () => {
237
238
  // No using statements
238
239
  expect(result).not.to.include("using System.Linq");
239
240
  });
241
+ it("should emit global static calls through member binding type for surface globals", () => {
242
+ const module = {
243
+ kind: "module",
244
+ filePath: "/src/test.ts",
245
+ namespace: "MyApp",
246
+ className: "test",
247
+ isStaticContainer: true,
248
+ imports: [],
249
+ body: [
250
+ {
251
+ kind: "expressionStatement",
252
+ expression: {
253
+ kind: "call",
254
+ callee: {
255
+ kind: "memberAccess",
256
+ object: {
257
+ kind: "identifier",
258
+ name: "Array",
259
+ inferredType: {
260
+ kind: "referenceType",
261
+ name: "ArrayConstructor",
262
+ },
263
+ resolvedClrType: "Tsonic.JSRuntime.JSArray`1",
264
+ resolvedAssembly: "Tsonic.JSRuntime",
265
+ csharpName: "JSArray",
266
+ },
267
+ property: "from",
268
+ isComputed: false,
269
+ isOptional: false,
270
+ memberBinding: {
271
+ kind: "method",
272
+ assembly: "Tsonic.JSRuntime",
273
+ type: "Tsonic.JSRuntime.JSArrayStatics",
274
+ member: "from",
275
+ isExtensionMethod: false,
276
+ },
277
+ },
278
+ arguments: [{ kind: "literal", value: "abc" }],
279
+ isOptional: false,
280
+ },
281
+ },
282
+ ],
283
+ exports: [],
284
+ };
285
+ const result = emitModule(module);
286
+ expect(result).to.include('global::Tsonic.JSRuntime.JSArrayStatics.from("abc")');
287
+ expect(result).not.to.include("global::Tsonic.JSRuntime.JSArray.from");
288
+ });
240
289
  it("should escape C# keywords in hierarchical member bindings", () => {
241
290
  const module = {
242
291
  kind: "module",
@@ -263,6 +312,7 @@ describe("Expression Emission", () => {
263
312
  isComputed: false,
264
313
  isOptional: false,
265
314
  memberBinding: {
315
+ kind: "method",
266
316
  assembly: "express",
267
317
  type: "Express.Express",
268
318
  member: "static",
@@ -290,6 +340,7 @@ describe("Expression Emission", () => {
290
340
  isComputed: false,
291
341
  isOptional: false,
292
342
  memberBinding: {
343
+ kind: "property",
293
344
  assembly: "express",
294
345
  type: "Express.Request",
295
346
  member: "params",
@@ -332,6 +383,7 @@ describe("Expression Emission", () => {
332
383
  isComputed: false,
333
384
  isOptional: false,
334
385
  memberBinding: {
386
+ kind: "method",
335
387
  assembly: "Tsonic.JSRuntime",
336
388
  type: "Tsonic.JSRuntime.String",
337
389
  member: "split",
@@ -349,7 +401,7 @@ describe("Expression Emission", () => {
349
401
  expect(result).to.include('global::Tsonic.JSRuntime.String.split(path, "/")');
350
402
  expect(result).not.to.include("path.split");
351
403
  });
352
- it("should rewrite js-surface array functional calls via LINQ", () => {
404
+ it("should lower numeric wrapper extension methods through surface bindings", () => {
353
405
  const module = {
354
406
  kind: "module",
355
407
  filePath: "/src/test.ts",
@@ -366,95 +418,19 @@ describe("Expression Emission", () => {
366
418
  kind: "memberAccess",
367
419
  object: {
368
420
  kind: "identifier",
369
- name: "nums",
370
- inferredType: {
371
- kind: "arrayType",
372
- elementType: { kind: "primitiveType", name: "int" },
373
- },
421
+ name: "value",
422
+ inferredType: { kind: "primitiveType", name: "number" },
374
423
  },
375
- property: "map",
376
- isComputed: false,
377
- isOptional: false,
378
- },
379
- arguments: [{ kind: "identifier", name: "doubleIt" }],
380
- isOptional: false,
381
- inferredType: {
382
- kind: "arrayType",
383
- elementType: { kind: "primitiveType", name: "int" },
384
- },
385
- },
386
- },
387
- ],
388
- exports: [],
389
- };
390
- const result = emitModule(module, { surface: "js" });
391
- expect(result).to.include("global::System.Linq.Enumerable.Select(nums, doubleIt)");
392
- expect(result).to.include("global::System.Linq.Enumerable.ToArray(");
393
- expect(result).not.to.include("new global::Tsonic.JSRuntime.JSArray");
394
- });
395
- it("should rewrite js-surface reduce/reduceRight/join via deterministic CLR calls", () => {
396
- const numsExpr = {
397
- kind: "identifier",
398
- name: "nums",
399
- inferredType: {
400
- kind: "arrayType",
401
- elementType: { kind: "primitiveType", name: "int" },
402
- },
403
- };
404
- const module = {
405
- kind: "module",
406
- filePath: "/src/test.ts",
407
- namespace: "MyApp",
408
- className: "test",
409
- isStaticContainer: true,
410
- imports: [],
411
- body: [
412
- {
413
- kind: "expressionStatement",
414
- expression: {
415
- kind: "call",
416
- callee: {
417
- kind: "memberAccess",
418
- object: numsExpr,
419
- property: "reduce",
420
- isComputed: false,
421
- isOptional: false,
422
- },
423
- arguments: [
424
- { kind: "identifier", name: "sum" },
425
- { kind: "literal", value: 0 },
426
- ],
427
- isOptional: false,
428
- },
429
- },
430
- {
431
- kind: "expressionStatement",
432
- expression: {
433
- kind: "call",
434
- callee: {
435
- kind: "memberAccess",
436
- object: numsExpr,
437
- property: "reduceRight",
438
- isComputed: false,
439
- isOptional: false,
440
- },
441
- arguments: [
442
- { kind: "identifier", name: "sum" },
443
- { kind: "literal", value: 0 },
444
- ],
445
- isOptional: false,
446
- },
447
- },
448
- {
449
- kind: "expressionStatement",
450
- expression: {
451
- kind: "call",
452
- callee: {
453
- kind: "memberAccess",
454
- object: numsExpr,
455
- property: "join",
424
+ property: "toString",
456
425
  isComputed: false,
457
426
  isOptional: false,
427
+ memberBinding: {
428
+ kind: "method",
429
+ assembly: "Tsonic.JSRuntime",
430
+ type: "Tsonic.JSRuntime.Number",
431
+ member: "toString",
432
+ isExtensionMethod: true,
433
+ },
458
434
  },
459
435
  arguments: [],
460
436
  isOptional: false,
@@ -463,11 +439,9 @@ describe("Expression Emission", () => {
463
439
  ],
464
440
  exports: [],
465
441
  };
466
- const result = emitModule(module, { surface: "js" });
467
- expect(result).to.include("global::System.Linq.Enumerable.Aggregate(nums, 0, sum)");
468
- expect(result).to.include("global::System.Linq.Enumerable.Aggregate(global::System.Linq.Enumerable.Reverse(nums), 0, sum)");
469
- expect(result).to.include('global::System.String.Join(",", nums)');
470
- expect(result).not.to.include("new global::Tsonic.JSRuntime.JSArray");
442
+ const result = emitModule(module);
443
+ expect(result).to.include("global::Tsonic.JSRuntime.Number.toString(value)");
444
+ expect(result).not.to.include("value.toString()");
471
445
  });
472
446
  it("should emit fluent LINQ extension method calls (required for EF query precompilation)", () => {
473
447
  const module = {
@@ -493,6 +467,7 @@ describe("Expression Emission", () => {
493
467
  isComputed: false,
494
468
  isOptional: false,
495
469
  memberBinding: {
470
+ kind: "method",
496
471
  assembly: "System.Linq",
497
472
  type: "System.Linq.Queryable",
498
473
  member: "Count",
@@ -542,6 +517,7 @@ describe("Expression Emission", () => {
542
517
  isComputed: false,
543
518
  isOptional: false,
544
519
  memberBinding: {
520
+ kind: "method",
545
521
  assembly: "System.Linq",
546
522
  type: "System.Linq.Queryable",
547
523
  member: m.member,
@@ -586,6 +562,7 @@ describe("Expression Emission", () => {
586
562
  isComputed: false,
587
563
  isOptional: false,
588
564
  memberBinding: {
565
+ kind: "method",
589
566
  assembly: "System.Linq",
590
567
  type: "System.Linq.Enumerable",
591
568
  member: "ToArray",
@@ -611,6 +588,7 @@ describe("Expression Emission", () => {
611
588
  isComputed: false,
612
589
  isOptional: false,
613
590
  memberBinding: {
591
+ kind: "method",
614
592
  assembly: "System.Linq",
615
593
  type: "System.Linq.Enumerable",
616
594
  member: "ToList",
@@ -637,6 +615,7 @@ describe("Expression Emission", () => {
637
615
  isComputed: false,
638
616
  isOptional: false,
639
617
  memberBinding: {
618
+ kind: "method",
640
619
  assembly: "System.Linq",
641
620
  type: "System.Linq.Enumerable",
642
621
  member: "Where",
@@ -683,6 +662,7 @@ describe("Expression Emission", () => {
683
662
  isComputed: false,
684
663
  isOptional: false,
685
664
  memberBinding: {
665
+ kind: "method",
686
666
  assembly: "Microsoft.EntityFrameworkCore",
687
667
  type: "Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions",
688
668
  member: "AsNoTracking",
@@ -729,6 +709,7 @@ describe("Expression Emission", () => {
729
709
  isComputed: false,
730
710
  isOptional: false,
731
711
  memberBinding: {
712
+ kind: "method",
732
713
  assembly: "System.Linq",
733
714
  type: "System.Linq.Enumerable",
734
715
  member: "ToList",
@@ -742,6 +723,7 @@ describe("Expression Emission", () => {
742
723
  isComputed: false,
743
724
  isOptional: false,
744
725
  memberBinding: {
726
+ kind: "method",
745
727
  assembly: "System.Linq",
746
728
  type: "System.Linq.Enumerable",
747
729
  member: "ToArray",
@@ -1033,6 +1015,7 @@ describe("Expression Emission", () => {
1033
1015
  isComputed: false,
1034
1016
  isOptional: false,
1035
1017
  memberBinding: {
1018
+ kind: "method",
1036
1019
  assembly: "MyLib",
1037
1020
  type: "MyLib.Math",
1038
1021
  member: "Add",
@@ -1085,7 +1068,7 @@ describe("Expression Emission", () => {
1085
1068
  // Should emit regular property access
1086
1069
  expect(result).to.include("obj.property");
1087
1070
  });
1088
- it("should emit array Length for js/node surfaces when binding provides lowercase length", () => {
1071
+ it("should emit member-binding CLR name exactly (no surface rewrite)", () => {
1089
1072
  const module = {
1090
1073
  kind: "module",
1091
1074
  filePath: "/src/test.ts",
@@ -1100,33 +1083,187 @@ describe("Expression Emission", () => {
1100
1083
  kind: "memberAccess",
1101
1084
  object: {
1102
1085
  kind: "identifier",
1103
- name: "nums",
1086
+ name: "value",
1087
+ inferredType: { kind: "primitiveType", name: "string" },
1088
+ },
1089
+ property: "length",
1090
+ isComputed: false,
1091
+ isOptional: false,
1092
+ memberBinding: {
1093
+ kind: "property",
1094
+ assembly: "System.Private.CoreLib",
1095
+ type: "System.String",
1096
+ member: "Length",
1097
+ },
1098
+ },
1099
+ },
1100
+ ],
1101
+ exports: [],
1102
+ };
1103
+ const result = emitModule(module);
1104
+ expect(result).to.include("value.Length");
1105
+ expect(result).not.to.include("value.length");
1106
+ });
1107
+ it("should emit global simple-binding member access as static CLR access", () => {
1108
+ const module = {
1109
+ kind: "module",
1110
+ filePath: "/src/test.ts",
1111
+ namespace: "MyApp",
1112
+ className: "test",
1113
+ isStaticContainer: true,
1114
+ imports: [],
1115
+ body: [
1116
+ {
1117
+ kind: "expressionStatement",
1118
+ expression: {
1119
+ kind: "memberAccess",
1120
+ object: {
1121
+ kind: "identifier",
1122
+ name: "console",
1123
+ inferredType: { kind: "referenceType", name: "Console" },
1124
+ },
1125
+ property: "log",
1126
+ isComputed: false,
1127
+ isOptional: false,
1128
+ memberBinding: {
1129
+ kind: "method",
1130
+ assembly: "Tsonic.JSRuntime",
1131
+ type: "Tsonic.JSRuntime.console",
1132
+ member: "log",
1133
+ },
1134
+ },
1135
+ },
1136
+ ],
1137
+ exports: [],
1138
+ };
1139
+ const result = emitModule(module);
1140
+ expect(result).to.include("global::Tsonic.JSRuntime.console.log");
1141
+ });
1142
+ it("should keep local member access when identifier case differs from CLR type leaf", () => {
1143
+ const module = {
1144
+ kind: "module",
1145
+ filePath: "/src/test.ts",
1146
+ namespace: "MyApp",
1147
+ className: "test",
1148
+ isStaticContainer: true,
1149
+ imports: [],
1150
+ body: [
1151
+ {
1152
+ kind: "expressionStatement",
1153
+ expression: {
1154
+ kind: "memberAccess",
1155
+ object: {
1156
+ kind: "identifier",
1157
+ name: "entity",
1104
1158
  inferredType: {
1105
- kind: "arrayType",
1106
- elementType: { kind: "primitiveType", name: "int" },
1159
+ kind: "referenceType",
1160
+ name: "Entity",
1161
+ resolvedClrType: "Acme.Core.Entity",
1107
1162
  },
1108
1163
  },
1164
+ property: "Maybe",
1165
+ isComputed: false,
1166
+ isOptional: false,
1167
+ memberBinding: {
1168
+ kind: "property",
1169
+ assembly: "Acme.Core",
1170
+ type: "Acme.Core.Entity",
1171
+ member: "Maybe",
1172
+ },
1173
+ },
1174
+ },
1175
+ ],
1176
+ exports: [],
1177
+ };
1178
+ const result = emitModule(module);
1179
+ expect(result).to.include("entity.Maybe");
1180
+ expect(result).not.to.include("global::Acme.Core.Entity.Maybe");
1181
+ });
1182
+ it("should emit extension member value access as static invocation", () => {
1183
+ const module = {
1184
+ kind: "module",
1185
+ filePath: "/src/test.ts",
1186
+ namespace: "MyApp",
1187
+ className: "test",
1188
+ isStaticContainer: true,
1189
+ imports: [],
1190
+ body: [
1191
+ {
1192
+ kind: "expressionStatement",
1193
+ expression: {
1194
+ kind: "memberAccess",
1195
+ object: {
1196
+ kind: "identifier",
1197
+ name: "value",
1198
+ inferredType: { kind: "primitiveType", name: "string" },
1199
+ },
1109
1200
  property: "length",
1110
1201
  isComputed: false,
1111
1202
  isOptional: false,
1112
1203
  memberBinding: {
1204
+ kind: "method",
1113
1205
  assembly: "Tsonic.JSRuntime",
1114
- type: "Tsonic.JSRuntime.JSArray",
1206
+ type: "Tsonic.JSRuntime.String",
1115
1207
  member: "length",
1208
+ isExtensionMethod: true,
1116
1209
  },
1117
1210
  },
1118
1211
  },
1119
1212
  ],
1120
1213
  exports: [],
1121
1214
  };
1122
- const jsResult = emitModule(module, { surface: "js" });
1123
- expect(jsResult).to.include("nums.Length");
1124
- expect(jsResult).not.to.include("nums.length");
1125
- const nodeResult = emitModule(module, { surface: "nodejs" });
1126
- expect(nodeResult).to.include("nums.Length");
1127
- expect(nodeResult).not.to.include("nums.length");
1215
+ const result = emitModule(module);
1216
+ expect(result).to.include("global::Tsonic.JSRuntime.String.length(value)");
1217
+ expect(result).not.to.include("value.length");
1128
1218
  });
1129
- it("should emit array Length for js/node surfaces without member binding", () => {
1219
+ it("should emit array wrapper call for non-System.Array member bindings", () => {
1220
+ const module = {
1221
+ kind: "module",
1222
+ filePath: "/src/test.ts",
1223
+ namespace: "MyApp",
1224
+ className: "test",
1225
+ isStaticContainer: true,
1226
+ imports: [],
1227
+ body: [
1228
+ {
1229
+ kind: "expressionStatement",
1230
+ expression: {
1231
+ kind: "call",
1232
+ callee: {
1233
+ kind: "memberAccess",
1234
+ object: {
1235
+ kind: "identifier",
1236
+ name: "nums",
1237
+ inferredType: {
1238
+ kind: "arrayType",
1239
+ elementType: { kind: "primitiveType", name: "int" },
1240
+ },
1241
+ },
1242
+ property: "map",
1243
+ isComputed: false,
1244
+ isOptional: false,
1245
+ memberBinding: {
1246
+ kind: "method",
1247
+ assembly: "Tsonic.JSRuntime",
1248
+ type: "Tsonic.JSRuntime.JSArray`1",
1249
+ member: "map",
1250
+ },
1251
+ },
1252
+ arguments: [{ kind: "identifier", name: "project" }],
1253
+ isOptional: false,
1254
+ inferredType: {
1255
+ kind: "arrayType",
1256
+ elementType: { kind: "primitiveType", name: "int" },
1257
+ },
1258
+ },
1259
+ },
1260
+ ],
1261
+ exports: [],
1262
+ };
1263
+ const result = emitModule(module);
1264
+ expect(result).to.include("new global::Tsonic.JSRuntime.JSArray<int>(nums).map(project).toArray()");
1265
+ });
1266
+ it("should emit array wrapper property access for non-System.Array member bindings", () => {
1130
1267
  const module = {
1131
1268
  kind: "module",
1132
1269
  filePath: "/src/test.ts",
@@ -1150,19 +1287,21 @@ describe("Expression Emission", () => {
1150
1287
  property: "length",
1151
1288
  isComputed: false,
1152
1289
  isOptional: false,
1290
+ memberBinding: {
1291
+ kind: "property",
1292
+ assembly: "Tsonic.JSRuntime",
1293
+ type: "Tsonic.JSRuntime.JSArray`1",
1294
+ member: "length",
1295
+ },
1153
1296
  },
1154
1297
  },
1155
1298
  ],
1156
1299
  exports: [],
1157
1300
  };
1158
- const jsResult = emitModule(module, { surface: "js" });
1159
- expect(jsResult).to.include("nums.Length");
1160
- expect(jsResult).not.to.include("nums.length");
1161
- const nodeResult = emitModule(module, { surface: "nodejs" });
1162
- expect(nodeResult).to.include("nums.Length");
1163
- expect(nodeResult).not.to.include("nums.length");
1301
+ const result = emitModule(module);
1302
+ expect(result).to.include("new global::Tsonic.JSRuntime.JSArray<int>(nums).length");
1164
1303
  });
1165
- it("should emit array Length for js/node surfaces when array type is referenceType", () => {
1304
+ it("should emit array wrapper property access for nullable array receivers", () => {
1166
1305
  const module = {
1167
1306
  kind: "module",
1168
1307
  filePath: "/src/test.ts",
@@ -1177,29 +1316,36 @@ describe("Expression Emission", () => {
1177
1316
  kind: "memberAccess",
1178
1317
  object: {
1179
1318
  kind: "identifier",
1180
- name: "nums",
1319
+ name: "maybeNums",
1181
1320
  inferredType: {
1182
- kind: "referenceType",
1183
- name: "Array",
1184
- typeArguments: [{ kind: "primitiveType", name: "int" }],
1321
+ kind: "unionType",
1322
+ types: [
1323
+ {
1324
+ kind: "arrayType",
1325
+ elementType: { kind: "primitiveType", name: "int" },
1326
+ },
1327
+ { kind: "primitiveType", name: "undefined" },
1328
+ ],
1185
1329
  },
1186
1330
  },
1187
1331
  property: "length",
1188
1332
  isComputed: false,
1189
1333
  isOptional: false,
1334
+ memberBinding: {
1335
+ kind: "property",
1336
+ assembly: "Tsonic.JSRuntime",
1337
+ type: "Tsonic.JSRuntime.JSArray`1",
1338
+ member: "length",
1339
+ },
1190
1340
  },
1191
1341
  },
1192
1342
  ],
1193
1343
  exports: [],
1194
1344
  };
1195
- const jsResult = emitModule(module, { surface: "js" });
1196
- expect(jsResult).to.include("nums.Length");
1197
- expect(jsResult).not.to.include("nums.length");
1198
- const nodeResult = emitModule(module, { surface: "nodejs" });
1199
- expect(nodeResult).to.include("nums.Length");
1200
- expect(nodeResult).not.to.include("nums.length");
1345
+ const result = emitModule(module);
1346
+ expect(result).to.include("new global::Tsonic.JSRuntime.JSArray<int>(maybeNums).length");
1201
1347
  });
1202
- it("should emit array Length for js/node surfaces with member binding and ReadonlyArray referenceType", () => {
1348
+ it("should emit array wrapper property access for ReadonlyArray receivers", () => {
1203
1349
  const module = {
1204
1350
  kind: "module",
1205
1351
  filePath: "/src/test.ts",
@@ -1225,8 +1371,9 @@ describe("Expression Emission", () => {
1225
1371
  isComputed: false,
1226
1372
  isOptional: false,
1227
1373
  memberBinding: {
1374
+ kind: "property",
1228
1375
  assembly: "Tsonic.JSRuntime",
1229
- type: "Tsonic.JSRuntime.JSArray",
1376
+ type: "Tsonic.JSRuntime.JSArray`1",
1230
1377
  member: "length",
1231
1378
  },
1232
1379
  },
@@ -1234,12 +1381,37 @@ describe("Expression Emission", () => {
1234
1381
  ],
1235
1382
  exports: [],
1236
1383
  };
1237
- const jsResult = emitModule(module, { surface: "js" });
1238
- expect(jsResult).to.include("nums.Length");
1239
- expect(jsResult).not.to.include("nums.length");
1240
- const nodeResult = emitModule(module, { surface: "nodejs" });
1241
- expect(nodeResult).to.include("nums.Length");
1242
- expect(nodeResult).not.to.include("nums.length");
1384
+ const result = emitModule(module);
1385
+ expect(result).to.include("new global::Tsonic.JSRuntime.JSArray<int>(nums).length");
1386
+ });
1387
+ it("should preserve source member name when no CLR member binding exists", () => {
1388
+ const module = {
1389
+ kind: "module",
1390
+ filePath: "/src/test.ts",
1391
+ namespace: "MyApp",
1392
+ className: "test",
1393
+ isStaticContainer: true,
1394
+ imports: [],
1395
+ body: [
1396
+ {
1397
+ kind: "expressionStatement",
1398
+ expression: {
1399
+ kind: "memberAccess",
1400
+ object: {
1401
+ kind: "identifier",
1402
+ name: "value",
1403
+ inferredType: { kind: "primitiveType", name: "string" },
1404
+ },
1405
+ property: "length",
1406
+ isComputed: false,
1407
+ isOptional: false,
1408
+ },
1409
+ },
1410
+ ],
1411
+ exports: [],
1412
+ };
1413
+ const result = emitModule(module);
1414
+ expect(result).to.include("value.length");
1243
1415
  });
1244
1416
  it("should project CLR Union_n member access deterministically", () => {
1245
1417
  const unionReference = {
@@ -1323,6 +1495,7 @@ describe("Expression Emission", () => {
1323
1495
  isComputed: false,
1324
1496
  isOptional: false,
1325
1497
  memberBinding: {
1498
+ kind: "property",
1326
1499
  assembly: "MyApp",
1327
1500
  type: "MyApp.Ok",
1328
1501
  member: "success",
@@ -1342,6 +1515,7 @@ describe("Expression Emission", () => {
1342
1515
  isComputed: false,
1343
1516
  isOptional: false,
1344
1517
  memberBinding: {
1518
+ kind: "property",
1345
1519
  assembly: "MyApp",
1346
1520
  type: "MyApp.Err",
1347
1521
  member: "error",
@@ -1361,6 +1535,7 @@ describe("Expression Emission", () => {
1361
1535
  isComputed: false,
1362
1536
  isOptional: false,
1363
1537
  memberBinding: {
1538
+ kind: "property",
1364
1539
  assembly: "MyApp",
1365
1540
  type: "MyApp.Ok",
1366
1541
  member: "data",