semantifold 0.3.0 → 0.5.0

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 (303) hide show
  1. package/README.md +61 -15
  2. package/build/index.d.ts +52 -2
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +60 -8
  5. package/build/src/artifact-path.d.ts +23 -1
  6. package/build/src/artifact-path.d.ts.map +1 -1
  7. package/build/src/artifact-path.js +45 -3
  8. package/build/src/artifacts.d.ts.map +1 -1
  9. package/build/src/artifacts.js +66 -1
  10. package/build/src/backends/android.d.ts +49 -0
  11. package/build/src/backends/android.d.ts.map +1 -0
  12. package/build/src/backends/android.js +1274 -0
  13. package/build/src/backends/c-validation.d.ts.map +1 -1
  14. package/build/src/backends/c-validation.js +7 -3
  15. package/build/src/backends/cpp-validation.d.ts.map +1 -1
  16. package/build/src/backends/cpp-validation.js +7 -3
  17. package/build/src/backends/csharp.js +7 -1
  18. package/build/src/backends/dart-runtime.d.ts +2 -0
  19. package/build/src/backends/dart-runtime.d.ts.map +1 -0
  20. package/build/src/backends/dart-runtime.js +41 -0
  21. package/build/src/backends/dart.d.ts +20 -0
  22. package/build/src/backends/dart.d.ts.map +1 -0
  23. package/build/src/backends/dart.js +321 -0
  24. package/build/src/backends/effects.d.ts +60 -0
  25. package/build/src/backends/effects.d.ts.map +1 -0
  26. package/build/src/backends/effects.js +746 -0
  27. package/build/src/backends/go.js +18 -4
  28. package/build/src/backends/identifiers.d.ts +36 -4
  29. package/build/src/backends/identifiers.d.ts.map +1 -1
  30. package/build/src/backends/identifiers.js +160 -10
  31. package/build/src/backends/index.d.ts.map +1 -1
  32. package/build/src/backends/index.js +27 -4
  33. package/build/src/backends/ios.d.ts +68 -0
  34. package/build/src/backends/ios.d.ts.map +1 -0
  35. package/build/src/backends/ios.js +1503 -0
  36. package/build/src/backends/java.d.ts.map +1 -1
  37. package/build/src/backends/java.js +429 -36
  38. package/build/src/backends/javascript.d.ts +6 -0
  39. package/build/src/backends/javascript.d.ts.map +1 -1
  40. package/build/src/backends/javascript.js +380 -24
  41. package/build/src/backends/kotlin-runtime.d.ts +3 -0
  42. package/build/src/backends/kotlin-runtime.d.ts.map +1 -0
  43. package/build/src/backends/kotlin-runtime.js +29 -0
  44. package/build/src/backends/kotlin.d.ts +33 -0
  45. package/build/src/backends/kotlin.d.ts.map +1 -0
  46. package/build/src/backends/kotlin.js +138 -0
  47. package/build/src/backends/native-validation.d.ts +2 -2
  48. package/build/src/backends/native-validation.d.ts.map +1 -1
  49. package/build/src/backends/native-validation.js +2 -2
  50. package/build/src/backends/ordered-expressions.d.ts.map +1 -1
  51. package/build/src/backends/ordered-expressions.js +39 -7
  52. package/build/src/backends/php-stdlib-support.d.ts +19 -0
  53. package/build/src/backends/php-stdlib-support.d.ts.map +1 -0
  54. package/build/src/backends/php-stdlib-support.js +227 -0
  55. package/build/src/backends/php.d.ts.map +1 -1
  56. package/build/src/backends/php.js +448 -33
  57. package/build/src/backends/program.d.ts +83 -0
  58. package/build/src/backends/program.d.ts.map +1 -0
  59. package/build/src/backends/program.js +1309 -0
  60. package/build/src/backends/python.js +7 -1
  61. package/build/src/backends/ruby.d.ts.map +1 -1
  62. package/build/src/backends/ruby.js +310 -24
  63. package/build/src/backends/rust-runtime.d.ts +7 -0
  64. package/build/src/backends/rust-runtime.d.ts.map +1 -0
  65. package/build/src/backends/rust-runtime.js +32 -0
  66. package/build/src/backends/rust-validation.d.ts +9 -0
  67. package/build/src/backends/rust-validation.d.ts.map +1 -0
  68. package/build/src/backends/rust-validation.js +78 -0
  69. package/build/src/backends/rust.d.ts +24 -0
  70. package/build/src/backends/rust.d.ts.map +1 -0
  71. package/build/src/backends/rust.js +344 -0
  72. package/build/src/backends/scalars.d.ts +14 -6
  73. package/build/src/backends/scalars.d.ts.map +1 -1
  74. package/build/src/backends/scalars.js +167 -17
  75. package/build/src/backends/shared.d.ts +23 -4
  76. package/build/src/backends/shared.d.ts.map +1 -1
  77. package/build/src/backends/shared.js +2174 -62
  78. package/build/src/backends/stdlib-support.d.ts +14 -0
  79. package/build/src/backends/stdlib-support.d.ts.map +1 -0
  80. package/build/src/backends/stdlib-support.js +237 -0
  81. package/build/src/backends/swift-runtime.d.ts +3 -0
  82. package/build/src/backends/swift-runtime.d.ts.map +1 -0
  83. package/build/src/backends/swift-runtime.js +14 -0
  84. package/build/src/backends/swift.d.ts +8 -0
  85. package/build/src/backends/swift.d.ts.map +1 -0
  86. package/build/src/backends/swift.js +127 -0
  87. package/build/src/backends/typescript.d.ts.map +1 -1
  88. package/build/src/backends/typescript.js +344 -24
  89. package/build/src/backends/wasm.d.ts +43 -0
  90. package/build/src/backends/wasm.d.ts.map +1 -0
  91. package/build/src/backends/wasm.js +2164 -0
  92. package/build/src/backends/writer.d.ts +255 -4
  93. package/build/src/backends/writer.d.ts.map +1 -1
  94. package/build/src/backends/writer.js +491 -3
  95. package/build/src/diagnostic.d.ts +4 -3
  96. package/build/src/diagnostic.d.ts.map +1 -1
  97. package/build/src/diagnostic.js +5 -3
  98. package/build/src/frontends/c.d.ts.map +1 -1
  99. package/build/src/frontends/c.js +5 -3
  100. package/build/src/frontends/cpp.d.ts.map +1 -1
  101. package/build/src/frontends/cpp.js +5 -3
  102. package/build/src/frontends/csharp.d.ts.map +1 -1
  103. package/build/src/frontends/csharp.js +7 -3
  104. package/build/src/frontends/dart.d.ts +10 -0
  105. package/build/src/frontends/dart.d.ts.map +1 -0
  106. package/build/src/frontends/dart.js +913 -0
  107. package/build/src/frontends/index.d.ts +3 -1
  108. package/build/src/frontends/index.d.ts.map +1 -1
  109. package/build/src/frontends/index.js +15 -5
  110. package/build/src/frontends/java.d.ts +110 -1
  111. package/build/src/frontends/java.d.ts.map +1 -1
  112. package/build/src/frontends/java.js +1725 -70
  113. package/build/src/frontends/javascript-typescript.d.ts +69 -1
  114. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  115. package/build/src/frontends/javascript-typescript.js +1825 -93
  116. package/build/src/frontends/kotlin.d.ts +10 -0
  117. package/build/src/frontends/kotlin.d.ts.map +1 -0
  118. package/build/src/frontends/kotlin.js +685 -0
  119. package/build/src/frontends/php.d.ts +118 -1
  120. package/build/src/frontends/php.d.ts.map +1 -1
  121. package/build/src/frontends/php.js +1583 -76
  122. package/build/src/frontends/program.d.ts +74 -0
  123. package/build/src/frontends/program.d.ts.map +1 -0
  124. package/build/src/frontends/program.js +989 -0
  125. package/build/src/frontends/ruby.d.ts +68 -1
  126. package/build/src/frontends/ruby.d.ts.map +1 -1
  127. package/build/src/frontends/ruby.js +1446 -101
  128. package/build/src/frontends/rust-parser.d.ts +11 -0
  129. package/build/src/frontends/rust-parser.d.ts.map +1 -0
  130. package/build/src/frontends/rust-parser.js +15 -0
  131. package/build/src/frontends/rust.d.ts +14 -0
  132. package/build/src/frontends/rust.d.ts.map +1 -0
  133. package/build/src/frontends/rust.js +715 -0
  134. package/build/src/frontends/scalars.d.ts +10 -0
  135. package/build/src/frontends/scalars.d.ts.map +1 -1
  136. package/build/src/frontends/scalars.js +28 -2
  137. package/build/src/frontends/swift.d.ts +10 -0
  138. package/build/src/frontends/swift.d.ts.map +1 -0
  139. package/build/src/frontends/swift.js +860 -0
  140. package/build/src/frontends/types.d.ts +186 -0
  141. package/build/src/frontends/types.d.ts.map +1 -0
  142. package/build/src/frontends/types.js +634 -0
  143. package/build/src/language-registry.d.ts +46 -4
  144. package/build/src/language-registry.d.ts.map +1 -1
  145. package/build/src/language-registry.js +177 -13
  146. package/build/src/mapping.d.ts +2 -2
  147. package/build/src/mapping.d.ts.map +1 -1
  148. package/build/src/mapping.js +31 -10
  149. package/build/src/semantic/capabilities.d.ts +66 -0
  150. package/build/src/semantic/capabilities.d.ts.map +1 -0
  151. package/build/src/semantic/capabilities.js +649 -0
  152. package/build/src/semantic/effects.d.ts +26 -0
  153. package/build/src/semantic/effects.d.ts.map +1 -0
  154. package/build/src/semantic/effects.js +764 -0
  155. package/build/src/semantic/generics.d.ts +23 -0
  156. package/build/src/semantic/generics.d.ts.map +1 -0
  157. package/build/src/semantic/generics.js +61 -0
  158. package/build/src/semantic/lifetime.d.ts +37 -0
  159. package/build/src/semantic/lifetime.d.ts.map +1 -0
  160. package/build/src/semantic/lifetime.js +527 -0
  161. package/build/src/semantic/provenance.d.ts +9 -2
  162. package/build/src/semantic/provenance.d.ts.map +1 -1
  163. package/build/src/semantic/provenance.js +462 -20
  164. package/build/src/semantic/scalars.d.ts +11 -0
  165. package/build/src/semantic/scalars.d.ts.map +1 -1
  166. package/build/src/semantic/scalars.js +16 -1
  167. package/build/src/semantic/stdlib.d.ts +254 -0
  168. package/build/src/semantic/stdlib.d.ts.map +1 -0
  169. package/build/src/semantic/stdlib.js +880 -0
  170. package/build/src/semantic/types.d.ts +3108 -178
  171. package/build/src/semantic/types.d.ts.map +1 -1
  172. package/build/src/semantic/types.js +730 -15
  173. package/build/src/semantic/validate.d.ts +85 -5
  174. package/build/src/semantic/validate.d.ts.map +1 -1
  175. package/build/src/semantic/validate.js +2576 -98
  176. package/build/src/stdlib-facades.d.ts +304 -0
  177. package/build/src/stdlib-facades.d.ts.map +1 -0
  178. package/build/src/stdlib-facades.js +1167 -0
  179. package/build/src/stdlib-negotiation.d.ts +69 -0
  180. package/build/src/stdlib-negotiation.d.ts.map +1 -0
  181. package/build/src/stdlib-negotiation.js +403 -0
  182. package/build/src/stdlib-providers.d.ts +72 -0
  183. package/build/src/stdlib-providers.d.ts.map +1 -0
  184. package/build/src/stdlib-providers.js +339 -0
  185. package/build/src/toolchains.d.ts +55 -1
  186. package/build/src/toolchains.d.ts.map +1 -1
  187. package/build/src/toolchains.js +15 -4
  188. package/docs/android.md +79 -0
  189. package/docs/architecture.md +47 -13
  190. package/docs/dart.md +60 -0
  191. package/docs/goals.md +3 -3
  192. package/docs/ios.md +121 -0
  193. package/docs/kotlin.md +42 -0
  194. package/docs/language-support.md +238 -26
  195. package/docs/multi-file-modules.md +52 -0
  196. package/docs/parser-qualification.md +121 -8
  197. package/docs/plans/2026-09-05-standard-library-portability.md +10 -10
  198. package/docs/rust.md +88 -0
  199. package/docs/source-maps.md +17 -7
  200. package/docs/specification.md +5 -0
  201. package/docs/standard-library-portability.md +39 -8
  202. package/docs/swift.md +44 -0
  203. package/docs/testing.md +381 -4
  204. package/docs/webassembly.md +52 -0
  205. package/index.js +66 -8
  206. package/node_modules/semantifold-tree-sitter-legacy-internal/README.md +2 -2
  207. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/LICENSE +21 -0
  208. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/README.md +45 -0
  209. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/binding.gyp +30 -0
  210. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding.cc +20 -0
  211. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding_test.js +9 -0
  212. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.d.ts +28 -0
  213. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.js +11 -0
  214. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/grammar.js +1630 -0
  215. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/package.json +64 -0
  216. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-arm64/tree-sitter-rust.node +0 -0
  217. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-x64/tree-sitter-rust.node +0 -0
  218. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-arm64/tree-sitter-rust.node +0 -0
  219. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-x64/tree-sitter-rust.node +0 -0
  220. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-arm64/tree-sitter-rust.node +0 -0
  221. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-x64/tree-sitter-rust.node +0 -0
  222. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/highlights.scm +159 -0
  223. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/injections.scm +9 -0
  224. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/tags.scm +60 -0
  225. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/grammar.json +9405 -0
  226. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/node-types.json +5399 -0
  227. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/parser.c +192569 -0
  228. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/scanner.c +393 -0
  229. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/alloc.h +54 -0
  230. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/array.h +290 -0
  231. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/parser.h +266 -0
  232. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter-rust.wasm +0 -0
  233. package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter.json +53 -0
  234. package/node_modules/semantifold-tree-sitter-legacy-internal/package.json +3 -2
  235. package/node_modules/semantifold-tree-sitter-legacy-internal/src/c.js +5 -4
  236. package/package.json +5 -2
  237. package/src/artifact-path.js +47 -2
  238. package/src/artifacts.js +67 -0
  239. package/src/backends/android.js +1370 -0
  240. package/src/backends/c-validation.js +6 -1
  241. package/src/backends/cpp-validation.js +6 -1
  242. package/src/backends/csharp.js +3 -0
  243. package/src/backends/dart-runtime.js +41 -0
  244. package/src/backends/dart.js +326 -0
  245. package/src/backends/effects.js +705 -0
  246. package/src/backends/go.js +14 -2
  247. package/src/backends/identifiers.js +166 -9
  248. package/src/backends/index.js +27 -2
  249. package/src/backends/ios.js +1612 -0
  250. package/src/backends/java.js +454 -36
  251. package/src/backends/javascript.js +389 -23
  252. package/src/backends/kotlin-runtime.js +29 -0
  253. package/src/backends/kotlin.js +144 -0
  254. package/src/backends/native-validation.js +1 -1
  255. package/src/backends/ordered-expressions.js +40 -6
  256. package/src/backends/php-stdlib-support.js +239 -0
  257. package/src/backends/php.js +469 -32
  258. package/src/backends/program.js +1408 -0
  259. package/src/backends/python.js +3 -0
  260. package/src/backends/ruby.js +326 -23
  261. package/src/backends/rust-runtime.js +34 -0
  262. package/src/backends/rust-validation.js +75 -0
  263. package/src/backends/rust.js +335 -0
  264. package/src/backends/scalars.js +141 -16
  265. package/src/backends/shared.js +2314 -115
  266. package/src/backends/stdlib-support.js +234 -0
  267. package/src/backends/swift-runtime.js +14 -0
  268. package/src/backends/swift.js +132 -0
  269. package/src/backends/typescript.js +349 -23
  270. package/src/backends/wasm.js +2208 -0
  271. package/src/backends/writer.js +545 -2
  272. package/src/diagnostic.js +4 -2
  273. package/src/frontends/c.js +4 -2
  274. package/src/frontends/cpp.js +4 -2
  275. package/src/frontends/csharp.js +4 -2
  276. package/src/frontends/dart.js +963 -0
  277. package/src/frontends/index.js +14 -4
  278. package/src/frontends/java.js +1977 -69
  279. package/src/frontends/javascript-typescript.js +2046 -102
  280. package/src/frontends/kotlin.js +713 -0
  281. package/src/frontends/php.js +1725 -79
  282. package/src/frontends/program.js +1062 -0
  283. package/src/frontends/ruby.js +1624 -98
  284. package/src/frontends/rust-parser.js +18 -0
  285. package/src/frontends/rust.js +714 -0
  286. package/src/frontends/scalars.js +31 -1
  287. package/src/frontends/swift.js +893 -0
  288. package/src/frontends/types.js +750 -0
  289. package/src/language-registry.js +182 -12
  290. package/src/mapping.js +33 -9
  291. package/src/semantic/capabilities.js +671 -0
  292. package/src/semantic/effects.js +737 -0
  293. package/src/semantic/generics.js +58 -0
  294. package/src/semantic/lifetime.js +517 -0
  295. package/src/semantic/provenance.js +412 -19
  296. package/src/semantic/scalars.js +17 -0
  297. package/src/semantic/stdlib.js +921 -0
  298. package/src/semantic/types.js +810 -14
  299. package/src/semantic/validate.js +2934 -209
  300. package/src/stdlib-facades.js +1211 -0
  301. package/src/stdlib-negotiation.js +446 -0
  302. package/src/stdlib-providers.js +377 -0
  303. package/src/toolchains.js +16 -3
@@ -2,18 +2,28 @@
2
2
 
3
3
  import {parse as parseBabel} from "@babel/parser"
4
4
  import {parse as parseComment} from "comment-parser"
5
- import {missingType, parseFailure, unsupportedSyntax} from "../diagnostic.js"
5
+ import {missingType, parseFailure, SemantifoldDiagnostic, unsupportedSyntax} from "../diagnostic.js"
6
+ import {blockReferencesProtectedEntry} from "../semantic/effects.js"
6
7
  import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
7
8
  import {withAdaptedOperation} from "../semantic/operators.js"
8
9
  import {withParserRanges} from "../semantic/provenance.js"
9
10
  import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
10
- import {requireSourceScalarType} from "./scalars.js"
11
+ import {isTask034ResourceProbe} from "../semantic/capabilities.js"
12
+ import {hasExactEffectSupport} from "../backends/effects.js"
13
+ import {requireSourceReturnType, requireSourceScalarType} from "./scalars.js"
14
+ import {documentedValueType, instantiatedRecordFieldType, iterationBindingType, iterationOperandType, knownCallReturnType, listType, mapType, optionalType, orderedMapTypeFromMap, ownedReferenceType, ownedResourceType, preservesGenericOptionalEvidence, recordType, referenceType, typeVariable} from "./types.js"
11
15
 
12
16
  /** @typedef {NonNullable<import("@babel/parser").ParseResult<import("@babel/types").File>["tokens"]>[number]} BabelToken */
13
17
  /** @typedef {{byStart: Map<number, BabelToken>, tokens: BabelToken[]}} BabelTokenIndex */
18
+ /** @typedef {{name: string, nameLocation: import("../semantic/types.js").SourceLocation, parameters: import("../semantic/types.js").Parameter[], returnType: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: import("../semantic/types.js").TypeParameter[], location: import("../semantic/types.js").SourceLocation}} JavaScriptFunctionSignature */
19
+ /** @typedef {{bindings: Map<string, import("../semantic/types.js").SemanticBindingType>, classes: Map<string, import("../semantic/types.js").ClassDeclaration>, currentClass?: import("../semantic/types.js").ClassDeclaration, errors: Map<string, import("../semantic/types.js").ErrorDeclaration>, errorNames: Map<string, import("../semantic/types.js").ErrorDeclaration>, functions: Map<string, JavaScriptFunctionSignature>, lexicalValueNames?: Set<string>, orderedMapDeclarations?: Set<object>, recordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, returnType?: import("../semantic/types.js").SemanticFunctionReturnType, typeParameters?: Map<string, import("../semantic/types.js").TypeParameter>, valueRecordNames: Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration>}} JavaScriptConversionContext */
14
20
 
15
21
  /** @type {WeakMap<object, BabelTokenIndex>} */
16
22
  const nodeTokens = new WeakMap()
23
+ const referenceMethodHooks = new Set([
24
+ "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor",
25
+ "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf"
26
+ ])
17
27
  const babelBinaryOperations = new Map([
18
28
  ["+", "Add"],
19
29
  ["-", "Subtract"],
@@ -118,11 +128,30 @@ function commentTagLocation(comment, tag, field, filename, source) {
118
128
 
119
129
  const orderedFields = ["start", "delimiter", "postDelimiter", "tag", "postTag", "type", "postType", "name"]
120
130
  const fieldIndex = orderedFields.indexOf(field)
121
- const start = lineStart + orderedFields.slice(0, fieldIndex).reduce((length, key) =>
131
+ let start = lineStart + orderedFields.slice(0, fieldIndex).reduce((length, key) =>
122
132
  length + line.tokens[/** @type {keyof typeof line.tokens} */ (key)].length, 0)
123
133
  const token = line.tokens[field]
134
+ let length = token.length
135
+
136
+ if (field == "type" && tag.type) {
137
+ const nestedOffset = token.indexOf(tag.type)
138
+
139
+ if (nestedOffset < 0) throw new Error(`Comment parser omitted the @${tag.tag} type spelling.`)
140
+ start += nestedOffset
141
+ length = tag.type.length
142
+ }
124
143
 
125
- return locationFromOffsets(filename, source, start, start + token.length)
144
+ return locationFromOffsets(filename, source, start, start + length)
145
+ }
146
+
147
+ /**
148
+ * Reports whether `Map` resolves to the native intrinsic in the current lexical scope.
149
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
150
+ * @returns {boolean} Whether native Map construction is unshadowed.
151
+ */
152
+ function nativeMapAvailable(context) {
153
+ return !context.bindings.has("Map") && !context.lexicalValueNames?.has("Map") &&
154
+ !context.functions.has("Map") && !context.valueRecordNames.has("Map") && !context.errorNames.has("Map")
126
155
  }
127
156
 
128
157
  /**
@@ -131,15 +160,61 @@ function commentTagLocation(comment, tag, field, filename, source) {
131
160
  * @param {"javascript" | "typescript"} language - Frontend language.
132
161
  * @param {string} filename - Source filename.
133
162
  * @param {string} source - Complete source.
163
+ * @param {JavaScriptConversionContext} [context] - Typed lexical conversion context.
164
+ * @param {import("../semantic/types.js").SemanticValueType} [expectedType] - Explicit contextual value type.
165
+ * @param {boolean} [preserveOptional] - Whether an optional identifier remains wrapped.
134
166
  * @returns {import("../semantic/types.js").Expression} Semantic expression.
135
167
  */
136
- function convertExpression(node, language, filename, source) {
168
+ function convertExpression(node, language, filename, source, context = {bindings: new Map(), classes: new Map(), errors: new Map(), errorNames: new Map(), functions: new Map(), recordNames: new Map(), records: new Map(), valueRecordNames: new Map()}, expectedType, preserveOptional = false) {
137
169
  const location = nodeLocation(node, filename, source)
138
170
 
171
+ if (node.type == "Identifier" && node.name == "undefined") {
172
+ return unsupportedSyntax(language, "undefined absence value", location)
173
+ }
174
+
175
+ if (expectedType?.kind == "OptionalType") {
176
+ if (node.type == "NullLiteral") {
177
+ return withParserRanges({kind: /** @type {const} */ ("OptionalNone"), location}, {absence: location})
178
+ }
179
+ if (knownExpressionType(node, context, language, filename, source)?.kind == "OptionalType") {
180
+ return convertExpression(node, language, filename, source, context, undefined, true)
181
+ }
182
+ return withParserRanges({
183
+ kind: /** @type {const} */ ("OptionalSome"),
184
+ location,
185
+ value: convertExpression(node, language, filename, source, context, expectedType.valueType)
186
+ }, {some: location})
187
+ }
188
+
189
+ if (node.type == "NullLiteral") return unsupportedSyntax(language, "null outside an explicit optional context", location)
190
+
191
+ if (node.type == "TSNonNullExpression") {
192
+ const expression = convertExpression(node.expression, language, filename, source, context)
193
+
194
+ if (language != "typescript" || expression.kind != "MapLookupExpression") {
195
+ return unsupportedSyntax(language, "non-null assertion other than Map.get result", location)
196
+ }
197
+ expression.location = location
198
+ return expression
199
+ }
200
+
139
201
  if (node.type == "Identifier") {
140
- return withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location, name: node.name}, {
202
+ const identifier = withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location, name: node.name}, {
141
203
  name: identifierLocation(node, filename, source)
142
204
  })
205
+
206
+ if (!preserveOptional && context.bindings.get(node.name)?.kind == "OptionalType") {
207
+ return withParserRanges({kind: /** @type {const} */ ("OptionalUnwrap"), location, operand: identifier}, {unwrap: location})
208
+ }
209
+
210
+ return identifier
211
+ }
212
+
213
+ if (node.type == "ThisExpression") {
214
+ if (!context.currentClass) return unsupportedSyntax(language, "receiver outside a reference class", location)
215
+
216
+ return withParserRanges({classId: /** @type {string} */ (context.currentClass.id), kind: /** @type {const} */ ("ReceiverExpression"), location},
217
+ {receiver: location})
143
218
  }
144
219
 
145
220
  if (node.type == "NumericLiteral") {
@@ -162,6 +237,199 @@ function convertExpression(node, language, filename, source) {
162
237
  return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: node.value}, {literal: location})
163
238
  }
164
239
 
240
+ if (node.type == "ArrayExpression") {
241
+ const elementType = expectedType?.kind == "ListType" ? expectedType.elementType : undefined
242
+ const elements = node.elements.map((element, index) => {
243
+ if (!element) return unsupportedSyntax(language, "sparse array hole", arrayHoleLocation(node, index, filename, source))
244
+ if (element.type == "SpreadElement") return unsupportedSyntax(language, "array spread", nodeLocation(element, filename, source))
245
+
246
+ return convertExpression(element, language, filename, source, context, elementType)
247
+ })
248
+
249
+ return withParserRanges({elements, kind: /** @type {const} */ ("ListLiteral"), location}, {literal: location})
250
+ }
251
+
252
+ if (node.type == "NewExpression" && node.callee.type == "Identifier" && node.callee.name == "Map") {
253
+ if (!nativeMapAvailable(context)) {
254
+ return unsupportedSyntax(language, "shadowed Map constructor", identifierLocation(node.callee, filename, source))
255
+ }
256
+ if (expectedType?.kind == "RecordType") return unsupportedSyntax(language, "Map used as a structural record substitute", location)
257
+ if (node.typeArguments || node.typeParameters || node.arguments.length > 1) {
258
+ return unsupportedSyntax(language, "generic or invalid Map construction", location)
259
+ }
260
+ const initializer = node.arguments[0]
261
+
262
+ if (!initializer) return withParserRanges({
263
+ entries: [],
264
+ kind: expectedType?.kind == "OrderedMapType"
265
+ ? /** @type {const} */ ("OrderedMapLiteral")
266
+ : /** @type {const} */ ("MapLiteral"),
267
+ location
268
+ }, {literal: location})
269
+ if (initializer.type == "SpreadElement" || initializer.type == "ArgumentPlaceholder") {
270
+ return unsupportedSyntax(language, initializer.type, nodeLocation(initializer, filename, source))
271
+ }
272
+ if (initializer.type != "ArrayExpression") {
273
+ return unsupportedSyntax(language, "Map initializer other than an entry array", nodeLocation(initializer, filename, source))
274
+ }
275
+ const valueType = expectedType?.kind == "MapType" || expectedType?.kind == "OrderedMapType"
276
+ ? expectedType.valueType : undefined
277
+ const entries = initializer.elements.map((element, index) => {
278
+ if (!element) return unsupportedSyntax(language, "sparse map initializer", arrayHoleLocation(initializer, index, filename, source))
279
+ if (element.type == "SpreadElement") return unsupportedSyntax(language, "map spread", nodeLocation(element, filename, source))
280
+ if (element.type != "ArrayExpression" || element.elements.length != 2) {
281
+ return unsupportedSyntax(language, "Map entry other than a key/value pair", nodeLocation(element, filename, source))
282
+ }
283
+ const [key, value] = element.elements
284
+
285
+ if (!key) return unsupportedSyntax(language, "sparse map key", arrayHoleLocation(element, 0, filename, source))
286
+ if (!value) return unsupportedSyntax(language, "sparse map value", arrayHoleLocation(element, 1, filename, source))
287
+ if (key.type == "SpreadElement" || value.type == "SpreadElement") {
288
+ const spread = key.type == "SpreadElement" ? key : value
289
+
290
+ return unsupportedSyntax(language, "map entry spread", nodeLocation(spread, filename, source))
291
+ }
292
+ if (key.type != "StringLiteral") {
293
+ return unsupportedSyntax(language, "map key other than a string literal", nodeLocation(key, filename, source))
294
+ }
295
+ const keyExpression = /** @type {import("../semantic/types.js").StringLiteral} */ (
296
+ convertExpression(key, language, filename, source, context)
297
+ )
298
+
299
+ return withParserRanges({
300
+ key: keyExpression,
301
+ kind: /** @type {const} */ ("MapEntry"),
302
+ location: nodeLocation(element, filename, source),
303
+ value: convertExpression(value, language, filename, source, context, valueType)
304
+ }, {
305
+ key: keyExpression.location,
306
+ operator: tokenLocation(element, ",", key.end ?? element.start ?? 0, value.start ?? element.end ?? source.length, filename, source)
307
+ })
308
+ })
309
+
310
+ return withParserRanges({
311
+ entries,
312
+ kind: expectedType?.kind == "OrderedMapType"
313
+ ? /** @type {const} */ ("OrderedMapLiteral")
314
+ : /** @type {const} */ ("MapLiteral"),
315
+ location
316
+ }, {literal: location})
317
+ }
318
+
319
+ if (node.type == "NewExpression" && node.callee.type == "Identifier" && context.valueRecordNames.has(node.callee.name)) {
320
+ const declaration = /** @type {import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration} */ (context.valueRecordNames.get(node.callee.name))
321
+ if (declaration.kind == "ClassDeclaration") {
322
+ if (node.typeArguments || node.typeParameters) return unsupportedSyntax(language, "generic reference construction", location)
323
+ const arguments_ = node.arguments.map((argument, index) => {
324
+ if (argument.type == "SpreadElement" || argument.type == "ArgumentPlaceholder") {
325
+ return unsupportedSyntax(language, argument.type, nodeLocation(argument, filename, source))
326
+ }
327
+
328
+ return convertExpression(argument, language, filename, source, context, declaration.constructor.parameters[index]?.type)
329
+ })
330
+ const classLocation = identifierLocation(node.callee, filename, source)
331
+
332
+ return withParserRanges({arguments: arguments_, kind: /** @type {const} */ ("ReferenceConstruction"), location,
333
+ reference: declaration.ownership?.kind == "ownedResource"
334
+ ? ownedReferenceType(/** @type {string} */ (declaration.id), classLocation)
335
+ : referenceType(/** @type {string} */ (declaration.id), classLocation)}, {class: classLocation})
336
+ }
337
+ const sourceArguments = node.typeArguments?.params ?? node.typeParameters?.params
338
+ const typeArguments = sourceArguments?.map((/** @type {import("@babel/types").TSType} */ argument) => convertTypeScriptValueTypeNode(
339
+ argument,
340
+ `Record '${declaration.name}' application`, location, filename, source, context.recordNames, context.typeParameters
341
+ )) ?? (expectedType?.kind == "RecordType" && expectedType.declarationId == declaration.id ? expectedType.arguments : undefined)
342
+ const arguments_ = node.arguments.map((argument, index) => {
343
+ if (argument.type == "SpreadElement" || argument.type == "ArgumentPlaceholder") {
344
+ return unsupportedSyntax(language, argument.type, nodeLocation(argument, filename, source))
345
+ }
346
+
347
+ return convertExpression(argument, language, filename, source, context,
348
+ instantiatedRecordFieldType(declaration, typeArguments, index))
349
+ })
350
+
351
+ return withParserRanges({
352
+ arguments: arguments_,
353
+ kind: /** @type {const} */ ("RecordConstruction"),
354
+ location,
355
+ record: recordType(/** @type {string} */ (declaration.id), identifierLocation(node.callee, filename, source), typeArguments)
356
+ }, {record: identifierLocation(node.callee, filename, source)})
357
+ }
358
+
359
+ if (node.type == "MemberExpression" && !node.optional && node.object.type != "Super") {
360
+ if (!node.computed && node.object.type == "ThisExpression" && context.currentClass) {
361
+ /** @type {import("@babel/types").Identifier | undefined} */
362
+ let property
363
+
364
+ if (language == "javascript" && node.property.type == "PrivateName") property = node.property.id
365
+ if (language == "typescript" && node.property.type == "Identifier") property = node.property
366
+ if (!property) return unsupportedSyntax(language, "noncanonical private instance field access", location)
367
+ const field = context.currentClass.fields.find((candidate) => candidate.name == property.name)
368
+
369
+ if (!field) return unsupportedSyntax(language, `unknown private field '${property.name}'`, identifierLocation(property, filename, source))
370
+ return withParserRanges({field: /** @type {string} */ (field.id), kind: /** @type {const} */ ("PrivateFieldRead"), location,
371
+ receiver: /** @type {import("../semantic/types.js").ReceiverExpression} */ (
372
+ convertExpression(node.object, language, filename, source, context))}, {member: identifierLocation(property, filename, source)})
373
+ }
374
+ if (knownValueExpressionType(node.object, context, language, filename, source)?.kind == "ReferenceType") {
375
+ return unsupportedSyntax(language, "private reference-class field access", location)
376
+ }
377
+ if (!node.computed && node.object.type == "Identifier" && node.property.type == "Identifier" &&
378
+ node.property.name == "message" && context.bindings.get(node.object.name)?.kind == "ErrorType") {
379
+ const receiverLocation = identifierLocation(node.object, filename, source)
380
+
381
+ return withParserRanges({
382
+ kind: /** @type {const} */ ("ErrorMessageRead"),
383
+ location,
384
+ receiver: withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location: receiverLocation, name: node.object.name}, {
385
+ name: receiverLocation
386
+ })
387
+ }, {member: identifierLocation(node.property, filename, source)})
388
+ }
389
+ if (node.computed) {
390
+ if (knownValueExpressionType(node.object, context, language, filename, source)?.kind == "RecordType") {
391
+ return unsupportedSyntax(language, "computed record member access", nodeLocation(node.property, filename, source))
392
+ }
393
+ if (node.property.type == "PrivateName") return unsupportedSyntax(language, node.property.type, location)
394
+
395
+ return withParserRanges({
396
+ collection: convertExpression(node.object, language, filename, source, context),
397
+ index: convertExpression(node.property, language, filename, source, context),
398
+ kind: /** @type {const} */ ("ListIndexExpression"),
399
+ location,
400
+ totality: /** @type {const} */ ("proven")
401
+ }, {
402
+ operator: tokenLocation(node, "[", node.object.end ?? node.start ?? 0, node.property.start ?? node.end ?? source.length, filename, source)
403
+ })
404
+ }
405
+ if (node.property.type == "Identifier" && ["length", "size"].includes(node.property.name)) {
406
+ const receiverType = knownValueExpressionType(node.object, context, language, filename, source)
407
+
408
+ if (receiverType?.kind == "RecordType") {
409
+ return withParserRanges({
410
+ field: node.property.name,
411
+ kind: /** @type {const} */ ("MemberRead"),
412
+ location,
413
+ receiver: convertExpression(node.object, language, filename, source, context)
414
+ }, {member: identifierLocation(node.property, filename, source)})
415
+ }
416
+ return withParserRanges({
417
+ collection: convertExpression(node.object, language, filename, source, context),
418
+ collectionKind: /** @type {"list" | "map"} */ (node.property.name == "length" ? "list" : "map"),
419
+ kind: /** @type {const} */ ("CollectionSizeExpression"),
420
+ location
421
+ }, {operator: identifierLocation(node.property, filename, source)})
422
+ }
423
+ if (node.property.type == "Identifier") {
424
+ return withParserRanges({
425
+ field: node.property.name,
426
+ kind: /** @type {const} */ ("MemberRead"),
427
+ location,
428
+ receiver: convertExpression(node.object, language, filename, source, context)
429
+ }, {member: identifierLocation(node.property, filename, source)})
430
+ }
431
+ }
432
+
165
433
  if (node.type == "TemplateLiteral") {
166
434
  if (node.expressions.length > 0) return unsupportedSyntax(language, "interpolated string", location)
167
435
 
@@ -175,7 +443,7 @@ function convertExpression(node, language, filename, source) {
175
443
  }
176
444
 
177
445
  if (node.type == "UnaryExpression" && ["!", "-"].includes(node.operator)) {
178
- const operand = convertExpression(node.argument, language, filename, source)
446
+ const operand = convertExpression(node.argument, language, filename, source, context)
179
447
  const semantic = withAdaptedOperation(withParserRanges({
180
448
  kind: "UnaryExpression",
181
449
  location,
@@ -190,9 +458,9 @@ function convertExpression(node, language, filename, source) {
190
458
  if (node.type == "LogicalExpression" && ["&&", "||"].includes(node.operator)) {
191
459
  const semantic = withAdaptedOperation(withParserRanges({
192
460
  kind: "BinaryExpression",
193
- left: convertExpression(node.left, language, filename, source),
461
+ left: convertExpression(node.left, language, filename, source, context),
194
462
  location,
195
- right: convertExpression(node.right, language, filename, source)
463
+ right: convertExpression(node.right, language, filename, source, context)
196
464
  }, {
197
465
  operator: tokenLocation(node, node.operator, node.left.end ?? node.start ?? 0, node.right.start ?? node.end ?? source.length, filename, source)
198
466
  }), node.operator == "&&" ? "And" : "Or")
@@ -200,11 +468,22 @@ function convertExpression(node, language, filename, source) {
200
468
  return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
201
469
  }
202
470
 
471
+ if (node.type == "BinaryExpression" && node.operator == "!==" && node.left.type == "Identifier" &&
472
+ node.right.type == "NullLiteral") {
473
+ const operand = /** @type {import("../semantic/types.js").IdentifierExpression} */ (
474
+ convertExpression(node.left, language, filename, source, context, undefined, true)
475
+ )
476
+
477
+ return withParserRanges({kind: /** @type {const} */ ("OptionalIsPresent"), location, operand}, {
478
+ operator: tokenLocation(node, node.operator, node.left.end ?? node.start ?? 0, node.right.start ?? node.end ?? source.length, filename, source)
479
+ })
480
+ }
481
+
203
482
  if (node.type == "BinaryExpression" && babelBinaryOperations.has(node.operator)) {
204
483
  if (node.left.type == "PrivateName") unsupportedSyntax(language, node.left.type, location)
205
484
 
206
- const left = convertExpression(node.left, language, filename, source)
207
- const right = convertExpression(node.right, language, filename, source)
485
+ const left = convertExpression(node.left, language, filename, source, context)
486
+ const right = convertExpression(node.right, language, filename, source, context)
208
487
 
209
488
  const semantic = withAdaptedOperation(withParserRanges({
210
489
  kind: "BinaryExpression",
@@ -223,12 +502,17 @@ function convertExpression(node, language, filename, source) {
223
502
  }
224
503
 
225
504
  if (node.type == "CallExpression" && node.callee.type == "Identifier") {
226
- const arguments_ = node.arguments.map((argument) => {
505
+ if (node.optional || node.callee.optional || node.typeArguments || node.typeParameters) {
506
+ return unsupportedSyntax(language, "optional or generic call", location)
507
+ }
508
+ const signature = context.functions.get(node.callee.name)
509
+ const arguments_ = node.arguments.map((argument, index) => {
227
510
  if (argument.type == "SpreadElement" || argument.type == "ArgumentPlaceholder") {
228
511
  return unsupportedSyntax(language, argument.type, nodeLocation(argument, filename, source))
229
512
  }
230
513
 
231
- return convertExpression(argument, language, filename, source)
514
+ return convertExpression(argument, language, filename, source, context, signature?.parameters[index]?.type,
515
+ preservesGenericOptionalEvidence(signature, index))
232
516
  })
233
517
 
234
518
  return withParserRanges({arguments: arguments_, callee: node.callee.name, kind: /** @type {const} */ ("CallExpression"), location}, {
@@ -236,23 +520,207 @@ function convertExpression(node, language, filename, source) {
236
520
  })
237
521
  }
238
522
 
523
+ if (node.type == "CallExpression" && !node.optional && !node.typeArguments && !node.typeParameters &&
524
+ node.callee.type == "MemberExpression" && !node.callee.computed && !node.callee.optional &&
525
+ node.callee.object.type != "Super" && node.callee.property.type == "Identifier") {
526
+ const receiverType = knownValueExpressionType(node.callee.object, context, language, filename, source)
527
+
528
+ if (receiverType?.kind == "ReferenceType" || receiverType?.kind == "OwnedReferenceType") {
529
+ const declaration = context.classes.get(receiverType.declarationId)
530
+ const methodName = node.callee.property.name
531
+ const method = declaration?.methods.find((candidate) => candidate.name == methodName)
532
+ const arguments_ = node.arguments.map((argument, index) => {
533
+ if (argument.type == "SpreadElement" || argument.type == "ArgumentPlaceholder") {
534
+ return unsupportedSyntax(language, argument.type, nodeLocation(argument, filename, source))
535
+ }
536
+
537
+ return convertExpression(argument, language, filename, source, context, method?.parameters[index]?.type)
538
+ })
539
+
540
+ return withParserRanges({arguments: arguments_, kind: /** @type {const} */ ("MethodCallExpression"), location,
541
+ method: method?.id ?? node.callee.property.name,
542
+ receiver: convertExpression(node.callee.object, language, filename, source, context)},
543
+ {member: identifierLocation(node.callee.property, filename, source)})
544
+ }
545
+ }
546
+
547
+ if (node.type == "CallExpression" && !node.optional && !node.typeArguments && !node.typeParameters &&
548
+ node.callee.type == "MemberExpression" && !node.callee.computed && !node.callee.optional &&
549
+ node.callee.object.type != "Super" && node.callee.property.type == "Identifier" &&
550
+ node.callee.property.name == "get") {
551
+ if (node.arguments.length != 1) return unsupportedSyntax(language, "Map.get argument count", location)
552
+ const key = node.arguments[0]
553
+
554
+ if (key.type == "SpreadElement" || key.type == "ArgumentPlaceholder") {
555
+ return unsupportedSyntax(language, key.type, nodeLocation(key, filename, source))
556
+ }
557
+
558
+ return withParserRanges({
559
+ collection: convertExpression(node.callee.object, language, filename, source, context),
560
+ key: convertExpression(key, language, filename, source, context),
561
+ kind: /** @type {const} */ ("MapLookupExpression"),
562
+ location,
563
+ totality: /** @type {const} */ ("proven")
564
+ }, {operator: identifierLocation(node.callee.property, filename, source)})
565
+ }
566
+
239
567
  return unsupportedSyntax(language, node.type, location)
240
568
  }
241
569
 
570
+ /**
571
+ * Resolves only expression result types already established by explicit module
572
+ * signatures and collection bindings.
573
+ * @param {import("@babel/types").Expression} node - Parser-owned expression.
574
+ * @param {JavaScriptConversionContext} context - Typed lexical context.
575
+ * @param {"javascript" | "typescript"} language - Frontend language.
576
+ * @param {string} filename - Source filename.
577
+ * @param {string} source - Complete source.
578
+ * @returns {import("../semantic/types.js").SemanticFunctionReturnType | import("../semantic/types.js").ErrorType | undefined} Known result type.
579
+ */
580
+ function knownExpressionType(node, context, language, filename, source) {
581
+ if (node.type == "TSNonNullExpression") return knownExpressionType(node.expression, context, language, filename, source)
582
+ if (node.type == "Identifier") return context.bindings.get(node.name)
583
+ if (node.type == "CallExpression" && node.callee.type == "Identifier") {
584
+ const signature = context.functions.get(node.callee.name)
585
+
586
+ if (signature) return knownCallReturnType(signature, node.arguments.map((argument) =>
587
+ argument.type == "SpreadElement" || argument.type == "ArgumentPlaceholder"
588
+ ? undefined
589
+ : knownExpressionType(argument, context, language, filename, source)))
590
+ }
591
+ if (node.type == "NewExpression" && node.callee.type == "Identifier") {
592
+ const declaration = context.valueRecordNames.get(node.callee.name)
593
+
594
+ if (declaration?.id) {
595
+ if (declaration.kind == "ClassDeclaration") return {declarationId: declaration.id,
596
+ kind: declaration.ownership?.kind == "ownedResource" ? "OwnedReferenceType" : "ReferenceType"}
597
+ const sourceArguments = node.typeArguments?.params ?? node.typeParameters?.params
598
+ const typeArguments = sourceArguments?.map((/** @type {import("@babel/types").TSType} */ argument) =>
599
+ convertTypeScriptValueTypeNode(
600
+ argument,
601
+ `Record '${declaration.name}' application`,
602
+ nodeLocation(node, filename, source),
603
+ filename,
604
+ source,
605
+ context.recordNames,
606
+ context.typeParameters
607
+ ))
608
+
609
+ return {declarationId: declaration.id, kind: "RecordType", ...(typeArguments ? {arguments: typeArguments} : {})}
610
+ }
611
+ }
612
+ if (node.type == "ThisExpression" && context.currentClass?.id) {
613
+ return {declarationId: context.currentClass.id, kind: "ReferenceType"}
614
+ }
615
+ if (node.type == "CallExpression" && node.callee.type == "MemberExpression" && !node.callee.computed &&
616
+ node.callee.object.type != "Super" && node.callee.property.type == "Identifier") {
617
+ const receiver = knownValueExpressionType(node.callee.object, context, language, filename, source)
618
+
619
+ if (receiver?.kind == "ReferenceType") {
620
+ const methodName = node.callee.property.name
621
+
622
+ return context.classes.get(receiver.declarationId)?.methods.find((method) => method.name == methodName)?.returnType
623
+ }
624
+ }
625
+ if (node.type == "MemberExpression" && !node.computed && node.object.type == "ThisExpression" && context.currentClass) {
626
+ if (language == "javascript" && node.property.type == "PrivateName") {
627
+ const name = node.property.id.name
628
+
629
+ return context.currentClass.fields.find((field) => field.name == name)?.type
630
+ }
631
+ if (language == "typescript" && node.property.type == "Identifier") {
632
+ const name = node.property.name
633
+
634
+ return context.currentClass.fields.find((field) => field.name == name)?.type
635
+ }
636
+
637
+ return undefined
638
+ }
639
+ if (node.type == "MemberExpression" && !node.computed && node.object.type != "Super" && node.property.type == "Identifier") {
640
+ const receiver = knownValueExpressionType(node.object, context, language, filename, source)
641
+
642
+ if (receiver?.kind != "RecordType") return undefined
643
+ const declaration = context.records.get(receiver.declarationId)
644
+ const memberName = node.property.name
645
+ const index = declaration?.fields.findIndex((field) => field.name == memberName) ?? -1
646
+
647
+ return declaration ? instantiatedRecordFieldType(declaration, receiver.arguments, index) : undefined
648
+ }
649
+ if (node.type == "MemberExpression" && node.computed && node.object.type != "Super") {
650
+ const collectionType = knownValueExpressionType(node.object, context, language, filename, source)
651
+
652
+ if (collectionType?.kind == "ListType") return collectionType.elementType
653
+ }
654
+ if (node.type == "CallExpression" && node.callee.type == "MemberExpression" &&
655
+ !node.callee.computed && node.callee.object.type != "Super" &&
656
+ node.callee.property.type == "Identifier" && node.callee.property.name == "get") {
657
+ const collectionType = knownValueExpressionType(node.callee.object, context, language, filename, source)
658
+
659
+ if (collectionType?.kind == "MapType" || collectionType?.kind == "OrderedMapType") return collectionType.valueType
660
+ }
661
+
662
+ return undefined
663
+ }
664
+
665
+ /**
666
+ * Resolves the value type produced by the frontend's implicit optional-binding unwrap.
667
+ * Semantic validation separately proves that the unwrap occurs only on a present path.
668
+ * @param {import("@babel/types").Expression} node - Parser-owned expression.
669
+ * @param {JavaScriptConversionContext} context - Typed lexical context.
670
+ * @param {"javascript" | "typescript"} language - Frontend language.
671
+ * @param {string} filename - Source filename.
672
+ * @param {string} source - Complete source.
673
+ * @returns {import("../semantic/types.js").SemanticFunctionReturnType | import("../semantic/types.js").ErrorType | undefined} Converted value type.
674
+ */
675
+ function knownValueExpressionType(node, context, language, filename, source) {
676
+ const type = knownExpressionType(node, context, language, filename, source)
677
+
678
+ return node.type == "Identifier" && type?.kind == "OptionalType" ? type.valueType : type
679
+ }
680
+
681
+ /**
682
+ * Returns a stable parser-bounded location for one absent array element.
683
+ * @param {import("@babel/types").ArrayExpression} node - Array expression.
684
+ * @param {number} index - Missing element index.
685
+ * @param {string} filename - Source filename.
686
+ * @param {string} source - Complete source.
687
+ * @returns {import("../semantic/types.js").SourceLocation} Hole location.
688
+ */
689
+ function arrayHoleLocation(node, index, filename, source) {
690
+ const previous = node.elements.slice(0, index).findLast((element) => element != null)
691
+ const next = node.elements.slice(index + 1).find((element) => element != null)
692
+ const start = previous?.end ?? (node.start ?? 0) + 1
693
+ const end = next?.start ?? Math.max(start + 1, (node.end ?? source.length) - 1)
694
+
695
+ return locationFromOffsets(filename, source, start, Math.max(start + 1, Math.min(end, source.length)))
696
+ }
697
+
242
698
  /**
243
699
  * Converts a supported return statement.
244
700
  * @param {import("@babel/types").Statement} node - Babel statement.
245
701
  * @param {"javascript" | "typescript"} language - Frontend language.
246
702
  * @param {string} filename - Source filename.
247
703
  * @param {string} source - Complete source.
704
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
248
705
  * @returns {import("../semantic/types.js").ReturnStatement} Semantic return.
249
706
  */
250
- function convertReturn(node, language, filename, source) {
707
+ function convertReturn(node, language, filename, source, context) {
251
708
  const location = nodeLocation(node, filename, source)
252
709
 
253
- if (node.type != "ReturnStatement" || !node.argument) return unsupportedSyntax(language, node.type, location)
710
+ if (node.type != "ReturnStatement") return unsupportedSyntax(language, node.type, location)
254
711
 
255
- return {expression: convertExpression(node.argument, language, filename, source), kind: "ReturnStatement", location}
712
+ return {
713
+ ...(node.argument ? {expression: convertExpression(
714
+ node.argument,
715
+ language,
716
+ filename,
717
+ source,
718
+ context,
719
+ context.returnType?.kind == "TypeReference" && context.returnType.name == "void" ? undefined : context.returnType
720
+ )} : {}),
721
+ kind: "ReturnStatement",
722
+ location
723
+ }
256
724
  }
257
725
 
258
726
  /**
@@ -261,9 +729,10 @@ function convertReturn(node, language, filename, source) {
261
729
  * @param {"javascript" | "typescript"} language - Frontend language.
262
730
  * @param {string} filename - Source filename.
263
731
  * @param {string} source - Complete source.
264
- * @returns {import("../semantic/types.js").LocalStatement} Semantic local statement.
732
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
733
+ * @returns {import("../semantic/types.js").LocalStatement | import("../semantic/types.js").PrivateFieldWriteStatement} Semantic local or private-field statement.
265
734
  */
266
- function convertLocalStatement(node, language, filename, source) {
735
+ function convertLocalStatement(node, language, filename, source, context) {
267
736
  const location = nodeLocation(node, filename, source)
268
737
 
269
738
  if (node.type == "VariableDeclaration") {
@@ -277,13 +746,20 @@ function convertLocalStatement(node, language, filename, source) {
277
746
  if (declarator.id.type != "Identifier") return unsupportedSyntax(language, declarator.id.type, declaratorLocation)
278
747
  if (!declarator.init) return unsupportedSyntax(language, "uninitialized declaration", declaratorLocation)
279
748
 
280
- const type = language == "typescript"
281
- ? convertTypeScriptType(declarator.id.typeAnnotation, `Local '${declarator.id.name}'`, declaratorLocation, filename, source)
282
- : localJavaScriptType(node, declarator.id.name, filename, source)
749
+ let type = language == "typescript"
750
+ ? convertTypeScriptType(declarator.id.typeAnnotation, `Local '${declarator.id.name}'`, declaratorLocation, filename, source,
751
+ context.recordNames, context.typeParameters)
752
+ : localJavaScriptType(node, declarator.id.name, filename, source, context.recordNames, context.typeParameters)
283
753
 
284
- return withParserRanges({
285
- initializer: convertExpression(declarator.init, language, filename, source),
286
- kind: "LocalDeclaration",
754
+ if (node.kind == "const" && type.kind == "MapType" && context.orderedMapDeclarations?.has(declarator) &&
755
+ declarator.init.type == "NewExpression" && declarator.init.callee.type == "Identifier" &&
756
+ declarator.init.callee.name == "Map" && nativeMapAvailable(context)) {
757
+ type = orderedMapTypeFromMap(type, declaratorLocation)
758
+ }
759
+
760
+ const semantic = withParserRanges({
761
+ initializer: convertExpression(declarator.init, language, filename, source, context, type),
762
+ kind: /** @type {const} */ ("LocalDeclaration"),
287
763
  location,
288
764
  mutable: node.kind == "let",
289
765
  name: declarator.id.name,
@@ -292,6 +768,9 @@ function convertLocalStatement(node, language, filename, source) {
292
768
  name: identifierLocation(declarator.id, filename, source),
293
769
  operator: tokenLocation(declarator, "=", declarator.id.end ?? declarator.start ?? 0, declarator.init.start ?? declarator.end ?? source.length, filename, source)
294
770
  })
771
+
772
+ context.bindings.set(declarator.id.name, type)
773
+ return semantic
295
774
  }
296
775
 
297
776
  if (node.type == "ExpressionStatement" && node.expression.type == "AssignmentExpression") {
@@ -299,20 +778,45 @@ function convertLocalStatement(node, language, filename, source) {
299
778
  const targetLocation = nodeLocation(assignment.left, filename, source)
300
779
 
301
780
  if (assignment.operator != "=") return unsupportedSyntax(language, `assignment ${assignment.operator}`, location)
781
+ if (assignment.left.type == "MemberExpression" && !assignment.left.computed &&
782
+ assignment.left.object.type == "ThisExpression" && context.currentClass) {
783
+ /** @type {import("@babel/types").Identifier | undefined} */
784
+ let property
785
+
786
+ if (language == "javascript" && assignment.left.property.type == "PrivateName") property = assignment.left.property.id
787
+ if (language == "typescript" && assignment.left.property.type == "Identifier") property = assignment.left.property
788
+ if (!property) return unsupportedSyntax(language, "noncanonical private instance field assignment", location)
789
+ const field = context.currentClass.fields.find((candidate) => candidate.name == property.name)
790
+
791
+ if (!field) return unsupportedSyntax(language, `unknown private field '${property.name}'`, identifierLocation(property, filename, source))
792
+ return withParserRanges({
793
+ expression: convertExpression(assignment.right, language, filename, source, context, field.type),
794
+ field: /** @type {string} */ (field.id),
795
+ kind: /** @type {const} */ ("PrivateFieldWriteStatement"),
796
+ location,
797
+ receiver: /** @type {import("../semantic/types.js").ReceiverExpression} */ (
798
+ convertExpression(assignment.left.object, language, filename, source, context))
799
+ }, {member: identifierLocation(property, filename, source), operator: tokenLocation(assignment, "=",
800
+ assignment.left.end ?? assignment.start ?? 0, assignment.right.start ?? assignment.end ?? source.length, filename, source)})
801
+ }
302
802
  if (assignment.left.type != "Identifier") return unsupportedSyntax(language, assignment.left.type, targetLocation)
303
803
 
304
804
  const target = withParserRanges({kind: /** @type {const} */ ("IdentifierExpression"), location: targetLocation, name: assignment.left.name}, {
305
805
  name: identifierLocation(assignment.left, filename, source)
306
806
  })
307
807
 
308
- return withParserRanges({
309
- expression: convertExpression(assignment.right, language, filename, source),
310
- kind: "AssignmentStatement",
808
+ const bindingType = context.bindings.get(assignment.left.name)
809
+ const semantic = withParserRanges({
810
+ expression: convertExpression(assignment.right, language, filename, source, context,
811
+ bindingType?.kind == "ErrorType" ? undefined : bindingType),
812
+ kind: /** @type {const} */ ("AssignmentStatement"),
311
813
  location,
312
814
  target
313
815
  }, {
314
816
  operator: tokenLocation(assignment, "=", assignment.left.end ?? assignment.start ?? 0, assignment.right.start ?? assignment.end ?? source.length, filename, source)
315
817
  })
818
+
819
+ return semantic
316
820
  }
317
821
 
318
822
  return unsupportedSyntax(language, node.type, location)
@@ -324,9 +828,11 @@ function convertLocalStatement(node, language, filename, source) {
324
828
  * @param {string} name - Local name.
325
829
  * @param {string} filename - Source filename.
326
830
  * @param {string} source - Complete source.
327
- * @returns {import("../semantic/types.js").TypeReference} Semantic type.
831
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
832
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
833
+ * @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
328
834
  */
329
- function localJavaScriptType(node, name, filename, source) {
835
+ function localJavaScriptType(node, name, filename, source, recordNames, typeParameters = new Map()) {
330
836
  const comment = node.leadingComments?.at(-1)
331
837
  const location = nodeLocation(node, filename, source)
332
838
 
@@ -350,7 +856,10 @@ function localJavaScriptType(node, name, filename, source) {
350
856
  "javascript",
351
857
  `Local '${name}'`,
352
858
  location,
353
- tag ? commentTagLocation(comment, tag, "type", filename, source) : location
859
+ source,
860
+ tag ? commentTagLocation(comment, tag, "type", filename, source) : location,
861
+ recordNames,
862
+ typeParameters
354
863
  )
355
864
  }
356
865
 
@@ -361,19 +870,255 @@ function localJavaScriptType(node, name, filename, source) {
361
870
  * @param {string} filename - Source filename.
362
871
  * @param {string} source - Complete source.
363
872
  * @param {boolean} canonicalZeroRequired - Whether generated scalar output may contain signed zero.
873
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
364
874
  * @returns {import("../semantic/types.js").Statement} Semantic statement.
365
875
  */
366
- function convertStatement(node, language, filename, source, canonicalZeroRequired) {
367
- if (node.type == "ReturnStatement") return convertReturn(node, language, filename, source)
368
- if (node.type == "IfStatement") return convertIf(node, language, filename, source, canonicalZeroRequired)
876
+ function convertStatement(node, language, filename, source, canonicalZeroRequired, context) {
877
+ if (node.type == "ReturnStatement") return convertReturn(node, language, filename, source, context)
878
+ if (node.type == "IfStatement") return convertIf(node, language, filename, source, canonicalZeroRequired, context)
879
+ if (node.type == "ThrowStatement") return convertTypedThrow(node, language, filename, source, context)
880
+ if (node.type == "TryStatement") return convertTypedTry(node, language, filename, source, canonicalZeroRequired, context)
881
+ if (node.type == "WhileStatement") return convertWhile(node, language, filename, source, canonicalZeroRequired, context)
882
+ if (node.type == "ForOfStatement") return convertForEach(node, language, filename, source, canonicalZeroRequired, context)
883
+ if (node.type == "BreakStatement" || node.type == "ContinueStatement") {
884
+ if (node.label) return unsupportedSyntax(language, `labeled ${node.type == "BreakStatement" ? "break" : "continue"}`,
885
+ nodeLocation(node.label, filename, source))
886
+ const keyword = node.type == "BreakStatement" ? "break" : "continue"
887
+ const location = tokenLocation(node, keyword, node.start ?? 0, node.end ?? source.length, filename, source)
888
+
889
+ return withParserRanges({kind: node.type, location}, {keyword: location})
890
+ }
369
891
  if (node.type == "VariableDeclaration" || node.type == "ExpressionStatement" && node.expression.type == "AssignmentExpression") {
370
- return convertLocalStatement(node, language, filename, source)
892
+ return convertLocalStatement(node, language, filename, source, context)
893
+ }
894
+ if (node.type == "ExpressionStatement") {
895
+ if (node.expression.type == "CallExpression" && (node.expression.callee.type == "Identifier" ||
896
+ node.expression.callee.type == "MemberExpression" && !node.expression.callee.computed &&
897
+ node.expression.callee.object.type != "Super" &&
898
+ ["ReferenceType", "OwnedReferenceType"].includes(String(
899
+ knownValueExpressionType(node.expression.callee.object, context, language, filename, source)?.kind)))) {
900
+ return {
901
+ expression: /** @type {import("../semantic/types.js").CallExpression | import("../semantic/types.js").MethodCallExpression} */ (
902
+ convertExpression(node.expression, language, filename, source, context)
903
+ ),
904
+ kind: "ExpressionStatement",
905
+ location: nodeLocation(node, filename, source)
906
+ }
907
+ }
908
+
909
+ return convertPrint(node, language, filename, source, canonicalZeroRequired, context)
371
910
  }
372
- if (node.type == "ExpressionStatement") return convertPrint(node, language, filename, source, canonicalZeroRequired)
373
911
 
374
912
  return unsupportedSyntax(language, node.type, nodeLocation(node, filename, source))
375
913
  }
376
914
 
915
+ /**
916
+ * Converts exact `throw new DeclaredError(message)`.
917
+ * @param {import("@babel/types").ThrowStatement} node - Babel throw statement.
918
+ * @param {"javascript" | "typescript"} language - Frontend language.
919
+ * @param {string} filename - Source filename.
920
+ * @param {string} source - Complete source.
921
+ * @param {JavaScriptConversionContext} context - Typed lexical context.
922
+ * @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
923
+ */
924
+ function convertTypedThrow(node, language, filename, source, context) {
925
+ const location = nodeLocation(node, filename, source)
926
+ const argument = node.argument
927
+
928
+ if (!argument || argument.type != "NewExpression" || argument.callee.type != "Identifier" ||
929
+ context.bindings.has(argument.callee.name) || !context.errorNames.has(argument.callee.name) || argument.arguments.length != 1 ||
930
+ argument.arguments[0].type == "SpreadElement" || argument.arguments[0].type == "ArgumentPlaceholder" ||
931
+ argument.typeArguments || argument.typeParameters) {
932
+ return unsupportedSyntax(language, "throw other than exact declared error construction", location)
933
+ }
934
+ const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(argument.callee.name))
935
+ const typeLocation = identifierLocation(argument.callee, filename, source)
936
+
937
+ return withParserRanges({
938
+ error: withParserRanges({
939
+ error: withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {type: typeLocation}),
940
+ kind: /** @type {const} */ ("ErrorConstruction"),
941
+ location: nodeLocation(argument, filename, source),
942
+ message: convertExpression(argument.arguments[0], language, filename, source, context)
943
+ }, {type: typeLocation}),
944
+ kind: /** @type {const} */ ("RaiseStatement"),
945
+ location
946
+ }, {keyword: tokenLocation(node, "throw", node.start ?? 0, argument.start ?? node.end ?? source.length, filename, source)})
947
+ }
948
+
949
+ /**
950
+ * Converts one catch with the canonical `instanceof` guard and exact rethrow.
951
+ * @param {import("@babel/types").TryStatement} node - Babel try statement.
952
+ * @param {"javascript" | "typescript"} language - Frontend language.
953
+ * @param {string} filename - Source filename.
954
+ * @param {string} source - Complete source.
955
+ * @param {boolean} canonicalZeroRequired - Whether scalar output canonicalizes zero.
956
+ * @param {JavaScriptConversionContext} context - Typed lexical context.
957
+ * @returns {import("../semantic/types.js").TryStatement} Semantic exact handler.
958
+ */
959
+ function convertTypedTry(node, language, filename, source, canonicalZeroRequired, context) {
960
+ const location = nodeLocation(node, filename, source)
961
+
962
+ if (node.finalizer || !node.handler || node.handler.param?.type != "Identifier" || node.handler.body.directives.length > 0) {
963
+ return unsupportedSyntax(language, "try without one explicit guarded catch", location)
964
+ }
965
+ const binding = node.handler.param
966
+ const guard = node.handler.body.body[0]
967
+
968
+ if (!guard || guard.type != "IfStatement" || guard.alternate || guard.test.type != "UnaryExpression" ||
969
+ guard.test.operator != "!" || guard.test.argument.type != "BinaryExpression" || guard.test.argument.operator != "instanceof" ||
970
+ guard.test.argument.left.type != "Identifier" || guard.test.argument.left.name != binding.name ||
971
+ guard.test.argument.right.type != "Identifier" || guard.test.argument.right.name == binding.name ||
972
+ context.bindings.has(guard.test.argument.right.name) || !context.errorNames.has(guard.test.argument.right.name) ||
973
+ guard.consequent.type != "BlockStatement" || guard.consequent.body.length != 1 || guard.consequent.directives.length > 0 ||
974
+ guard.consequent.body[0].type != "ThrowStatement" || guard.consequent.body[0].argument?.type != "Identifier" ||
975
+ guard.consequent.body[0].argument.name != binding.name) {
976
+ return unsupportedSyntax(language, "catch without exact instanceof guard and unmatched rethrow", nodeLocation(guard ?? node.handler, filename, source))
977
+ }
978
+ const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(guard.test.argument.right.name))
979
+ const caughtTypeLocation = identifierLocation(guard.test.argument.right, filename, source)
980
+ const catchType = withParserRanges({
981
+ declarationId: /** @type {string} */ (declaration.id),
982
+ kind: /** @type {const} */ ("ErrorType")
983
+ }, {type: caughtTypeLocation})
984
+ const bodyContext = {...context, bindings: new Map(context.bindings)}
985
+ const catchContext = {...context, bindings: new Map(context.bindings)}
986
+
987
+ catchContext.bindings.set(binding.name, catchType)
988
+ const catchStatements = node.handler.body.body.slice(1)
989
+
990
+ return withParserRanges({
991
+ body: convertBlock(node.block, language, filename, source, canonicalZeroRequired, bodyContext),
992
+ catchBinding: withParserRanges({
993
+ kind: /** @type {const} */ ("CatchBinding"),
994
+ location: identifierLocation(binding, filename, source),
995
+ mutable: /** @type {const} */ (false),
996
+ name: binding.name,
997
+ type: catchType
998
+ }, {name: identifierLocation(binding, filename, source)}),
999
+ catchBody: convertBlock(catchStatements, language, filename, source, canonicalZeroRequired, catchContext,
1000
+ nodeLocation(node.handler.body, filename, source)),
1001
+ catchType,
1002
+ kind: /** @type {const} */ ("TryStatement"),
1003
+ location
1004
+ }, {
1005
+ catch: tokenLocation(node.handler, "catch", node.handler.start ?? 0, binding.start ?? node.handler.end ?? source.length, filename, source),
1006
+ guard: nodeLocation(guard, filename, source),
1007
+ try: tokenLocation(node, "try", node.start ?? 0, node.block.start ?? node.end ?? source.length, filename, source)
1008
+ })
1009
+ }
1010
+
1011
+ /**
1012
+ * Converts the exact block-bodied `for (const value of list)` profile.
1013
+ * @param {import("@babel/types").ForOfStatement} node - Babel for-of statement.
1014
+ * @param {"javascript" | "typescript"} language - Frontend language.
1015
+ * @param {string} filename - Source filename.
1016
+ * @param {string} source - Complete source.
1017
+ * @param {boolean} canonicalZeroRequired - Whether scalar output canonicalizes zero.
1018
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
1019
+ * @returns {import("../semantic/types.js").ForEachStatement | import("../semantic/types.js").ForEachMapStatement} Semantic loop.
1020
+ */
1021
+ function convertForEach(node, language, filename, source, canonicalZeroRequired, context) {
1022
+ const location = nodeLocation(node, filename, source)
1023
+
1024
+ if (node.await) return unsupportedSyntax(language, "async for-of", location)
1025
+ if (node.left.type != "VariableDeclaration") return unsupportedSyntax(language, "for-of assignment binding", nodeLocation(node.left, filename, source))
1026
+ if (node.left.kind != "const") {
1027
+ return unsupportedSyntax(language, `${node.left.kind} iteration binding`, tokenLocation(
1028
+ node.left,
1029
+ node.left.kind,
1030
+ node.left.start ?? node.start ?? 0,
1031
+ node.left.end ?? node.end ?? source.length,
1032
+ filename,
1033
+ source
1034
+ ))
1035
+ }
1036
+ if (node.left.declarations.length != 1) return unsupportedSyntax(language, "multiple iteration bindings", nodeLocation(node.left, filename, source))
1037
+ const declarator = node.left.declarations[0]
1038
+
1039
+ if (declarator.id.type == "ArrayPattern") {
1040
+ const [key, value] = declarator.id.elements
1041
+
1042
+ if (declarator.id.elements.length != 2 || !key || !value || key.type != "Identifier" || value.type != "Identifier" ||
1043
+ declarator.id.typeAnnotation || key.typeAnnotation || value.typeAnnotation || key.optional || value.optional) {
1044
+ return unsupportedSyntax(language, "map iteration binding shape", nodeLocation(declarator.id, filename, source))
1045
+ }
1046
+ if (declarator.init) return unsupportedSyntax(language, "initialized iteration binding", nodeLocation(declarator, filename, source))
1047
+ if (node.body.type != "BlockStatement") return unsupportedSyntax(language, "for-of without block body", nodeLocation(node.body, filename, source))
1048
+ const collectionType = iterationOperandType(knownExpressionType(node.right, context, language, filename, source))
1049
+
1050
+ if (collectionType?.kind == "MapType") {
1051
+ return unsupportedSyntax(language, "unordered map iteration", nodeLocation(node.right, filename, source))
1052
+ }
1053
+ if (!collectionType || collectionType.kind != "OrderedMapType") {
1054
+ return missingType(language, "Ordered map iteration collection", nodeLocation(node.right, filename, source))
1055
+ }
1056
+ const keyLocation = identifierLocation(key, filename, source)
1057
+ const valueLocation = identifierLocation(value, filename, source)
1058
+ const keyType = iterationBindingType(collectionType.keyType, keyLocation)
1059
+ const valueType = iterationBindingType(collectionType.valueType, valueLocation)
1060
+ const keyBinding = withParserRanges({
1061
+ kind: /** @type {const} */ ("ValueBinding"), location: keyLocation, mutable: /** @type {const} */ (false),
1062
+ name: key.name, type: keyType
1063
+ }, {name: keyLocation})
1064
+ const valueBinding = withParserRanges({
1065
+ kind: /** @type {const} */ ("ValueBinding"), location: valueLocation, mutable: /** @type {const} */ (false),
1066
+ name: value.name, type: valueType
1067
+ }, {name: valueLocation})
1068
+ const bodyContext = {...context, bindings: new Map(context.bindings)}
1069
+
1070
+ bodyContext.bindings.set(key.name, keyType)
1071
+ bodyContext.bindings.set(value.name, valueType)
1072
+ return withParserRanges({
1073
+ body: convertBlock(node.body, language, filename, source, canonicalZeroRequired, bodyContext),
1074
+ keyBinding,
1075
+ kind: /** @type {const} */ ("ForEachMapStatement"),
1076
+ location,
1077
+ map: convertExpression(node.right, language, filename, source, context),
1078
+ valueBinding
1079
+ }, {
1080
+ operator: tokenLocation(node, "of", node.left.end ?? node.start ?? 0, node.right.start ?? node.end ?? source.length,
1081
+ filename, source)
1082
+ })
1083
+ }
1084
+ if (declarator.id.type != "Identifier") return unsupportedSyntax(language, declarator.id.type, nodeLocation(declarator.id, filename, source))
1085
+ if (declarator.init) return unsupportedSyntax(language, "initialized iteration binding", nodeLocation(declarator, filename, source))
1086
+ if (node.body.type != "BlockStatement") return unsupportedSyntax(language, "for-of without block body", nodeLocation(node.body, filename, source))
1087
+ const collectionType = iterationOperandType(knownExpressionType(node.right, context, language, filename, source))
1088
+
1089
+ if (collectionType?.kind == "MapType" || collectionType?.kind == "OrderedMapType") {
1090
+ return unsupportedSyntax(language, "map iteration without key/value pair binding", nodeLocation(node.right, filename, source))
1091
+ }
1092
+ if (!collectionType || collectionType.kind != "ListType") {
1093
+ return missingType(language, "Iteration collection", nodeLocation(node.right, filename, source))
1094
+ }
1095
+ const bindingLocation = identifierLocation(declarator.id, filename, source)
1096
+ const inferredType = collectionType.elementType
1097
+ const bindingType = language == "typescript" && declarator.id.typeAnnotation
1098
+ ? convertTypeScriptType(declarator.id.typeAnnotation, `Iteration binding '${declarator.id.name}'`, bindingLocation, filename, source,
1099
+ context.recordNames, context.typeParameters)
1100
+ : iterationBindingType(inferredType, bindingLocation)
1101
+ const valueBinding = withParserRanges({
1102
+ kind: /** @type {const} */ ("ValueBinding"),
1103
+ location: bindingLocation,
1104
+ mutable: /** @type {const} */ (false),
1105
+ name: declarator.id.name,
1106
+ type: bindingType
1107
+ }, {name: bindingLocation})
1108
+ const bodyContext = {...context, bindings: new Map(context.bindings)}
1109
+
1110
+ bodyContext.bindings.set(declarator.id.name, bindingType)
1111
+ return withParserRanges({
1112
+ body: convertBlock(node.body, language, filename, source, canonicalZeroRequired, bodyContext),
1113
+ kind: /** @type {const} */ ("ForEachStatement"),
1114
+ list: convertExpression(node.right, language, filename, source, context),
1115
+ location,
1116
+ valueBinding
1117
+ }, {
1118
+ operator: tokenLocation(node, "of", node.left.end ?? node.start ?? 0, node.right.start ?? node.end ?? source.length, filename, source)
1119
+ })
1120
+ }
1121
+
377
1122
  /**
378
1123
  * Converts one Babel block or normalized single-statement body.
379
1124
  * @param {import("@babel/types").BlockStatement | import("@babel/types").Statement[]} input - Parser block or statements.
@@ -381,13 +1126,23 @@ function convertStatement(node, language, filename, source, canonicalZeroRequire
381
1126
  * @param {string} filename - Source filename.
382
1127
  * @param {string} source - Complete source.
383
1128
  * @param {boolean} canonicalZeroRequired - Whether generated scalar output may contain signed zero.
1129
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
384
1130
  * @param {import("../semantic/types.js").SourceLocation} [location] - Location for a statement-array block.
385
1131
  * @returns {import("../semantic/types.js").Block} Semantic block.
386
1132
  */
387
- function convertBlock(input, language, filename, source, canonicalZeroRequired, location) {
1133
+ function convertBlock(input, language, filename, source, canonicalZeroRequired, context, location) {
388
1134
  const statements = Array.isArray(input) ? input : input.body
389
1135
  const directives = Array.isArray(input) ? [] : input.directives
390
1136
  const blockLocation = location ?? nodeLocation(/** @type {import("@babel/types").BlockStatement} */ (input), filename, source)
1137
+ const lexicalValueNames = new Set(context.lexicalValueNames)
1138
+
1139
+ for (const statement of statements) {
1140
+ if (statement.type != "VariableDeclaration") continue
1141
+ for (const declarator of statement.declarations) {
1142
+ if (declarator.id.type == "Identifier") lexicalValueNames.add(declarator.id.name)
1143
+ }
1144
+ }
1145
+ const blockContext = {...context, lexicalValueNames}
391
1146
 
392
1147
  if (directives.length > 0) {
393
1148
  return unsupportedSyntax(language, "directive", nodeLocation(directives[0], filename, source))
@@ -396,7 +1151,7 @@ function convertBlock(input, language, filename, source, canonicalZeroRequired,
396
1151
  return {
397
1152
  kind: "Block",
398
1153
  location: blockLocation,
399
- statements: statements.map((statement) => convertStatement(statement, language, filename, source, canonicalZeroRequired))
1154
+ statements: statements.map((statement) => convertStatement(statement, language, filename, source, canonicalZeroRequired, blockContext))
400
1155
  }
401
1156
  }
402
1157
 
@@ -405,38 +1160,126 @@ function convertBlock(input, language, filename, source, canonicalZeroRequired,
405
1160
  * @param {import("@babel/types").FunctionDeclaration} node - Babel function node.
406
1161
  * @param {string} filename - Source filename.
407
1162
  * @param {string} source - Complete source.
408
- * @returns {{parameters: Map<string, {location: import("../semantic/types.js").SourceLocation, sourceType: string}>, returnType: {location: import("../semantic/types.js").SourceLocation, sourceType: string} | undefined}} Parsed types.
1163
+ * @param {string} [ownerId] - Stable declaration identity for type-parameter IDs.
1164
+ * @returns {{parameters: Map<string, {location: import("../semantic/types.js").SourceLocation, sourceType: string}>, returnType: {location: import("../semantic/types.js").SourceLocation, sourceType: string} | undefined, typeParameters?: import("../semantic/types.js").TypeParameter[]}} Parsed types.
409
1165
  */
410
- function jsdocTypes(node, filename, source) {
1166
+ function jsdocTypes(node, filename, source, ownerId) {
411
1167
  const comment = node.leadingComments?.find((candidate) => candidate.type == "CommentBlock" && candidate.value.startsWith("*"))
412
1168
 
413
1169
  if (!comment) missingType("javascript", `Function '${node.id?.name ?? "anonymous"}'`, nodeLocation(node, filename, source))
414
1170
 
415
1171
  const commentBlock = /** @type {import("@babel/types").CommentBlock} */ (comment)
416
1172
  const block = parseComment(`/*${commentBlock.value}*/`)[0]
417
- const parameters = new Map(block.tags.filter((tag) => tag.tag == "param").map((tag) => [tag.name, {
1173
+ const parameterTags = block.tags.filter((tag) => tag.tag == "param")
1174
+ const returnTags = block.tags.filter((tag) => tag.tag == "returns" || tag.tag == "return")
1175
+ const typeParameters = jsdocTypeParameters(commentBlock, block.tags, ownerId, filename, source)
1176
+
1177
+ if (new Set(parameterTags.map((tag) => tag.name)).size != parameterTags.length) {
1178
+ const duplicate = parameterTags.find((tag, index) => parameterTags.findIndex((candidate) => candidate.name == tag.name) != index)
1179
+
1180
+ return unsupportedSyntax("javascript", "duplicate @param annotation", duplicate
1181
+ ? commentTagLocation(commentBlock, duplicate, "name", filename, source)
1182
+ : nodeLocation(node, filename, source))
1183
+ }
1184
+ if (returnTags.length > 1) {
1185
+ return unsupportedSyntax("javascript", "duplicate @return annotation",
1186
+ commentTagLocation(commentBlock, returnTags[1], "type", filename, source))
1187
+ }
1188
+ const parameters = new Map(parameterTags.map((tag) => [tag.name, {
418
1189
  location: commentTagLocation(commentBlock, tag, "type", filename, source),
419
1190
  sourceType: tag.type
420
1191
  }]))
421
- const returnTag = block.tags.find((tag) => tag.tag == "returns" || tag.tag == "return")
1192
+ const returnTag = returnTags[0]
422
1193
 
423
1194
  return {
424
1195
  parameters,
425
- returnType: returnTag ? {location: commentTagLocation(commentBlock, returnTag, "type", filename, source), sourceType: returnTag.type} : undefined
1196
+ returnType: returnTag ? {location: commentTagLocation(commentBlock, returnTag, "type", filename, source), sourceType: returnTag.type} : undefined,
1197
+ ...(typeParameters ? {typeParameters} : {})
426
1198
  }
427
1199
  }
428
1200
 
1201
+ /**
1202
+ * Reads exact unbounded invariant `@template Name` declarations.
1203
+ * @param {import("@babel/types").CommentBlock} comment - Parser-owned JSDoc block.
1204
+ * @param {import("comment-parser").Spec[]} tags - Parsed tags.
1205
+ * @param {string | undefined} ownerId - Stable declaration identity.
1206
+ * @param {string} filename - Source filename.
1207
+ * @param {string} source - Complete source.
1208
+ * @returns {import("../semantic/types.js").TypeParameter[] | undefined} Ordered parameters when present.
1209
+ */
1210
+ function jsdocTypeParameters(comment, tags, ownerId, filename, source) {
1211
+ const malformed = tags.find((tag) => tag.tag.startsWith("template") && tag.tag != "template")
1212
+
1213
+ if (malformed) return unsupportedSyntax("javascript", "unsupported type parameter variance", commentTagLocation(comment, malformed, "name", filename, source))
1214
+ const templateTags = tags.filter((tag) => tag.tag == "template")
1215
+
1216
+ if (templateTags.length == 0) return undefined
1217
+ if (!ownerId) return unsupportedSyntax("javascript", "type parameter outside a declaration",
1218
+ nodeLocation(/** @type {import("@babel/types").Node} */ (/** @type {unknown} */ (comment)), filename, source))
1219
+
1220
+ return templateTags.map((tag, index) => {
1221
+ const location = commentTagLocation(comment, tag, "name", filename, source)
1222
+
1223
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(tag.name) || tag.type || tag.description) {
1224
+ return unsupportedSyntax("javascript", "bounded, defaulted, or malformed type parameter", location)
1225
+ }
1226
+
1227
+ return withParserRanges({id: `${ownerId}:type:${index}`, kind: /** @type {const} */ ("TypeParameter"), location, name: tag.name}, {name: location})
1228
+ })
1229
+ }
1230
+
1231
+ /**
1232
+ * Reads an optional class-level JSDoc template block.
1233
+ * @param {import("@babel/types").ClassDeclaration} node - Class declaration.
1234
+ * @param {string} ownerId - Record identity.
1235
+ * @param {string} filename - Source filename.
1236
+ * @param {string} source - Complete source.
1237
+ * @returns {import("../semantic/types.js").TypeParameter[] | undefined} Ordered parameters.
1238
+ */
1239
+ function classJsdocTypeParameters(node, ownerId, filename, source) {
1240
+ const comment = /** @type {import("@babel/types").CommentBlock | undefined} */ (
1241
+ node.leadingComments?.find((candidate) => candidate.type == "CommentBlock" && candidate.value.startsWith("*")))
1242
+
1243
+ if (!comment) return undefined
1244
+ const block = parseComment(`/*${comment.value}*/`)[0]
1245
+
1246
+ return jsdocTypeParameters(comment, block.tags, ownerId, filename, source)
1247
+ }
1248
+
429
1249
  /**
430
1250
  * Requires a supported JavaScript JSDoc scalar type spelling.
431
1251
  * @param {string | undefined} sourceType - Source-language type.
432
1252
  * @param {"javascript" | "typescript"} language - Frontend language.
433
1253
  * @param {string} subject - Typed subject.
434
1254
  * @param {import("../semantic/types.js").SourceLocation} location - Source location.
1255
+ * @param {string} source - Complete parser input.
435
1256
  * @param {import("../semantic/types.js").SourceLocation} [typeLocation] - Exact type token location.
436
- * @returns {import("../semantic/types.js").TypeReference} Semantic type.
1257
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1258
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1259
+ * @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
437
1260
  */
438
- function convertType(sourceType, language, subject, location, typeLocation = location) {
439
- return requireSourceScalarType(language, sourceType, subject, location, typeLocation)
1261
+ function convertType(sourceType, language, subject, location, source, typeLocation = location, recordNames = new Map(), typeParameters = new Map()) {
1262
+ if (language != "javascript") return requireSourceScalarType(language, sourceType, subject, location, typeLocation)
1263
+
1264
+ return documentedValueType({language, location: typeLocation, ownerLocation: location, records: recordNames, source, sourceType, subject, typeParameters})
1265
+ }
1266
+
1267
+ /**
1268
+ * Converts one exact JavaScript function return annotation.
1269
+ * @param {string | undefined} sourceType - JSDoc type spelling.
1270
+ * @param {"javascript" | "typescript"} language - Frontend language.
1271
+ * @param {string} subject - Typed function return.
1272
+ * @param {import("../semantic/types.js").SourceLocation} location - Function location.
1273
+ * @param {string} source - Complete parser input.
1274
+ * @param {import("../semantic/types.js").SourceLocation} [typeLocation] - Exact annotation location.
1275
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1276
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1277
+ * @returns {import("../semantic/types.js").SemanticFunctionReturnType} Semantic return type.
1278
+ */
1279
+ function convertReturnType(sourceType, language, subject, location, source, typeLocation = location, recordNames = new Map(), typeParameters = new Map()) {
1280
+ if (sourceType == "void") return requireSourceReturnType(language, sourceType, subject, location, typeLocation)
1281
+
1282
+ return convertType(sourceType, language, subject, location, source, typeLocation, recordNames, typeParameters)
440
1283
  }
441
1284
 
442
1285
  /**
@@ -446,54 +1289,200 @@ function convertType(sourceType, language, subject, location, typeLocation = loc
446
1289
  * @param {import("../semantic/types.js").SourceLocation} ownerLocation - Owning declaration location.
447
1290
  * @param {string} filename - Source filename.
448
1291
  * @param {string} source - Complete source.
449
- * @returns {import("../semantic/types.js").TypeReference} Semantic scalar type.
1292
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1293
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1294
+ * @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
450
1295
  */
451
- function convertTypeScriptType(annotation, subject, ownerLocation, filename, source) {
1296
+ function convertTypeScriptType(annotation, subject, ownerLocation, filename, source, recordNames = new Map(), typeParameters = new Map()) {
452
1297
  if (!annotation) return missingType("typescript", subject, ownerLocation)
453
1298
  if (annotation.type != "TSTypeAnnotation") {
454
1299
  return unsupportedSyntax("typescript", "unsupported scalar type", nodeLocation(annotation, filename, source))
455
1300
  }
456
1301
 
457
- const typeNode = annotation.typeAnnotation
458
- const sourceType = typeNode.type == "TSNumberKeyword"
459
- ? "number"
460
- : typeNode.type == "TSBooleanKeyword"
461
- ? "boolean"
462
- : typeNode.type == "TSStringKeyword" ? "string" : undefined
463
-
464
- if (!sourceType) {
465
- return unsupportedSyntax("typescript", "unsupported scalar type", nodeLocation(typeNode, filename, source))
466
- }
467
-
468
- return requireSourceScalarType("typescript", sourceType, subject, ownerLocation, nodeLocation(typeNode, filename, source))
1302
+ return convertTypeScriptValueTypeNode(annotation.typeAnnotation, subject, ownerLocation, filename, source, recordNames, typeParameters)
469
1303
  }
470
1304
 
471
1305
  /**
472
- * Converts a supported function declaration.
473
- * @param {import("@babel/types").FunctionDeclaration} node - Babel function.
474
- * @param {"javascript" | "typescript"} language - Frontend language.
1306
+ * Converts one bounded recursive TypeScript value-type tree.
1307
+ * @param {import("@babel/types").TSType} typeNode - Parser-owned type node.
1308
+ * @param {string} subject - Typed subject.
1309
+ * @param {import("../semantic/types.js").SourceLocation} ownerLocation - Owning declaration.
475
1310
  * @param {string} filename - Source filename.
476
1311
  * @param {string} source - Complete source.
477
- * @param {boolean} canonicalZeroRequired - Whether generated scalar output may contain signed zero.
478
- * @returns {import("../semantic/types.js").FunctionDeclaration} Semantic function.
1312
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1313
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1314
+ * @returns {import("../semantic/types.js").SemanticValueType} Semantic value type.
479
1315
  */
480
- function convertFunction(node, language, filename, source, canonicalZeroRequired) {
481
- const location = nodeLocation(node, filename, source)
1316
+ function convertTypeScriptValueTypeNode(typeNode, subject, ownerLocation, filename, source, recordNames = new Map(), typeParameters = new Map()) {
1317
+ const sourceType = typeNode.type == "TSNumberKeyword" ? "number" :
1318
+ typeNode.type == "TSBooleanKeyword" ? "boolean" : typeNode.type == "TSStringKeyword" ? "string" : undefined
482
1319
 
483
- if (!node.id) return unsupportedSyntax(language, "anonymous function", location)
484
- if (node.async) return unsupportedSyntax(language, "async function", location)
485
- if (node.generator) return unsupportedSyntax(language, "generator function", location)
1320
+ if (sourceType) {
1321
+ return requireSourceScalarType("typescript", sourceType, subject, ownerLocation, nodeLocation(typeNode, filename, source))
1322
+ }
1323
+ if (typeNode.type == "TSTypeReference" && typeNode.typeName.type == "Identifier" && !typeNode.typeParameters &&
1324
+ typeParameters.has(typeNode.typeName.name)) {
1325
+ const parameter = /** @type {import("../semantic/types.js").TypeParameter} */ (typeParameters.get(typeNode.typeName.name))
486
1326
 
487
- const documentedTypes = language == "javascript" ? jsdocTypes(node, filename, source) : undefined
488
- const parameters = node.params.map((parameter) => {
489
- const parameterLocation = nodeLocation(parameter, filename, source)
1327
+ return typeVariable(/** @type {string} */ (parameter.id), nodeLocation(typeNode, filename, source))
1328
+ }
1329
+ if (typeNode.type == "TSTypeReference" && typeNode.typeName.type == "Identifier" &&
1330
+ recordNames.has(typeNode.typeName.name)) {
1331
+ const declaration = /** @type {import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration} */ (recordNames.get(typeNode.typeName.name))
1332
+
1333
+ if (declaration.kind == "EffectResourceDeclaration") {
1334
+ if (typeNode.typeParameters) return unsupportedSyntax("typescript", "generic owned resource type", nodeLocation(typeNode, filename, source))
1335
+ return ownedResourceType(declaration.id, nodeLocation(typeNode.typeName, filename, source))
1336
+ }
1337
+ if (declaration.kind == "ClassDeclaration") {
1338
+ if (typeNode.typeParameters) return unsupportedSyntax("typescript", "generic reference class type", nodeLocation(typeNode, filename, source))
1339
+ const location = nodeLocation(typeNode.typeName, filename, source)
1340
+ return declaration.ownership?.kind == "ownedResource"
1341
+ ? ownedReferenceType(/** @type {string} */ (declaration.id), location)
1342
+ : referenceType(/** @type {string} */ (declaration.id), location)
1343
+ }
1344
+ const arguments_ = typeNode.typeParameters?.params.map((parameter) =>
1345
+ convertTypeScriptValueTypeNode(parameter, subject, ownerLocation, filename, source, recordNames, typeParameters))
1346
+
1347
+ return recordType(/** @type {string} */ (declaration.id), nodeLocation(typeNode.typeName, filename, source), arguments_)
1348
+ }
1349
+ if (typeNode.type == "TSUnionType") {
1350
+ if (typeNode.types.length != 2 || typeNode.types[1].type != "TSNullKeyword") {
1351
+ return unsupportedSyntax(
1352
+ "typescript",
1353
+ typeNode.types.some((member) => member.type == "TSNullKeyword")
1354
+ ? "arbitrary union type"
1355
+ : "unsupported collection or scalar type",
1356
+ nodeLocation(typeNode, filename, source)
1357
+ )
1358
+ }
1359
+ const valueNode = typeNode.types[0]
1360
+
1361
+ return optionalType(
1362
+ convertTypeScriptValueTypeNode(valueNode, subject, ownerLocation, filename, source, recordNames, typeParameters),
1363
+ nodeLocation(typeNode, filename, source),
1364
+ nodeLocation(valueNode, filename, source)
1365
+ )
1366
+ }
1367
+ if (typeNode.type == "TSTypeOperator" && typeNode.operator == "readonly" && typeNode.typeAnnotation.type == "TSArrayType") {
1368
+ const elementNode = typeNode.typeAnnotation.elementType
1369
+
1370
+ return listType(
1371
+ convertTypeScriptValueTypeNode(elementNode, subject, ownerLocation, filename, source, recordNames, typeParameters),
1372
+ nodeLocation(typeNode, filename, source),
1373
+ nodeLocation(elementNode, filename, source)
1374
+ )
1375
+ }
1376
+ if (typeNode.type == "TSTypeReference" && typeNode.typeName.type == "Identifier" &&
1377
+ ["ReadonlyArray", "ReadonlyMap"].includes(typeNode.typeName.name)) {
1378
+ const parameters = typeNode.typeParameters?.params ?? []
1379
+
1380
+ if (typeNode.typeName.name == "ReadonlyArray" && parameters.length == 1) {
1381
+ return listType(
1382
+ convertTypeScriptValueTypeNode(parameters[0], subject, ownerLocation, filename, source, recordNames, typeParameters),
1383
+ nodeLocation(typeNode, filename, source),
1384
+ nodeLocation(parameters[0], filename, source)
1385
+ )
1386
+ }
1387
+ if (typeNode.typeName.name == "ReadonlyMap" && parameters.length == 2) {
1388
+ const keyType = convertTypeScriptValueTypeNode(parameters[0], subject, ownerLocation, filename, source, recordNames, typeParameters)
1389
+
1390
+ if (keyType.kind != "TypeReference") {
1391
+ return unsupportedSyntax("typescript", "map key type other than string", nodeLocation(parameters[0], filename, source))
1392
+ }
1393
+ return mapType(
1394
+ keyType,
1395
+ convertTypeScriptValueTypeNode(parameters[1], subject, ownerLocation, filename, source, recordNames, typeParameters),
1396
+ nodeLocation(typeNode, filename, source),
1397
+ nodeLocation(parameters[0], filename, source),
1398
+ nodeLocation(parameters[1], filename, source)
1399
+ )
1400
+ }
1401
+ }
1402
+
1403
+ return unsupportedSyntax("typescript", "unsupported collection or scalar type", nodeLocation(typeNode, filename, source))
1404
+ }
1405
+
1406
+ /**
1407
+ * Converts one exact TypeScript function return annotation.
1408
+ * @param {import("@babel/types").TypeAnnotation | import("@babel/types").TSTypeAnnotation | import("@babel/types").Noop | null | undefined} annotation - Return annotation.
1409
+ * @param {string} subject - Typed function return.
1410
+ * @param {import("../semantic/types.js").SourceLocation} ownerLocation - Function location.
1411
+ * @param {string} filename - Source filename.
1412
+ * @param {string} source - Complete source.
1413
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
1414
+ * @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
1415
+ * @returns {import("../semantic/types.js").SemanticFunctionReturnType} Semantic return type.
1416
+ */
1417
+ function convertTypeScriptReturnType(annotation, subject, ownerLocation, filename, source, recordNames = new Map(), typeParameters = new Map()) {
1418
+ if (!annotation) return requireSourceReturnType("typescript", undefined, subject, ownerLocation)
1419
+ if (annotation.type != "TSTypeAnnotation") {
1420
+ return unsupportedSyntax("typescript", "unsupported return type", nodeLocation(annotation, filename, source))
1421
+ }
1422
+ const typeNode = annotation.typeAnnotation
1423
+
1424
+ if (typeNode.type == "TSVoidKeyword") {
1425
+ return requireSourceReturnType("typescript", "void", subject, ownerLocation, nodeLocation(typeNode, filename, source))
1426
+ }
1427
+
1428
+ return convertTypeScriptValueTypeNode(typeNode, subject, ownerLocation, filename, source, recordNames, typeParameters)
1429
+ }
1430
+
1431
+ /**
1432
+ * Converts one native unbounded invariant TypeScript parameter list.
1433
+ * @param {import("@babel/types").TSTypeParameterDeclaration | null | undefined} declaration - Parser-owned list.
1434
+ * @param {string} ownerId - Stable owning declaration identity.
1435
+ * @param {string} filename - Source filename.
1436
+ * @param {string} source - Complete source.
1437
+ * @returns {import("../semantic/types.js").TypeParameter[] | undefined} Semantic parameters.
1438
+ */
1439
+ function typeScriptTypeParameters(declaration, ownerId, filename, source) {
1440
+ if (!declaration) return undefined
1441
+
1442
+ return declaration.params.map((parameter, index) => {
1443
+ const location = nodeLocation(parameter, filename, source)
1444
+
1445
+ if (parameter.constraint || parameter.default || Reflect.get(parameter, "in") || Reflect.get(parameter, "out") ||
1446
+ Reflect.get(parameter, "const") || typeof parameter.name != "string") {
1447
+ return unsupportedSyntax("typescript", "bounded, defaulted, variant, or const type parameter", location)
1448
+ }
1449
+
1450
+ return withParserRanges({id: `${ownerId}:type:${index}`, kind: /** @type {const} */ ("TypeParameter"), location, name: parameter.name}, {name: location})
1451
+ })
1452
+ }
1453
+
1454
+ /**
1455
+ * Converts a supported function signature before any body expressions.
1456
+ * @param {import("@babel/types").FunctionDeclaration} node - Babel function.
1457
+ * @param {"javascript" | "typescript"} language - Frontend language.
1458
+ * @param {string} filename - Source filename.
1459
+ * @param {string} source - Complete source.
1460
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
1461
+ * @param {string} ownerId - Stable function identity.
1462
+ * @returns {JavaScriptFunctionSignature} Semantic function signature.
1463
+ */
1464
+ function convertFunctionSignature(node, language, filename, source, recordNames, ownerId) {
1465
+ const location = nodeLocation(node, filename, source)
1466
+
1467
+ if (!node.id) return unsupportedSyntax(language, "anonymous function", location)
1468
+ if (node.async) return unsupportedSyntax(language, "async function", location)
1469
+ if (node.generator) return unsupportedSyntax(language, "generator function", location)
1470
+ const nativeTypeParameters = language == "typescript"
1471
+ ? typeScriptTypeParameters(/** @type {import("@babel/types").TSTypeParameterDeclaration | null | undefined} */ (node.typeParameters), ownerId, filename, source)
1472
+ : node.typeParameters ? unsupportedSyntax(language, "native generic function", nodeLocation(node.typeParameters, filename, source)) : undefined
1473
+ const documentedTypes = language == "javascript" ? jsdocTypes(node, filename, source, ownerId) : undefined
1474
+ const typeParameters_ = nativeTypeParameters ?? documentedTypes?.typeParameters
1475
+ const typeParameterNames = new Map((typeParameters_ ?? []).map((parameter) => [parameter.name, parameter]))
1476
+ const parameters = node.params.map((parameter) => {
1477
+ const parameterLocation = nodeLocation(parameter, filename, source)
490
1478
 
491
1479
  if (parameter.type != "Identifier") return unsupportedSyntax(language, parameter.type, parameterLocation)
1480
+ if (parameter.optional) return unsupportedSyntax(language, "optional parameter", parameterLocation)
492
1481
 
493
1482
  const documentedType = documentedTypes?.parameters.get(parameter.name)
494
1483
  const type = language == "javascript"
495
- ? convertType(documentedType?.sourceType, language, `Parameter '${parameter.name}'`, parameterLocation, documentedType?.location)
496
- : convertTypeScriptType(parameter.typeAnnotation, `Parameter '${parameter.name}'`, parameterLocation, filename, source)
1484
+ ? convertType(documentedType?.sourceType, language, `Parameter '${parameter.name}'`, parameterLocation, source, documentedType?.location, recordNames, typeParameterNames)
1485
+ : convertTypeScriptType(parameter.typeAnnotation, `Parameter '${parameter.name}'`, parameterLocation, filename, source, recordNames, typeParameterNames)
497
1486
 
498
1487
  return withParserRanges({
499
1488
  kind: /** @type {const} */ ("Parameter"),
@@ -502,20 +1491,500 @@ function convertFunction(node, language, filename, source, canonicalZeroRequired
502
1491
  type
503
1492
  }, {name: identifierLocation(parameter, filename, source)})
504
1493
  })
1494
+ const extraDocumentedParameter = documentedTypes && [...documentedTypes.parameters.keys()]
1495
+ .find((name) => !node.params.some((parameter) => parameter.type == "Identifier" && parameter.name == name))
1496
+
1497
+ if (extraDocumentedParameter) return unsupportedSyntax(language, `annotation for unknown parameter '${extraDocumentedParameter}'`, location)
505
1498
  const returnAnnotation = node.returnType
506
1499
  const returnType = language == "javascript"
507
- ? convertType(documentedTypes?.returnType?.sourceType, language, `Function '${node.id.name}' return`, location, documentedTypes?.returnType?.location)
508
- : convertTypeScriptType(returnAnnotation, `Function '${node.id.name}' return`, location, filename, source)
509
- const body = convertBlock(node.body, language, filename, source, canonicalZeroRequired)
1500
+ ? convertReturnType(documentedTypes?.returnType?.sourceType, language, `Function '${node.id.name}' return`, location, source, documentedTypes?.returnType?.location, recordNames, typeParameterNames)
1501
+ : convertTypeScriptReturnType(returnAnnotation, `Function '${node.id.name}' return`, location, filename, source, recordNames, typeParameterNames)
1502
+ return {
1503
+ location,
1504
+ name: node.id.name,
1505
+ nameLocation: identifierLocation(node.id, filename, source),
1506
+ parameters,
1507
+ returnType,
1508
+ ...(typeParameters_ ? {typeParameters: typeParameters_} : {})
1509
+ }
1510
+ }
1511
+
1512
+ /**
1513
+ * Converts a supported function body using all already-proved signatures.
1514
+ * @param {import("@babel/types").FunctionDeclaration} node - Babel function.
1515
+ * @param {JavaScriptFunctionSignature} signature - Converted signature.
1516
+ * @param {Map<string, JavaScriptFunctionSignature>} functions - Module signatures.
1517
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by name.
1518
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration>} valueRecordNames - Nominal declarations available in value positions.
1519
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Record declarations by identity.
1520
+ * @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
1521
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
1522
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Error declarations by identity.
1523
+ * @param {"javascript" | "typescript"} language - Frontend language.
1524
+ * @param {string} filename - Source filename.
1525
+ * @param {string} source - Complete source.
1526
+ * @param {boolean} canonicalZeroRequired - Whether generated scalar output may contain signed zero.
1527
+ * @returns {import("../semantic/types.js").FunctionDeclaration} Semantic function.
1528
+ */
1529
+ function convertFunction(node, signature, functions, recordNames, valueRecordNames, records, classes, errorNames, errors, language, filename, source, canonicalZeroRequired) {
1530
+ const context = {
1531
+ bindings: new Map(signature.parameters.map((parameter) => [parameter.name, parameter.type])),
1532
+ classes,
1533
+ errorNames,
1534
+ errors,
1535
+ functions,
1536
+ orderedMapDeclarations: orderedMapDeclarationsFor(node.body),
1537
+ recordNames,
1538
+ records,
1539
+ returnType: signature.returnType,
1540
+ typeParameters: new Map((signature.typeParameters ?? []).map((parameter) => [parameter.name, parameter])),
1541
+ valueRecordNames
1542
+ }
1543
+ const body = convertBlock(node.body, language, filename, source, canonicalZeroRequired, context)
510
1544
 
511
1545
  return withParserRanges({
512
1546
  body,
513
1547
  kind: "FunctionDeclaration",
514
- location,
515
- name: node.id.name,
516
- parameters,
517
- returnType
518
- }, {name: identifierLocation(node.id, filename, source)})
1548
+ location: signature.location,
1549
+ name: signature.name,
1550
+ parameters: signature.parameters,
1551
+ returnType: signature.returnType,
1552
+ ...(signature.typeParameters ? {typeParameters: signature.typeParameters} : {})
1553
+ }, {name: signature.nameLocation})
1554
+ }
1555
+
1556
+ /**
1557
+ * Resolves parser-owned pair loops to exact declarations inside one lexical owner.
1558
+ * Ambiguous same-name declarations produce no evidence and fail during loop conversion.
1559
+ * @param {unknown} value - Function body or entry-point subtree.
1560
+ * @returns {Set<object>} Exact pair-iterated parser declarations.
1561
+ */
1562
+ function orderedMapDeclarationsFor(value) {
1563
+ /** @type {{declaration: object, name: string, offset: number}[]} */
1564
+ const declarations = []
1565
+ /** @type {{name: string, offset: number}[]} */
1566
+ const receivers = []
1567
+ const seen = new WeakSet()
1568
+ /**
1569
+ * Visits one parser candidate.
1570
+ * @param {unknown} candidate - Parser candidate.
1571
+ */
1572
+ const visit = (candidate) => {
1573
+ if (!candidate || typeof candidate != "object" || seen.has(candidate)) return
1574
+ seen.add(candidate)
1575
+ const type = Reflect.get(candidate, "type")
1576
+
1577
+ if (type == "FunctionDeclaration" || type == "ClassDeclaration") return
1578
+ if (type == "VariableDeclarator") {
1579
+ const identifier = Reflect.get(candidate, "id")
1580
+
1581
+ if (Reflect.get(identifier, "type") == "Identifier") declarations.push({
1582
+ declaration: candidate,
1583
+ name: Reflect.get(identifier, "name"),
1584
+ offset: Reflect.get(candidate, "start")
1585
+ })
1586
+ } else if (type == "ForOfStatement") {
1587
+ const left = Reflect.get(candidate, "left")
1588
+ const right = Reflect.get(candidate, "right")
1589
+ const loopDeclarations = left && typeof left == "object" ? Reflect.get(left, "declarations") : undefined
1590
+ const pattern = Array.isArray(loopDeclarations) && loopDeclarations.length == 1 ? Reflect.get(loopDeclarations[0], "id") : undefined
1591
+ const elements = pattern && typeof pattern == "object" ? Reflect.get(pattern, "elements") : undefined
1592
+
1593
+ if (Reflect.get(left, "kind") == "const" && Reflect.get(pattern, "type") == "ArrayPattern" &&
1594
+ Array.isArray(elements) && elements.length == 2 && elements.every((element) => Reflect.get(element, "type") == "Identifier") &&
1595
+ Reflect.get(right, "type") == "Identifier") receivers.push({
1596
+ name: Reflect.get(right, "name"),
1597
+ offset: Reflect.get(candidate, "start")
1598
+ })
1599
+ }
1600
+ for (const [key, child] of Object.entries(candidate)) {
1601
+ if (["extra", "innerComments", "leadingComments", "loc", "trailingComments"].includes(key)) continue
1602
+ if (Array.isArray(child)) {
1603
+ for (const item of child) visit(item)
1604
+ } else visit(child)
1605
+ }
1606
+ }
1607
+
1608
+ visit(value)
1609
+ const selected = new Set()
1610
+ for (const receiver of receivers) {
1611
+ const matches = declarations.filter(({name, offset}) => name == receiver.name && offset < receiver.offset)
1612
+
1613
+ if (matches.length == 1) selected.add(matches[0].declaration)
1614
+ }
1615
+
1616
+ return selected
1617
+ }
1618
+
1619
+ /**
1620
+ * Converts one exact empty native Error subclass.
1621
+ * @param {import("@babel/types").ClassDeclaration} node - Babel class declaration.
1622
+ * @param {import("../semantic/types.js").ErrorDeclaration} declaration - Predeclared semantic error.
1623
+ * @param {"javascript" | "typescript"} language - Frontend language.
1624
+ * @param {string} filename - Source filename.
1625
+ * @param {string} source - Complete source.
1626
+ * @returns {import("../semantic/types.js").ErrorDeclaration} Semantic error declaration.
1627
+ */
1628
+ function convertJavaScriptError(node, declaration, language, filename, source) {
1629
+ const location = nodeLocation(node, filename, source)
1630
+
1631
+ if (!node.id || node.superClass?.type != "Identifier" || node.superClass.name != "Error" ||
1632
+ node.body.body.length != 0 || node.decorators?.length || node.typeParameters || node.superTypeParameters ||
1633
+ Reflect.get(node, "abstract") || Reflect.get(node, "declare") || Reflect.get(node, "implements")?.length) {
1634
+ return unsupportedSyntax(language, "noncanonical typed error declaration", location)
1635
+ }
1636
+
1637
+ return withParserRanges(declaration, {
1638
+ name: identifierLocation(node.id, filename, source),
1639
+ type: identifierLocation(node.superClass, filename, source)
1640
+ })
1641
+ }
1642
+
1643
+ /**
1644
+ * Predeclares the bounded native private-field reference-class profile and all callable signatures.
1645
+ * @param {import("@babel/types").ClassDeclaration} node - Parser class.
1646
+ * @param {import("../semantic/types.js").ClassDeclaration} declaration - Predeclared class identity.
1647
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Nominal types by name.
1648
+ * @param {"javascript" | "typescript"} language - Source language.
1649
+ * @param {string} filename - Source filename.
1650
+ * @param {string} source - Complete source.
1651
+ * @returns {import("../semantic/types.js").ClassDeclaration} Semantic class.
1652
+ */
1653
+ function predeclareReferenceClass(node, declaration, nominalNames, language, filename, source) {
1654
+ const location = nodeLocation(node, filename, source)
1655
+
1656
+ if (!node.id || node.superClass || node.decorators?.length || node.typeParameters || node.superTypeParameters ||
1657
+ Reflect.get(node, "abstract") || Reflect.get(node, "declare") || Reflect.get(node, "implements")?.length) {
1658
+ return unsupportedSyntax(language, "reference class inheritance, generics, decoration, or modifier", location)
1659
+ }
1660
+ const constructorNodes = /** @type {import("@babel/types").ClassMethod[]} */ (
1661
+ node.body.body.filter((member) => member.type == "ClassMethod" && member.kind == "constructor"))
1662
+ const fieldNodes = node.body.body.filter((member) => member.type == "ClassProperty" || member.type == "ClassPrivateProperty")
1663
+ const methodNodes = /** @type {import("@babel/types").ClassMethod[]} */ (
1664
+ node.body.body.filter((member) => member.type == "ClassMethod" && member.kind == "method"))
1665
+
1666
+ if (constructorNodes.length != 1 || fieldNodes.length == 0 ||
1667
+ fieldNodes.length + methodNodes.length + constructorNodes.length != node.body.body.length) {
1668
+ return unsupportedSyntax(language, "reference class requires private fields, one constructor, and ordinary methods", location)
1669
+ }
1670
+ declaration.fields = fieldNodes.map((member, index) => {
1671
+ const fieldLocation = nodeLocation(member, filename, source)
1672
+ let name
1673
+ let nameLocation
1674
+ let type
1675
+
1676
+ if (language == "typescript") {
1677
+ if (member.type != "ClassProperty" || member.key.type != "Identifier" || member.computed || member.static ||
1678
+ member.accessibility != "private" || member.readonly || member.value || member.optional || member.decorators?.length ||
1679
+ Reflect.get(member, "abstract") || Reflect.get(member, "declare")) {
1680
+ return unsupportedSyntax(language, "noncanonical private instance field", fieldLocation)
1681
+ }
1682
+ name = member.key.name
1683
+ nameLocation = identifierLocation(member.key, filename, source)
1684
+ type = convertTypeScriptType(member.typeAnnotation, `Private field '${name}'`, fieldLocation, filename, source, nominalNames)
1685
+ } else {
1686
+ if (member.type != "ClassPrivateProperty" || member.key.type != "PrivateName" || member.static || member.value ||
1687
+ member.decorators?.length) {
1688
+ return unsupportedSyntax(language, "noncanonical private instance field", fieldLocation)
1689
+ }
1690
+ name = member.key.id.name
1691
+ nameLocation = identifierLocation(member.key.id, filename, source)
1692
+ const comment = member.leadingComments?.at(-1)
1693
+ const gap = comment ? source.slice(comment.end ?? 0, member.start ?? 0) : ""
1694
+ const block = comment?.type == "CommentBlock" && comment.value.startsWith("*") && /^\s*$/u.test(gap)
1695
+ ? parseComment(`/*${comment.value}*/`)[0] : undefined
1696
+ const tags = block?.tags.filter((tag) => tag.tag == "type") ?? []
1697
+
1698
+ if (!comment || tags.length != 1 || tags[0].name || tags[0].description) {
1699
+ return missingType(language, `Private field '${name}'`, fieldLocation)
1700
+ }
1701
+ type = convertType(tags[0].type, language, `Private field '${name}'`, fieldLocation, source,
1702
+ commentTagLocation(/** @type {import("@babel/types").CommentBlock} */ (comment), tags[0], "type", filename, source), nominalNames)
1703
+ }
1704
+
1705
+ return withParserRanges({id: `${declaration.id}:field:${index}`, kind: /** @type {const} */ ("PrivateField"),
1706
+ location: fieldLocation, name, type}, {name: nameLocation})
1707
+ })
1708
+ const ownedFields = declaration.fields.filter(({type}) => type.kind == "OwnedResourceType")
1709
+
1710
+ if (ownedFields.length == 1) {
1711
+ const ownedField = /** @type {import("../semantic/types.js").PrivateField & {type: import("../semantic/types.js").OwnedResourceType}} */ (ownedFields[0])
1712
+
1713
+ declaration.ownership = {fieldId: /** @type {string} */ (ownedField.id), kind: "ownedResource", resourceId: ownedField.type.resourceId}
1714
+ }
1715
+
1716
+ const constructorNode = /** @type {import("@babel/types").ClassMethod} */ (constructorNodes[0])
1717
+ const constructorSignature = convertClassCallableSignature(constructorNode, declaration, nominalNames, language,
1718
+ filename, source, true)
1719
+ declaration.constructor = withParserRanges({body: /** @type {import("../semantic/types.js").Block} */ ({}),
1720
+ id: `${declaration.id}:constructor`, kind: /** @type {const} */ ("ConstructorDeclaration"), location: constructorSignature.location,
1721
+ parameters: constructorSignature.parameters}, {constructor: constructorSignature.nameLocation})
1722
+ declaration.methods = methodNodes.map((methodNode, index) => {
1723
+ const signature = convertClassCallableSignature(methodNode, declaration, nominalNames, language, filename, source, false)
1724
+
1725
+ return withParserRanges({body: /** @type {import("../semantic/types.js").Block} */ ({}), id: `${declaration.id}:method:${index}`,
1726
+ kind: /** @type {const} */ ("MethodDeclaration"), location: signature.location, name: signature.name,
1727
+ parameters: signature.parameters, returnType: /** @type {import("../semantic/types.js").SemanticFunctionReturnType} */ (signature.returnType)},
1728
+ {name: signature.nameLocation})
1729
+ })
1730
+
1731
+ return withParserRanges(declaration, {name: identifierLocation(node.id, filename, source)})
1732
+ }
1733
+
1734
+ /**
1735
+ * Converts one reference class body after every class signature is complete.
1736
+ * @param {import("@babel/types").ClassDeclaration} node - Parser class.
1737
+ * @param {import("../semantic/types.js").ClassDeclaration} declaration - Complete predeclared class signature.
1738
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Nominal types by name.
1739
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration>} valueNames - Nominal constructors by name.
1740
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Records by identity.
1741
+ * @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Classes by identity.
1742
+ * @param {Map<string, JavaScriptFunctionSignature>} functions - Top-level signatures.
1743
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Errors by name.
1744
+ * @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Errors by identity.
1745
+ * @param {"javascript" | "typescript"} language - Source language.
1746
+ * @param {string} filename - Source filename.
1747
+ * @param {string} source - Complete source.
1748
+ * @param {boolean} canonicalZeroRequired - Whether scalar output canonicalizes zero.
1749
+ * @returns {import("../semantic/types.js").ClassDeclaration} Completed semantic class.
1750
+ */
1751
+ function convertReferenceClassBodies(node, declaration, nominalNames, valueNames, records, classes, functions, errorNames,
1752
+ errors, language, filename, source, canonicalZeroRequired) {
1753
+ const constructorNode = /** @type {import("@babel/types").ClassMethod} */ (
1754
+ node.body.body.find((member) => member.type == "ClassMethod" && member.kind == "constructor"))
1755
+ const methodNodes = /** @type {import("@babel/types").ClassMethod[]} */ (
1756
+ node.body.body.filter((member) => member.type == "ClassMethod" && member.kind == "method"))
1757
+ const baseContext = {bindings: new Map(), classes, currentClass: declaration, errorNames, errors, functions,
1758
+ recordNames: nominalNames, records, valueRecordNames: valueNames}
1759
+ const constructorContext = {...baseContext,
1760
+ bindings: new Map(declaration.constructor.parameters.map((parameter) => [parameter.name, parameter.type])),
1761
+ returnType: {kind: /** @type {const} */ ("TypeReference"), name: /** @type {const} */ ("void")}}
1762
+
1763
+ declaration.constructor.body = convertBlock(constructorNode.body, language, filename, source, canonicalZeroRequired,
1764
+ constructorContext)
1765
+ for (let index = 0; index < methodNodes.length; index += 1) {
1766
+ const method = declaration.methods[index]
1767
+ const context = {...baseContext, bindings: new Map(method.parameters.map((parameter) => [parameter.name, parameter.type])),
1768
+ returnType: method.returnType}
1769
+
1770
+ method.body = convertBlock(methodNodes[index].body, language, filename, source, canonicalZeroRequired, context)
1771
+ }
1772
+
1773
+ return declaration
1774
+ }
1775
+
1776
+ /**
1777
+ * Reads one constructor or method signature without admitting overloads or modifiers.
1778
+ * @param {import("@babel/types").ClassMethod} node - Parser method.
1779
+ * @param {import("../semantic/types.js").ClassDeclaration} declaration - Declaring class.
1780
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Nominal types.
1781
+ * @param {"javascript" | "typescript"} language - Source language.
1782
+ * @param {string} filename - Source filename.
1783
+ * @param {string} source - Complete source.
1784
+ * @param {boolean} constructor - Whether this is the constructor.
1785
+ * @returns {{location: import("../semantic/types.js").SourceLocation, name: string, nameLocation: import("../semantic/types.js").SourceLocation, parameters: import("../semantic/types.js").Parameter[], returnType?: import("../semantic/types.js").SemanticFunctionReturnType}} Signature.
1786
+ */
1787
+ function convertClassCallableSignature(node, declaration, nominalNames, language, filename, source, constructor) {
1788
+ const location = nodeLocation(node, filename, source)
1789
+
1790
+ if (node.computed || node.static || node.async || node.generator || node.decorators?.length || node.typeParameters ||
1791
+ node.key.type != "Identifier" || constructor != (node.kind == "constructor") ||
1792
+ language == "typescript" && node.accessibility != null) {
1793
+ return unsupportedSyntax(language, "noncanonical reference class callable", location)
1794
+ }
1795
+ const documented = language == "javascript"
1796
+ ? jsdocTypes(/** @type {import("@babel/types").FunctionDeclaration} */ (/** @type {unknown} */ (node)), filename, source)
1797
+ : undefined
1798
+ if (constructor && (node.returnType || documented?.returnType)) {
1799
+ return unsupportedSyntax(language, "constructor return annotation", location)
1800
+ }
1801
+ const parameters = node.params.map((parameter) => {
1802
+ const parameterLocation = nodeLocation(parameter, filename, source)
1803
+
1804
+ if (parameter.type != "Identifier" || parameter.optional || parameter.decorators?.length) {
1805
+ return unsupportedSyntax(language, "noncanonical method parameter", parameterLocation)
1806
+ }
1807
+ const documentedType = documented?.parameters.get(parameter.name)
1808
+ const type = language == "typescript"
1809
+ ? convertTypeScriptType(parameter.typeAnnotation, `Parameter '${parameter.name}'`, parameterLocation, filename, source, nominalNames)
1810
+ : convertType(documentedType?.sourceType, language, `Parameter '${parameter.name}'`, parameterLocation, source,
1811
+ documentedType?.location, nominalNames)
1812
+
1813
+ return withParserRanges({kind: /** @type {const} */ ("Parameter"), location: parameterLocation, name: parameter.name, type},
1814
+ {name: identifierLocation(parameter, filename, source)})
1815
+ })
1816
+ if (documented && documented.parameters.size != parameters.length) {
1817
+ return unsupportedSyntax(language, "method JSDoc parameter mismatch", location)
1818
+ }
1819
+ const name = constructor ? "constructor" : node.key.name
1820
+ const nameLocation = identifierLocation(node.key, filename, source)
1821
+
1822
+ if (!constructor && referenceMethodHooks.has(name)) {
1823
+ return unsupportedSyntax(language, `reserved reference method '${name}'`, nameLocation)
1824
+ }
1825
+
1826
+ return {location, name, nameLocation, parameters, ...(constructor ? {} : {returnType: language == "typescript"
1827
+ ? convertTypeScriptReturnType(node.returnType, `Method '${name}' return`, location, filename, source, nominalNames)
1828
+ : convertReturnType(documented?.returnType?.sourceType, language, `Method '${name}' return`, location, source,
1829
+ documented?.returnType?.location, nominalNames)})}
1830
+ }
1831
+
1832
+ /**
1833
+ * Converts one exact TypeScript readonly constructor-parameter record profile.
1834
+ * @param {import("@babel/types").ClassDeclaration} node - Class declaration.
1835
+ * @param {import("../semantic/types.js").RecordDeclaration} declaration - Predeclared nominal identity.
1836
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - All nominal declarations by name.
1837
+ * @param {string} filename - Source filename.
1838
+ * @param {string} source - Complete source.
1839
+ * @returns {import("../semantic/types.js").RecordDeclaration} Semantic record declaration.
1840
+ */
1841
+ function convertTypeScriptRecord(node, declaration, recordNames, filename, source) {
1842
+ const location = nodeLocation(node, filename, source)
1843
+ const implements_ = Reflect.get(node, "implements")
1844
+ const mixins = Reflect.get(node, "mixins")
1845
+
1846
+ if (!node.id) return unsupportedSyntax("typescript", "anonymous record class", location)
1847
+ if (node.superClass || (Array.isArray(implements_) && implements_.length) || node.decorators?.length || node.superTypeParameters ||
1848
+ Reflect.get(node, "abstract") || Reflect.get(node, "declare") || (Array.isArray(mixins) && mixins.length)) {
1849
+ return unsupportedSyntax("typescript", "record inheritance, decoration, or modifier", location)
1850
+ }
1851
+ if (node.body.body.length != 1 || node.body.body[0].type != "ClassMethod") {
1852
+ const extra = node.body.body[1] ?? node.body.body[0] ?? node.body
1853
+
1854
+ return unsupportedSyntax("typescript", "record class body outside one constructor", nodeLocation(extra, filename, source))
1855
+ }
1856
+ const constructor = node.body.body[0]
1857
+ const typeParameterNames = new Map((declaration.typeParameters ?? []).map((parameter) => [parameter.name, parameter]))
1858
+
1859
+ if (constructor.kind != "constructor" || constructor.computed || constructor.static || constructor.async || constructor.generator ||
1860
+ constructor.key.type != "Identifier" || constructor.key.name != "constructor" || constructor.decorators?.length ||
1861
+ constructor.returnType || constructor.typeParameters || Reflect.get(constructor, "accessibility") ||
1862
+ constructor.body.body.length != 0 || constructor.body.directives.length != 0) {
1863
+ return unsupportedSyntax("typescript", "noncanonical record constructor", nodeLocation(constructor, filename, source))
1864
+ }
1865
+ declaration.fields = constructor.params.map((parameter) => {
1866
+ const fieldLocation = nodeLocation(parameter, filename, source)
1867
+
1868
+ if (parameter.type != "TSParameterProperty" || parameter.readonly !== true || parameter.accessibility || parameter.override ||
1869
+ parameter.parameter.type != "Identifier" || parameter.parameter.optional || parameter.parameter.decorators?.length) {
1870
+ return unsupportedSyntax("typescript", "record field outside readonly parameter-property profile", fieldLocation)
1871
+ }
1872
+ const identifier = parameter.parameter
1873
+ const field = {
1874
+ kind: /** @type {const} */ ("RecordField"),
1875
+ location: fieldLocation,
1876
+ name: identifier.name,
1877
+ type: convertTypeScriptType(identifier.typeAnnotation, `Record field '${identifier.name}'`, fieldLocation, filename, source,
1878
+ recordNames, typeParameterNames)
1879
+ }
1880
+
1881
+ return withParserRanges(field, {name: identifierLocation(identifier, filename, source)})
1882
+ })
1883
+
1884
+ return withParserRanges(declaration, {name: identifierLocation(node.id, filename, source)})
1885
+ }
1886
+
1887
+ /**
1888
+ * Converts one exact JSDoc constructor/readonly-assignment/freeze record profile.
1889
+ * @param {import("@babel/types").ClassDeclaration} node - Class declaration.
1890
+ * @param {import("../semantic/types.js").RecordDeclaration} declaration - Predeclared nominal identity.
1891
+ * @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by name.
1892
+ * @param {string} filename - Source filename.
1893
+ * @param {string} source - Complete source.
1894
+ * @returns {import("../semantic/types.js").RecordDeclaration} Semantic record declaration.
1895
+ */
1896
+ function convertJavaScriptRecord(node, declaration, recordNames, filename, source) {
1897
+ const location = nodeLocation(node, filename, source)
1898
+ const implements_ = Reflect.get(node, "implements")
1899
+ const mixins = Reflect.get(node, "mixins")
1900
+
1901
+ if (!node.id) return unsupportedSyntax("javascript", "anonymous record class", location)
1902
+ if (node.superClass || node.decorators?.length || node.typeParameters || node.superTypeParameters ||
1903
+ (Array.isArray(implements_) && implements_.length) || (Array.isArray(mixins) && mixins.length)) {
1904
+ return unsupportedSyntax("javascript", "record inheritance or decoration", location)
1905
+ }
1906
+ if (node.body.body.length != 1 || node.body.body[0].type != "ClassMethod") {
1907
+ return unsupportedSyntax("javascript", "record class body outside one constructor", nodeLocation(node.body.body[1] ?? node.body.body[0] ?? node.body, filename, source))
1908
+ }
1909
+ const constructor = node.body.body[0]
1910
+ const typeParameterNames = new Map((declaration.typeParameters ?? []).map((parameter) => [parameter.name, parameter]))
1911
+
1912
+ if (constructor.kind != "constructor" || constructor.computed || constructor.static || constructor.async || constructor.generator ||
1913
+ constructor.key.type != "Identifier" || constructor.key.name != "constructor" || constructor.decorators?.length ||
1914
+ constructor.returnType || constructor.typeParameters || constructor.body.directives.length) {
1915
+ return unsupportedSyntax("javascript", "noncanonical record constructor", nodeLocation(constructor, filename, source))
1916
+ }
1917
+ const parameters = constructor.params
1918
+
1919
+ if (parameters.some((parameter) => parameter.type != "Identifier" || parameter.optional || parameter.decorators?.length)) {
1920
+ const invalid = parameters.find((parameter) => parameter.type != "Identifier" || parameter.optional || parameter.decorators?.length)
1921
+
1922
+ return unsupportedSyntax("javascript", "noncanonical record constructor parameter", nodeLocation(invalid ?? constructor, filename, source))
1923
+ }
1924
+ const documented = jsdocTypes(/** @type {import("@babel/types").FunctionDeclaration} */ (/** @type {unknown} */ (constructor)), filename, source)
1925
+
1926
+ if (documented.returnType || documented.parameters.size != parameters.length || constructor.body.body.length != parameters.length + 1) {
1927
+ return unsupportedSyntax("javascript", "incomplete record constructor profile", nodeLocation(constructor, filename, source))
1928
+ }
1929
+ declaration.fields = parameters.map((parameter, index) => {
1930
+ const identifier = /** @type {import("@babel/types").Identifier} */ (parameter)
1931
+ const statement = constructor.body.body[index]
1932
+ const documentedType = documented.parameters.get(identifier.name)
1933
+
1934
+ if (!documentedType || statement.type != "ExpressionStatement" || statement.expression.type != "AssignmentExpression" ||
1935
+ statement.expression.operator != "=" || statement.expression.left.type != "MemberExpression" || statement.expression.left.computed ||
1936
+ statement.expression.left.object.type != "ThisExpression" || statement.expression.left.property.type != "Identifier" ||
1937
+ statement.expression.left.property.name != identifier.name || statement.expression.right.type != "Identifier" ||
1938
+ statement.expression.right.name != identifier.name) {
1939
+ return unsupportedSyntax("javascript", "record field not initialized exactly once", nodeLocation(statement, filename, source))
1940
+ }
1941
+ const readonlyComment = statement.leadingComments?.at(-1)
1942
+ const gap = readonlyComment ? source.slice(readonlyComment.end ?? 0, statement.start ?? 0) : ""
1943
+ const tags = readonlyComment?.type == "CommentBlock" && readonlyComment.value.startsWith("*")
1944
+ ? parseComment(`/*${readonlyComment.value}*/`)[0]?.tags ?? []
1945
+ : []
1946
+
1947
+ if (!readonlyComment || !/^\s*$/u.test(gap) || tags.length != 1 || tags[0].tag != "readonly" ||
1948
+ tags[0].name || tags[0].type || tags[0].description) {
1949
+ return unsupportedSyntax("javascript", "record field without exact @readonly annotation", nodeLocation(statement, filename, source))
1950
+ }
1951
+ const field = {
1952
+ kind: /** @type {const} */ ("RecordField"),
1953
+ location: nodeLocation(statement, filename, source),
1954
+ name: identifier.name,
1955
+ type: convertType(documentedType.sourceType, "javascript", `Record field '${identifier.name}'`,
1956
+ nodeLocation(identifier, filename, source), source, documentedType.location, recordNames, typeParameterNames)
1957
+ }
1958
+
1959
+ return withParserRanges(field, {name: identifierLocation(statement.expression.left.property, filename, source)})
1960
+ })
1961
+ const freeze = constructor.body.body.at(-1)
1962
+
1963
+ if (!freeze || freeze.type != "ExpressionStatement" || freeze.expression.type != "CallExpression" || freeze.expression.optional ||
1964
+ freeze.expression.arguments.length != 1 || freeze.expression.arguments[0].type != "ThisExpression" ||
1965
+ !isJavaScriptRecordFreeze(freeze.expression)) {
1966
+ return unsupportedSyntax("javascript", "record constructor without final Object.freeze(this)", nodeLocation(freeze ?? constructor, filename, source))
1967
+ }
1968
+
1969
+ return withParserRanges(declaration, {name: identifierLocation(node.id, filename, source)})
1970
+ }
1971
+
1972
+ /**
1973
+ * Recognizes the canonical source freeze or the generated identifier-free intrinsic owner.
1974
+ * @param {import("@babel/types").CallExpression} expression - Final constructor call.
1975
+ * @returns {boolean} Whether the call owns the native record-freeze operation.
1976
+ */
1977
+ function isJavaScriptRecordFreeze(expression) {
1978
+ const callee = expression.callee
1979
+
1980
+ if (callee.type != "MemberExpression" || callee.computed || callee.optional ||
1981
+ callee.property.type != "Identifier" || callee.property.name != "freeze") return false
1982
+ if (callee.object.type == "Identifier") return callee.object.name == "Object"
1983
+ if (callee.object.type != "MemberExpression" || callee.object.computed || callee.object.optional ||
1984
+ callee.object.property.type != "Identifier" || callee.object.property.name != "constructor") return false
1985
+
1986
+ return callee.object.object.type == "ObjectExpression" && callee.object.object.properties.length == 0 &&
1987
+ callee.object.object.extra?.parenthesized === true
519
1988
  }
520
1989
 
521
1990
  /**
@@ -525,28 +1994,58 @@ function convertFunction(node, language, filename, source, canonicalZeroRequired
525
1994
  * @param {string} filename - Source filename.
526
1995
  * @param {string} source - Complete source.
527
1996
  * @param {boolean} canonicalZeroRequired - Whether generated scalar output may contain signed zero.
1997
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
528
1998
  * @returns {import("../semantic/types.js").IfStatement} Semantic branch.
529
1999
  */
530
- function convertIf(node, language, filename, source, canonicalZeroRequired) {
2000
+ function convertIf(node, language, filename, source, canonicalZeroRequired, context) {
531
2001
  const location = nodeLocation(node, filename, source)
2002
+ const condition = convertExpression(node.test, language, filename, source, context)
2003
+ const consequentContext = {...context, bindings: new Map(context.bindings)}
2004
+ const alternateContext = {...context, bindings: new Map(context.bindings)}
532
2005
  const consequent = node.consequent.type == "BlockStatement"
533
- ? convertBlock(node.consequent, language, filename, source, canonicalZeroRequired)
534
- : convertBlock([node.consequent], language, filename, source, canonicalZeroRequired, nodeLocation(node.consequent, filename, source))
2006
+ ? convertBlock(node.consequent, language, filename, source, canonicalZeroRequired, consequentContext)
2007
+ : convertBlock([node.consequent], language, filename, source, canonicalZeroRequired, consequentContext, nodeLocation(node.consequent, filename, source))
535
2008
  const alternate = node.alternate
536
2009
  ? node.alternate.type == "BlockStatement"
537
- ? convertBlock(node.alternate, language, filename, source, canonicalZeroRequired)
538
- : convertBlock([node.alternate], language, filename, source, canonicalZeroRequired, nodeLocation(node.alternate, filename, source))
2010
+ ? convertBlock(node.alternate, language, filename, source, canonicalZeroRequired, alternateContext)
2011
+ : convertBlock([node.alternate], language, filename, source, canonicalZeroRequired, alternateContext, nodeLocation(node.alternate, filename, source))
539
2012
  : undefined
540
2013
 
541
2014
  return {
542
2015
  ...(alternate ? {alternate} : {}),
543
- condition: convertExpression(node.test, language, filename, source),
2016
+ condition,
544
2017
  consequent,
545
2018
  kind: "IfStatement",
546
2019
  location
547
2020
  }
548
2021
  }
549
2022
 
2023
+ /**
2024
+ * Converts one exact block-bodied pre-condition loop.
2025
+ * @param {import("@babel/types").WhileStatement} node - Babel while statement.
2026
+ * @param {"javascript" | "typescript"} language - Frontend language.
2027
+ * @param {string} filename - Source filename.
2028
+ * @param {string} source - Complete source.
2029
+ * @param {boolean} canonicalZeroRequired - Whether generated scalar output may contain signed zero.
2030
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
2031
+ * @returns {import("../semantic/types.js").WhileStatement} Semantic loop.
2032
+ */
2033
+ function convertWhile(node, language, filename, source, canonicalZeroRequired, context) {
2034
+ const location = nodeLocation(node, filename, source)
2035
+
2036
+ if (node.body.type != "BlockStatement") {
2037
+ return unsupportedSyntax(language, "while without block body", nodeLocation(node.body, filename, source))
2038
+ }
2039
+ const bodyContext = {...context, bindings: new Map(context.bindings)}
2040
+
2041
+ return withParserRanges({
2042
+ body: convertBlock(node.body, language, filename, source, canonicalZeroRequired, bodyContext),
2043
+ condition: convertExpression(node.test, language, filename, source, context, undefined, true),
2044
+ kind: /** @type {const} */ ("WhileStatement"),
2045
+ location
2046
+ }, {keyword: tokenLocation(node, "while", node.start ?? 0, node.test.start ?? node.end ?? source.length, filename, source)})
2047
+ }
2048
+
550
2049
  /**
551
2050
  * Reports whether Babel parsed an operation that makes generated JavaScript-family scalar output require zero canonicalization.
552
2051
  * @param {unknown} value - Babel parser subtree.
@@ -595,9 +2094,10 @@ function hasCanonicalZeroReceiverWrapper(receiver, call) {
595
2094
  * @param {string} filename - Source filename.
596
2095
  * @param {string} source - Complete source.
597
2096
  * @param {boolean} canonicalZeroRequired - Whether this parsed module contains a sign-producing integer operation.
2097
+ * @param {JavaScriptConversionContext} context - Typed lexical conversion context.
598
2098
  * @returns {import("../semantic/types.js").PrintStatement} Print statement.
599
2099
  */
600
- function convertPrint(node, language, filename, source, canonicalZeroRequired) {
2100
+ function convertPrint(node, language, filename, source, canonicalZeroRequired, context) {
601
2101
  const location = nodeLocation(node, filename, source)
602
2102
 
603
2103
  if (node.type != "ExpressionStatement" || node.expression.type != "CallExpression") {
@@ -629,7 +2129,7 @@ function convertPrint(node, language, filename, source, canonicalZeroRequired) {
629
2129
  argument = argument.callee.object
630
2130
  }
631
2131
 
632
- return {expression: convertExpression(argument, language, filename, source), kind: "PrintStatement", location}
2132
+ return {expression: convertExpression(argument, language, filename, source, context), kind: "PrintStatement", location}
633
2133
  }
634
2134
 
635
2135
  /**
@@ -638,32 +2138,167 @@ function convertPrint(node, language, filename, source, canonicalZeroRequired) {
638
2138
  * @param {string} input.filename - Source filename.
639
2139
  * @param {"javascript" | "typescript"} input.language - Frontend language.
640
2140
  * @param {string} input.source - Source text.
2141
+ * @param {readonly import("../semantic/types.js").EffectCapabilityDeclaration[]} [input.capabilities] - Compiler-authorized declarations.
2142
+ * @param {{isEntry: boolean, functions: Map<string, import("../semantic/types.js").FunctionDeclaration>, records: Map<string, import("../semantic/types.js").RecordDeclaration>, errors?: Map<string, import("../semantic/types.js").ErrorDeclaration>, valueRecords?: Map<string, import("../semantic/types.js").RecordDeclaration>}} [input.program] - Resolved program imports and entry role.
641
2143
  * @returns {import("../semantic/types.js").SemanticModule} Semantic module.
642
2144
  */
643
- export function parseJavaScriptTypeScript({filename, language, source}) {
644
- const file = parseBabelSource({filename, language, source})
2145
+ export function parseJavaScriptTypeScript({capabilities = [], filename, language, source, program}) {
2146
+ const file = parseBabelSource({filename, language, source, sourceType: program ? "module" : "script"})
645
2147
  const tokens = file.tokens ?? []
646
2148
  const tokenIndex = {byStart: new Map(tokens.map((token) => [token.start, token])), tokens}
647
2149
  const canonicalZeroRequired = parserTreeRequiresCanonicalZero(file.program)
648
2150
 
649
2151
  rememberTokens(file, tokenIndex)
2152
+ const nullCheckOptOut = language == "typescript"
2153
+ ? file.comments?.find((comment) => comment.value.trim() == "@ts-nocheck")
2154
+ : undefined
2155
+
2156
+ if (nullCheckOptOut) {
2157
+ return unsupportedSyntax(language, "disabled TypeScript checking directive", locationFromOffsets(
2158
+ filename,
2159
+ source,
2160
+ nullCheckOptOut.start ?? 0,
2161
+ nullCheckOptOut.end ?? source.length
2162
+ ))
2163
+ }
650
2164
  if (file.program.directives.length > 0) {
651
2165
  return unsupportedSyntax(language, "top-level directive", nodeLocation(file.program.directives[0], filename, source))
652
2166
  }
653
- const functions = file.program.body.filter((node) => node.type == "FunctionDeclaration")
654
- .map((node) => convertFunction(node, language, filename, source, canonicalZeroRequired))
655
- const entryNodes = file.program.body.filter((node) => node.type != "FunctionDeclaration")
2167
+ /** @type {import("@babel/types").Statement[]} */
2168
+ let semanticNodes = []
2169
+
2170
+ for (const node of file.program.body) {
2171
+ if (node.type == "ImportDeclaration") {
2172
+ if (!program) return unsupportedSyntax(language, "import declaration", nodeLocation(node, filename, source))
2173
+ continue
2174
+ }
2175
+ if (node.type == "ExportNamedDeclaration") {
2176
+ if (!program) return unsupportedSyntax(language, "export declaration", nodeLocation(node, filename, source))
2177
+ if (node.source || node.declaration && !["ClassDeclaration", "FunctionDeclaration"].includes(node.declaration.type)) {
2178
+ return unsupportedSyntax(language, "re-export or unsupported exported declaration", nodeLocation(node, filename, source))
2179
+ }
2180
+ if (node.declaration) {
2181
+ if (!node.declaration.leadingComments && node.leadingComments) node.declaration.leadingComments = node.leadingComments
2182
+ semanticNodes.push(/** @type {import("@babel/types").ClassDeclaration | import("@babel/types").FunctionDeclaration} */ (node.declaration))
2183
+ }
2184
+ continue
2185
+ }
2186
+ if (node.type == "ExportDefaultDeclaration" || node.type == "ExportAllDeclaration") {
2187
+ return unsupportedSyntax(language, node.type, nodeLocation(node, filename, source))
2188
+ }
2189
+ semanticNodes.push(node)
2190
+ }
2191
+ semanticNodes = task034JavaScriptNodes(semanticNodes, capabilities, language, filename, source)
2192
+ const classNodes = semanticNodes.filter((node) => node.type == "ClassDeclaration")
2193
+ const errorNodes = classNodes.filter((node) => node.superClass?.type == "Identifier" && node.superClass.name == "Error")
2194
+ const referenceClassNodes = classNodes.filter((node) => !errorNodes.includes(node) &&
2195
+ node.body.body.some((member) => member.type == "ClassProperty" || member.type == "ClassPrivateProperty"))
2196
+ const recordNodes = classNodes.filter((node) => !errorNodes.includes(node) && !referenceClassNodes.includes(node))
2197
+ const errorDeclarations = errorNodes.map((node, index) => ({
2198
+ id: `error:${index}`,
2199
+ kind: /** @type {const} */ ("ErrorDeclaration"),
2200
+ location: nodeLocation(node, filename, source),
2201
+ name: node.id?.name ?? ""
2202
+ }))
2203
+ const recordDeclarations = recordNodes.map((node, index) => {
2204
+ const id = `record:${index}`
2205
+ const typeParameters = language == "typescript"
2206
+ ? typeScriptTypeParameters(/** @type {import("@babel/types").TSTypeParameterDeclaration | null | undefined} */ (node.typeParameters), id, filename, source)
2207
+ : classJsdocTypeParameters(node, id, filename, source)
2208
+
2209
+ return {
2210
+ fields: [],
2211
+ id,
2212
+ kind: /** @type {const} */ ("RecordDeclaration"),
2213
+ location: nodeLocation(node, filename, source),
2214
+ name: node.id?.name ?? "",
2215
+ ...(typeParameters ? {typeParameters} : {})
2216
+ }
2217
+ })
2218
+ const classDeclarations = referenceClassNodes.map((node, index) => ({
2219
+ constructor: /** @type {import("../semantic/types.js").ConstructorDeclaration} */ ({}),
2220
+ fields: [], id: `class:${index}`, kind: /** @type {const} */ ("ClassDeclaration"),
2221
+ location: nodeLocation(node, filename, source), methods: [], name: node.id?.name ?? ""
2222
+ }))
2223
+ const recordNames = /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} */ (
2224
+ new Map(program?.records ?? []))
2225
+ for (const resource of capabilities.flatMap(({resources}) => resources)) recordNames.set(resource.name, resource)
2226
+ const valueRecordNames = /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration>} */ (
2227
+ new Map(program?.valueRecords ?? program?.records ?? []))
2228
+ const errorNames = new Map(program?.errors ?? [])
2229
+ for (const failure of capabilities.flatMap(({failures}) => failures)) {
2230
+ errorNames.set(failure.name, /** @type {import("../semantic/types.js").ErrorDeclaration} */ (/** @type {unknown} */ (failure)))
2231
+ }
2232
+ for (const declaration of errorDeclarations) errorNames.set(declaration.name, declaration)
2233
+ const errorsById = new Map([...errorNames.values()].map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2234
+ const errors = errorNodes.map((node, index) => convertJavaScriptError(node, errorDeclarations[index], language, filename, source))
2235
+ for (const declaration of recordDeclarations) recordNames.set(declaration.name, declaration)
2236
+ for (const declaration of recordDeclarations) valueRecordNames.set(declaration.name, declaration)
2237
+ for (const declaration of classDeclarations) recordNames.set(declaration.name, declaration)
2238
+ for (const declaration of classDeclarations) valueRecordNames.set(declaration.name, declaration)
2239
+ const valueRecordsById = new Map(recordDeclarations.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2240
+ const classesById = new Map(classDeclarations.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
2241
+ const classes = referenceClassNodes.map((node, index) => predeclareReferenceClass(
2242
+ node, classDeclarations[index], recordNames, language, filename, source
2243
+ ))
2244
+ const functionNodes = semanticNodes.filter((node) => node.type == "FunctionDeclaration")
2245
+ const signatures = functionNodes.map((node, index) =>
2246
+ convertFunctionSignature(node, language, filename, source, recordNames, `function:${index}`))
2247
+ const functionSignatures = new Map([...program?.functions ?? []].map(([localName, declaration]) => [localName, {
2248
+ location: declaration.location,
2249
+ name: localName,
2250
+ nameLocation: declaration.location,
2251
+ parameters: declaration.parameters,
2252
+ returnType: declaration.returnType,
2253
+ ...(declaration.typeParameters ? {typeParameters: declaration.typeParameters} : {})
2254
+ }]))
2255
+ for (const operation of capabilities.flatMap(({operations}) => operations)) {
2256
+ if (!functionSignatures.has(operation.name)) functionSignatures.set(operation.name, {
2257
+ location: moduleLocation(filename, source),
2258
+ name: operation.name,
2259
+ nameLocation: moduleLocation(filename, source),
2260
+ parameters: operation.parameters.map((parameter) => ({...parameter, kind: /** @type {const} */ ("Parameter"), location: moduleLocation(filename, source)})),
2261
+ returnType: operation.returnType
2262
+ })
2263
+ }
2264
+ for (const signature of signatures) functionSignatures.set(signature.name, signature)
2265
+ const records = recordNodes.map((node, index) => language == "typescript"
2266
+ ? convertTypeScriptRecord(node, recordDeclarations[index], recordNames, filename, source)
2267
+ : convertJavaScriptRecord(node, recordDeclarations[index], recordNames, filename, source))
2268
+ for (let index = 0; index < referenceClassNodes.length; index += 1) {
2269
+ convertReferenceClassBodies(referenceClassNodes[index], classes[index], recordNames, valueRecordNames,
2270
+ valueRecordsById, classesById, functionSignatures, errorNames, errorsById, language, filename, source,
2271
+ canonicalZeroRequired)
2272
+ }
2273
+ const functions = functionNodes.map((node, index) =>
2274
+ convertFunction(node, signatures[index], functionSignatures, recordNames, valueRecordNames, valueRecordsById, classesById,
2275
+ errorNames, errorsById, language, filename, source, canonicalZeroRequired))
2276
+ const entryNodes = semanticNodes.filter((node) => node.type != "FunctionDeclaration" && node.type != "ClassDeclaration")
656
2277
  const location = moduleLocation(filename, source)
657
2278
 
658
- if (functions.length == 0) return unsupportedSyntax(language, "module without a function", location)
2279
+ if (program && !program.isEntry && entryNodes.length > 0) {
2280
+ return unsupportedSyntax(language, "top-level side effect outside the selected entry module", nodeLocation(entryNodes[0], filename, source))
2281
+ }
659
2282
  const entryLocation = entryNodes.length == 0 ? location : locationFromOffsets(
660
2283
  filename,
661
2284
  source,
662
2285
  entryNodes[0].start ?? 0,
663
2286
  entryNodes.at(-1)?.end ?? source.length
664
2287
  )
665
- const entryBlock = convertBlock(entryNodes, language, filename, source, canonicalZeroRequired, entryLocation)
2288
+ const entryBlock = convertBlock(
2289
+ entryNodes,
2290
+ language,
2291
+ filename,
2292
+ source,
2293
+ canonicalZeroRequired,
2294
+ {bindings: new Map(), classes: classesById, errorNames, errors: errorsById, functions: functionSignatures,
2295
+ orderedMapDeclarations: orderedMapDeclarationsFor(entryNodes), recordNames, records: valueRecordsById, valueRecordNames},
2296
+ entryLocation
2297
+ )
666
2298
 
2299
+ if (!program && functions.length == 0 && capabilities.length == 0 && !blockReferencesProtectedEntry(entryBlock)) {
2300
+ return unsupportedSyntax(language, "module without a function", location)
2301
+ }
667
2302
  return {
668
2303
  entryPoint: {
669
2304
  body: entryBlock,
@@ -672,8 +2307,62 @@ export function parseJavaScriptTypeScript({filename, language, source}) {
672
2307
  },
673
2308
  functions,
674
2309
  kind: "Module",
675
- location
2310
+ location,
2311
+ ...(errors.length > 0 ? {errors} : {}),
2312
+ ...(classes.length > 0 ? {classes} : {}),
2313
+ ...(records.length > 0 ? {records} : {})
2314
+ }
2315
+ }
2316
+
2317
+ /**
2318
+ * Removes exact protected Node probe support before portable conversion.
2319
+ * @param {import("@babel/types").Statement[]} nodes - Parsed top-level statements.
2320
+ * @param {readonly import("../semantic/types.js").EffectCapabilityDeclaration[]} capabilities - Authorized declarations.
2321
+ * @param {"javascript" | "typescript"} language - Source language.
2322
+ * @param {string} filename - Source filename.
2323
+ * @param {string} source - Complete source.
2324
+ * @returns {import("@babel/types").Statement[]} Portable statements.
2325
+ */
2326
+ function task034JavaScriptNodes(nodes, capabilities, language, filename, source) {
2327
+ if (!isTask034ResourceProbe(capabilities)) return nodes
2328
+ const first = nodes[0]
2329
+ const marker = first?.leadingComments?.some((comment) => comment.type == "CommentBlock" &&
2330
+ comment.value.trim() == "semantifold-task034-support")
2331
+
2332
+ if (!marker) return nodes
2333
+ if (!hasExactEffectSupport(source, language)) {
2334
+ return unsupportedSyntax(language, "malformed protected Task 034 support", nodeLocation(first, filename, source))
676
2335
  }
2336
+ const expected = [
2337
+ ["VariableDeclaration", "__semantifoldTask034Fs"], ["ClassDeclaration", "ProbeOperationFailure"],
2338
+ ["ClassDeclaration", "ProbeAcquireFailure"], ["ClassDeclaration", "ProbeReadFailure"],
2339
+ ["ClassDeclaration", "ProbeCloseFailure"], ["ClassDeclaration", "ProbeResourceClosed"],
2340
+ ["ClassDeclaration", "ProbeResource"], ["VariableDeclaration", "__semantifoldTask034Trace"],
2341
+ ["FunctionDeclaration", "probeEffect"], ["FunctionDeclaration", "probeAcquire"],
2342
+ ["FunctionDeclaration", "probeRead"], ["FunctionDeclaration", "probeClose"], ["FunctionDeclaration", "probeTrace"]
2343
+ ]
2344
+ /**
2345
+ * Reads the declaration name used to verify one protected support statement.
2346
+ * @param {import("@babel/types").Statement | undefined} node - Candidate statement.
2347
+ * @returns {string | undefined} Declared name.
2348
+ */
2349
+ const nodeName = (node) => {
2350
+ if (!node) return undefined
2351
+ if (node.type == "VariableDeclaration" && node.declarations.length == 1 && node.declarations[0].id.type == "Identifier") {
2352
+ return node.declarations[0].id.name
2353
+ }
2354
+ const id = Reflect.get(node, "id")
2355
+ return id && typeof id == "object" && typeof Reflect.get(id, "name") == "string" ? Reflect.get(id, "name") : undefined
2356
+ }
2357
+ const matches = expected.every(([type, name], index) => nodes[index]?.type == type && nodeName(nodes[index]) == name)
2358
+ const next = nodes[expected.length]
2359
+ const ended = next?.leadingComments?.some((comment) => comment.type == "CommentBlock" &&
2360
+ comment.value.trim() == "semantifold-task034-support-end")
2361
+
2362
+ if (!matches || next && !ended) {
2363
+ return unsupportedSyntax(language, "malformed protected Task 034 support", nodeLocation(first, filename, source))
2364
+ }
2365
+ return nodes.slice(expected.length)
677
2366
  }
678
2367
 
679
2368
  /**
@@ -682,21 +2371,276 @@ export function parseJavaScriptTypeScript({filename, language, source}) {
682
2371
  * @param {string} input.filename - Source filename.
683
2372
  * @param {"javascript" | "typescript"} input.language - Frontend language.
684
2373
  * @param {string} input.source - Source text.
2374
+ * @param {"module" | "script"} input.sourceType - Explicit parser goal.
685
2375
  * @returns {import("@babel/parser").ParseResult<import("@babel/types").File>} Babel file.
686
2376
  */
687
- function parseBabelSource({filename, language, source}) {
2377
+ function parseBabelSource({filename, language, source, sourceType}) {
688
2378
  try {
689
2379
  return parseBabel(source, {
690
- plugins: language == "typescript" ? ["typescript"] : [],
2380
+ allowAwaitOutsideFunction: true,
2381
+ plugins: language == "typescript" ? ["decorators-legacy", "typescript"] : ["decorators-legacy"],
691
2382
  sourceFilename: filename,
692
- sourceType: "script",
2383
+ sourceType,
693
2384
  tokens: true
694
2385
  })
695
2386
  } catch (error) {
696
- return parseFailure(language, error)
2387
+ return parseFailure(language, error, babelParserFailureLocation(error, filename, source))
697
2388
  }
698
2389
  }
699
2390
 
2391
+ /**
2392
+ * Reads only parser-owned ESM import/export declarations for program assembly.
2393
+ * @param {object} input - Parser input.
2394
+ * @param {string} input.filename - Source filename.
2395
+ * @param {"javascript" | "typescript"} input.language - Frontend language.
2396
+ * @param {string} input.source - Source text.
2397
+ * @returns {{imports: {declarationLocation: import("../semantic/types.js").SourceLocation, importedName: string, importedNameLocation: import("../semantic/types.js").SourceLocation, localName: string, localNameLocation: import("../semantic/types.js").SourceLocation, location: import("../semantic/types.js").SourceLocation, pathLocation: import("../semantic/types.js").SourceLocation, specifier: string, stdlibCandidate?: true, typeOnly: boolean}[], exports: {declarationLocation?: import("../semantic/types.js").SourceLocation, exportedName: string, exportedNameLocation?: import("../semantic/types.js").SourceLocation, localName: string, localNameLocation?: import("../semantic/types.js").SourceLocation, location: import("../semantic/types.js").SourceLocation, typeOnly: boolean}[]}} Parser-owned ESM module header.
2398
+ */
2399
+ export function inspectJavaScriptTypeScriptModule({filename, language, source}) {
2400
+ const file = parseBabelSource({filename, language, source, sourceType: "module"})
2401
+ const tokens = file.tokens ?? []
2402
+
2403
+ rememberTokens(file, {byStart: new Map(tokens.map((token) => [token.start, token])), tokens})
2404
+ const commonJs = findCommonJsNode(file.program)
2405
+
2406
+ if (commonJs) unsupportedSyntax(language, "CommonJS require/module exports", nodeLocation(commonJs, filename, source))
2407
+ const imports = []
2408
+ const exports = []
2409
+
2410
+ for (const node of file.program.body) {
2411
+ if (node.type == "ImportDeclaration") {
2412
+ const relative = node.source.value.startsWith("./") || node.source.value.startsWith("../")
2413
+ const stdlibCandidate = node.source.value.startsWith("semantifold:") || node.source.value.startsWith("node:")
2414
+
2415
+ if (!relative && !stdlibCandidate) {
2416
+ unsupportedSyntax(language, "bare package import", nodeLocation(node.source, filename, source))
2417
+ }
2418
+ if (node.specifiers.length == 0) unsupportedSyntax(language, "side-effect-only import", nodeLocation(node, filename, source))
2419
+ if ((node.attributes?.length ?? 0) > 0 || (node.assertions?.length ?? 0) > 0 || Reflect.get(node, "phase")) {
2420
+ unsupportedSyntax(language, "import attributes or phase", nodeLocation(node, filename, source))
2421
+ }
2422
+ for (const specifier of node.specifiers) {
2423
+ if (specifier.type != "ImportSpecifier" || specifier.imported.type != "Identifier") {
2424
+ unsupportedSyntax(language, specifier.type, nodeLocation(specifier, filename, source))
2425
+ }
2426
+ imports.push({
2427
+ declarationLocation: nodeLocation(node, filename, source),
2428
+ importedName: specifier.imported.name,
2429
+ importedNameLocation: identifierLocation(specifier.imported, filename, source),
2430
+ localName: specifier.local.name,
2431
+ localNameLocation: identifierLocation(specifier.local, filename, source),
2432
+ location: nodeLocation(specifier, filename, source),
2433
+ pathLocation: nodeLocation(node.source, filename, source),
2434
+ specifier: node.source.value,
2435
+ ...(!relative ? {stdlibCandidate: /** @type {const} */ (true)} : {}),
2436
+ typeOnly: node.importKind == "type" || specifier.importKind == "type"
2437
+ })
2438
+ }
2439
+ continue
2440
+ }
2441
+ if (node.type == "ExportDefaultDeclaration" || node.type == "ExportAllDeclaration") {
2442
+ unsupportedSyntax(language, node.type, nodeLocation(node, filename, source))
2443
+ }
2444
+ if (node.type != "ExportNamedDeclaration") continue
2445
+ if (node.source) unsupportedSyntax(language, "re-export", nodeLocation(node, filename, source))
2446
+ if (node.exportKind == "type") unsupportedSyntax(language, "type-only export", nodeLocation(node, filename, source))
2447
+ if (node.declaration) {
2448
+ if (node.declaration.type != "FunctionDeclaration" && node.declaration.type != "ClassDeclaration") {
2449
+ unsupportedSyntax(language, "unsupported exported declaration", nodeLocation(node.declaration, filename, source))
2450
+ }
2451
+ const identifier = node.declaration.id
2452
+
2453
+ if (!identifier) unsupportedSyntax(language, "anonymous exported declaration", nodeLocation(node.declaration, filename, source))
2454
+ exports.push({
2455
+ declarationLocation: nodeLocation(node, filename, source),
2456
+ exportedName: identifier.name,
2457
+ exportedNameLocation: identifierLocation(identifier, filename, source),
2458
+ localName: identifier.name,
2459
+ localNameLocation: identifierLocation(identifier, filename, source),
2460
+ location: identifierLocation(identifier, filename, source),
2461
+ typeOnly: /** @type {const} */ (false)
2462
+ })
2463
+ continue
2464
+ }
2465
+ for (const specifier of node.specifiers) {
2466
+ if (specifier.type != "ExportSpecifier" || specifier.local.type != "Identifier" || specifier.exported.type != "Identifier") {
2467
+ unsupportedSyntax(language, specifier.type, nodeLocation(specifier, filename, source))
2468
+ }
2469
+ if (specifier.exportKind == "type") unsupportedSyntax(language, "type-only export", nodeLocation(specifier, filename, source))
2470
+ exports.push({
2471
+ declarationLocation: nodeLocation(node, filename, source),
2472
+ exportedName: specifier.exported.name,
2473
+ exportedNameLocation: identifierLocation(specifier.exported, filename, source),
2474
+ localName: specifier.local.name,
2475
+ localNameLocation: identifierLocation(specifier.local, filename, source),
2476
+ location: nodeLocation(specifier, filename, source),
2477
+ typeOnly: /** @type {const} */ (false)
2478
+ })
2479
+ }
2480
+ }
2481
+
2482
+ const dynamic = findFacadeUnsafeJavaScriptNode(file.program, "dynamic", new Set())
2483
+
2484
+ if (dynamic) facadeFrontendFailure(language, "STDLIB_FACADE_DYNAMIC_REFERENCE",
2485
+ "Dynamic import cannot prove a standard-library facade identity.", nodeLocation(dynamic, filename, source))
2486
+ const stdlibBindings = new Set(imports.filter(({stdlibCandidate}) => stdlibCandidate).map(({localName}) => localName))
2487
+
2488
+ if (stdlibBindings.size > 0) {
2489
+ const mutation = findFacadeUnsafeJavaScriptNode(file.program, "mutation", stdlibBindings)
2490
+
2491
+ if (mutation) facadeFrontendFailure(language, "STDLIB_FACADE_SHADOWED",
2492
+ "A parser-proved standard-library facade binding cannot be reassigned or shadowed.", nodeLocation(mutation, filename, source))
2493
+ const reflection = findFacadeUnsafeJavaScriptNode(file.program, "reflection", stdlibBindings)
2494
+
2495
+ if (reflection) facadeFrontendFailure(language, "STDLIB_FACADE_REFLECTION_UNSUPPORTED",
2496
+ "Reflective lookup cannot preserve a proved standard-library facade symbol identity.", nodeLocation(reflection, filename, source))
2497
+ }
2498
+
2499
+ return {exports, imports}
2500
+ }
2501
+
2502
+ /**
2503
+ * Finds parser-owned dynamic, mutating, or reflective constructs relevant to facade identity.
2504
+ * @param {unknown} value - Babel subtree.
2505
+ * @param {"dynamic" | "mutation" | "reflection"} mode - Rejected construct class.
2506
+ * @param {Set<string>} bindings - Parser-proved native facade bindings.
2507
+ * @param {WeakSet<object>} [seen] - Cycle guard.
2508
+ * @returns {import("@babel/types").Node | undefined} First rejected parser node.
2509
+ */
2510
+ function findFacadeUnsafeJavaScriptNode(value, mode, bindings, seen = new WeakSet()) {
2511
+ if (!value || typeof value != "object" || seen.has(value)) return undefined
2512
+ seen.add(value)
2513
+ const node = /** @type {Record<string, unknown>} */ (value)
2514
+
2515
+ if (mode == "dynamic" && node.type == "ImportExpression") {
2516
+ const sourceNode = node.source
2517
+
2518
+ if (isFacadeModuleStringNode(sourceNode)) return /** @type {import("@babel/types").Node} */ (value)
2519
+ }
2520
+ if (mode == "dynamic" && node.type == "CallExpression" && node.callee && typeof node.callee == "object" &&
2521
+ Reflect.get(node.callee, "type") == "Import") {
2522
+ const arguments_ = Array.isArray(node.arguments) ? node.arguments : []
2523
+
2524
+ if (isFacadeModuleStringNode(arguments_[0])) return /** @type {import("@babel/types").Node} */ (value)
2525
+ }
2526
+ if (mode == "reflection" && node.type == "CallExpression" && node.callee && typeof node.callee == "object" &&
2527
+ Reflect.get(node.callee, "type") == "Identifier" && Reflect.get(node.callee, "name") == "eval") {
2528
+ return /** @type {import("@babel/types").Node} */ (value)
2529
+ }
2530
+ if (mode == "mutation" && (node.type == "AssignmentExpression" || node.type == "UpdateExpression")) {
2531
+ const target = node.type == "AssignmentExpression" ? node.left : node.argument
2532
+
2533
+ if (target && typeof target == "object" && Reflect.get(target, "type") == "Identifier" &&
2534
+ bindings.has(String(Reflect.get(target, "name")))) return /** @type {import("@babel/types").Node} */ (value)
2535
+ }
2536
+ for (const [key, child] of Object.entries(node)) {
2537
+ if (["extra", "innerComments", "leadingComments", "loc", "trailingComments"].includes(key)) continue
2538
+ if (Array.isArray(child)) {
2539
+ for (const item of child) {
2540
+ const found = findFacadeUnsafeJavaScriptNode(item, mode, bindings, seen)
2541
+
2542
+ if (found) return found
2543
+ }
2544
+ } else {
2545
+ const found = findFacadeUnsafeJavaScriptNode(child, mode, bindings, seen)
2546
+
2547
+ if (found) return found
2548
+ }
2549
+ }
2550
+ return undefined
2551
+ }
2552
+
2553
+ /**
2554
+ * Checks a Babel string literal for one catalog-style stdlib module identity.
2555
+ * @param {unknown} value - Babel import argument/source node.
2556
+ * @returns {boolean} Whether the exact literal is a facade module candidate.
2557
+ */
2558
+ function isFacadeModuleStringNode(value) {
2559
+ if (!value || typeof value != "object") return false
2560
+ const node = /** @type {Record<string, unknown>} */ (value)
2561
+ const literal = node.value
2562
+
2563
+ return node.type == "StringLiteral" && typeof literal == "string" &&
2564
+ (literal.startsWith("semantifold:") || literal.startsWith("node:"))
2565
+ }
2566
+
2567
+ /**
2568
+ * Throws one stable located facade frontend diagnostic.
2569
+ * @param {"javascript" | "typescript"} language - Source language.
2570
+ * @param {string} code - Stable diagnostic code.
2571
+ * @param {string} message - Diagnostic detail.
2572
+ * @param {import("../semantic/types.js").SourceLocation} location - Parser-owned location.
2573
+ * @returns {never} Always throws.
2574
+ */
2575
+ function facadeFrontendFailure(language, code, message, location) {
2576
+ throw new SemantifoldDiagnostic({code, language, location, message})
2577
+ }
2578
+
2579
+ /**
2580
+ * Finds CommonJS constructs through Babel nodes rather than source-text matching.
2581
+ * @param {unknown} value - Babel subtree.
2582
+ * @param {WeakSet<object>} [seen] - Cycle guard.
2583
+ * @returns {import("@babel/types").Node | undefined} Unsupported node.
2584
+ */
2585
+ function findCommonJsNode(value, seen = new WeakSet()) {
2586
+ if (!value || typeof value != "object" || seen.has(value)) return undefined
2587
+ seen.add(value)
2588
+ const node = /** @type {Record<string, unknown>} */ (value)
2589
+
2590
+ if (node.type == "CallExpression") {
2591
+ const callee = node.callee
2592
+
2593
+ if (callee && typeof callee == "object" && Reflect.get(callee, "type") == "Identifier" && Reflect.get(callee, "name") == "require") {
2594
+ return /** @type {import("@babel/types").Node} */ (value)
2595
+ }
2596
+ }
2597
+ if (node.type == "MemberExpression") {
2598
+ const object = node.object
2599
+
2600
+ if (object && typeof object == "object" && Reflect.get(object, "type") == "Identifier" &&
2601
+ ["exports", "module"].includes(String(Reflect.get(object, "name")))) {
2602
+ return /** @type {import("@babel/types").Node} */ (value)
2603
+ }
2604
+ }
2605
+ for (const [key, child] of Object.entries(node)) {
2606
+ if (["extra", "innerComments", "leadingComments", "loc", "trailingComments"].includes(key)) continue
2607
+ if (Array.isArray(child)) {
2608
+ for (const item of child) {
2609
+ const found = findCommonJsNode(item, seen)
2610
+
2611
+ if (found) return found
2612
+ }
2613
+ } else {
2614
+ const found = findCommonJsNode(child, seen)
2615
+
2616
+ if (found) return found
2617
+ }
2618
+ }
2619
+
2620
+ return undefined
2621
+ }
2622
+
2623
+ /**
2624
+ * Normalizes Babel's zero-based UTF-16 failure offset when available.
2625
+ * @param {unknown} error - Native Babel parser error.
2626
+ * @param {string} filename - Requested source filename.
2627
+ * @param {string} source - Complete source.
2628
+ * @returns {import("../semantic/types.js").SourceLocation} Failure range.
2629
+ */
2630
+ function babelParserFailureLocation(error, filename, source) {
2631
+ if (!error || typeof error != "object") return moduleLocation(filename, source)
2632
+ const direct = Reflect.get(error, "pos")
2633
+ const parserLocation = Reflect.get(error, "loc")
2634
+ const located = parserLocation && typeof parserLocation == "object" ? Reflect.get(parserLocation, "index") : undefined
2635
+ const offset = Number.isSafeInteger(direct) ? direct : located
2636
+
2637
+ if (typeof offset != "number" || !Number.isSafeInteger(offset) || offset < 0 || offset > source.length) {
2638
+ return moduleLocation(filename, source)
2639
+ }
2640
+
2641
+ return locationFromOffsets(filename, source, offset, Math.min(offset + 1, source.length))
2642
+ }
2643
+
700
2644
  /**
701
2645
  * Associates Babel nodes with their parser token stream without leaking it into semantic values.
702
2646
  * @param {object} value - Current parser value.