semantifold 0.3.0 → 0.4.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.
- package/README.md +58 -15
- package/build/index.d.ts +48 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +57 -8
- package/build/src/artifact-path.d.ts +23 -1
- package/build/src/artifact-path.d.ts.map +1 -1
- package/build/src/artifact-path.js +45 -3
- package/build/src/artifacts.d.ts.map +1 -1
- package/build/src/artifacts.js +66 -1
- package/build/src/backends/c-validation.d.ts.map +1 -1
- package/build/src/backends/c-validation.js +7 -3
- package/build/src/backends/cpp-validation.d.ts.map +1 -1
- package/build/src/backends/cpp-validation.js +7 -3
- package/build/src/backends/csharp.js +7 -1
- package/build/src/backends/dart-runtime.d.ts +2 -0
- package/build/src/backends/dart-runtime.d.ts.map +1 -0
- package/build/src/backends/dart-runtime.js +41 -0
- package/build/src/backends/dart.d.ts +20 -0
- package/build/src/backends/dart.d.ts.map +1 -0
- package/build/src/backends/dart.js +321 -0
- package/build/src/backends/effects.d.ts +60 -0
- package/build/src/backends/effects.d.ts.map +1 -0
- package/build/src/backends/effects.js +746 -0
- package/build/src/backends/go.js +18 -4
- package/build/src/backends/identifiers.d.ts +36 -4
- package/build/src/backends/identifiers.d.ts.map +1 -1
- package/build/src/backends/identifiers.js +154 -10
- package/build/src/backends/index.d.ts.map +1 -1
- package/build/src/backends/index.js +27 -4
- package/build/src/backends/ios.d.ts +68 -0
- package/build/src/backends/ios.d.ts.map +1 -0
- package/build/src/backends/ios.js +1503 -0
- package/build/src/backends/java.d.ts.map +1 -1
- package/build/src/backends/java.js +429 -36
- package/build/src/backends/javascript.d.ts +6 -0
- package/build/src/backends/javascript.d.ts.map +1 -1
- package/build/src/backends/javascript.js +380 -24
- package/build/src/backends/kotlin-runtime.d.ts +3 -0
- package/build/src/backends/kotlin-runtime.d.ts.map +1 -0
- package/build/src/backends/kotlin-runtime.js +29 -0
- package/build/src/backends/kotlin.d.ts +23 -0
- package/build/src/backends/kotlin.d.ts.map +1 -0
- package/build/src/backends/kotlin.js +136 -0
- package/build/src/backends/native-validation.d.ts +2 -2
- package/build/src/backends/native-validation.d.ts.map +1 -1
- package/build/src/backends/native-validation.js +2 -2
- package/build/src/backends/ordered-expressions.d.ts.map +1 -1
- package/build/src/backends/ordered-expressions.js +39 -7
- package/build/src/backends/php-stdlib-support.d.ts +19 -0
- package/build/src/backends/php-stdlib-support.d.ts.map +1 -0
- package/build/src/backends/php-stdlib-support.js +227 -0
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +448 -33
- package/build/src/backends/program.d.ts +81 -0
- package/build/src/backends/program.d.ts.map +1 -0
- package/build/src/backends/program.js +1308 -0
- package/build/src/backends/python.js +7 -1
- package/build/src/backends/ruby.d.ts.map +1 -1
- package/build/src/backends/ruby.js +310 -24
- package/build/src/backends/rust-runtime.d.ts +7 -0
- package/build/src/backends/rust-runtime.d.ts.map +1 -0
- package/build/src/backends/rust-runtime.js +32 -0
- package/build/src/backends/rust-validation.d.ts +9 -0
- package/build/src/backends/rust-validation.d.ts.map +1 -0
- package/build/src/backends/rust-validation.js +78 -0
- package/build/src/backends/rust.d.ts +24 -0
- package/build/src/backends/rust.d.ts.map +1 -0
- package/build/src/backends/rust.js +344 -0
- package/build/src/backends/scalars.d.ts +14 -6
- package/build/src/backends/scalars.d.ts.map +1 -1
- package/build/src/backends/scalars.js +167 -17
- package/build/src/backends/shared.d.ts +23 -4
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +2158 -62
- package/build/src/backends/stdlib-support.d.ts +14 -0
- package/build/src/backends/stdlib-support.d.ts.map +1 -0
- package/build/src/backends/stdlib-support.js +237 -0
- package/build/src/backends/swift-runtime.d.ts +3 -0
- package/build/src/backends/swift-runtime.d.ts.map +1 -0
- package/build/src/backends/swift-runtime.js +14 -0
- package/build/src/backends/swift.d.ts +8 -0
- package/build/src/backends/swift.d.ts.map +1 -0
- package/build/src/backends/swift.js +127 -0
- package/build/src/backends/typescript.d.ts.map +1 -1
- package/build/src/backends/typescript.js +344 -24
- package/build/src/backends/wasm.d.ts +43 -0
- package/build/src/backends/wasm.d.ts.map +1 -0
- package/build/src/backends/wasm.js +2164 -0
- package/build/src/backends/writer.d.ts +235 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +468 -3
- package/build/src/diagnostic.d.ts +4 -3
- package/build/src/diagnostic.d.ts.map +1 -1
- package/build/src/diagnostic.js +5 -3
- package/build/src/frontends/c.d.ts.map +1 -1
- package/build/src/frontends/c.js +5 -3
- package/build/src/frontends/cpp.d.ts.map +1 -1
- package/build/src/frontends/cpp.js +5 -3
- package/build/src/frontends/csharp.d.ts.map +1 -1
- package/build/src/frontends/csharp.js +7 -3
- package/build/src/frontends/dart.d.ts +10 -0
- package/build/src/frontends/dart.d.ts.map +1 -0
- package/build/src/frontends/dart.js +913 -0
- package/build/src/frontends/index.d.ts +3 -1
- package/build/src/frontends/index.d.ts.map +1 -1
- package/build/src/frontends/index.js +15 -5
- package/build/src/frontends/java.d.ts +110 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +1725 -70
- package/build/src/frontends/javascript-typescript.d.ts +69 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +1825 -93
- package/build/src/frontends/kotlin.d.ts +10 -0
- package/build/src/frontends/kotlin.d.ts.map +1 -0
- package/build/src/frontends/kotlin.js +685 -0
- package/build/src/frontends/php.d.ts +118 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +1583 -76
- package/build/src/frontends/program.d.ts +74 -0
- package/build/src/frontends/program.d.ts.map +1 -0
- package/build/src/frontends/program.js +989 -0
- package/build/src/frontends/ruby.d.ts +68 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +1446 -101
- package/build/src/frontends/rust-parser.d.ts +11 -0
- package/build/src/frontends/rust-parser.d.ts.map +1 -0
- package/build/src/frontends/rust-parser.js +15 -0
- package/build/src/frontends/rust.d.ts +14 -0
- package/build/src/frontends/rust.d.ts.map +1 -0
- package/build/src/frontends/rust.js +715 -0
- package/build/src/frontends/scalars.d.ts +10 -0
- package/build/src/frontends/scalars.d.ts.map +1 -1
- package/build/src/frontends/scalars.js +28 -2
- package/build/src/frontends/swift.d.ts +10 -0
- package/build/src/frontends/swift.d.ts.map +1 -0
- package/build/src/frontends/swift.js +860 -0
- package/build/src/frontends/types.d.ts +186 -0
- package/build/src/frontends/types.d.ts.map +1 -0
- package/build/src/frontends/types.js +634 -0
- package/build/src/language-registry.d.ts +46 -4
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +167 -13
- package/build/src/mapping.d.ts +2 -2
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +31 -10
- package/build/src/semantic/capabilities.d.ts +66 -0
- package/build/src/semantic/capabilities.d.ts.map +1 -0
- package/build/src/semantic/capabilities.js +649 -0
- package/build/src/semantic/effects.d.ts +26 -0
- package/build/src/semantic/effects.d.ts.map +1 -0
- package/build/src/semantic/effects.js +764 -0
- package/build/src/semantic/generics.d.ts +23 -0
- package/build/src/semantic/generics.d.ts.map +1 -0
- package/build/src/semantic/generics.js +61 -0
- package/build/src/semantic/lifetime.d.ts +37 -0
- package/build/src/semantic/lifetime.d.ts.map +1 -0
- package/build/src/semantic/lifetime.js +527 -0
- package/build/src/semantic/provenance.d.ts +9 -2
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +462 -20
- package/build/src/semantic/scalars.d.ts +11 -0
- package/build/src/semantic/scalars.d.ts.map +1 -1
- package/build/src/semantic/scalars.js +16 -1
- package/build/src/semantic/stdlib.d.ts +254 -0
- package/build/src/semantic/stdlib.d.ts.map +1 -0
- package/build/src/semantic/stdlib.js +880 -0
- package/build/src/semantic/types.d.ts +2952 -202
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +688 -15
- package/build/src/semantic/validate.d.ts +85 -5
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +2576 -98
- package/build/src/stdlib-facades.d.ts +304 -0
- package/build/src/stdlib-facades.d.ts.map +1 -0
- package/build/src/stdlib-facades.js +1167 -0
- package/build/src/stdlib-negotiation.d.ts +69 -0
- package/build/src/stdlib-negotiation.d.ts.map +1 -0
- package/build/src/stdlib-negotiation.js +403 -0
- package/build/src/stdlib-providers.d.ts +72 -0
- package/build/src/stdlib-providers.d.ts.map +1 -0
- package/build/src/stdlib-providers.js +339 -0
- package/build/src/toolchains.d.ts +55 -1
- package/build/src/toolchains.d.ts.map +1 -1
- package/build/src/toolchains.js +15 -4
- package/docs/architecture.md +45 -13
- package/docs/dart.md +60 -0
- package/docs/goals.md +3 -3
- package/docs/ios.md +121 -0
- package/docs/kotlin.md +42 -0
- package/docs/language-support.md +231 -26
- package/docs/multi-file-modules.md +52 -0
- package/docs/parser-qualification.md +121 -8
- package/docs/plans/2026-09-05-standard-library-portability.md +10 -10
- package/docs/rust.md +88 -0
- package/docs/source-maps.md +15 -7
- package/docs/standard-library-portability.md +39 -8
- package/docs/swift.md +44 -0
- package/docs/testing.md +366 -4
- package/docs/webassembly.md +52 -0
- package/index.js +63 -8
- package/node_modules/semantifold-tree-sitter-legacy-internal/README.md +2 -2
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/LICENSE +21 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/README.md +45 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/binding.gyp +30 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding.cc +20 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding_test.js +9 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.d.ts +28 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.js +11 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/grammar.js +1630 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/package.json +64 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/highlights.scm +159 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/injections.scm +9 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/tags.scm +60 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/grammar.json +9405 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/node-types.json +5399 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/parser.c +192569 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/scanner.c +393 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/alloc.h +54 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/array.h +290 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/parser.h +266 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter.json +53 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/package.json +3 -2
- package/node_modules/semantifold-tree-sitter-legacy-internal/src/c.js +5 -4
- package/package.json +5 -2
- package/src/artifact-path.js +47 -2
- package/src/artifacts.js +67 -0
- package/src/backends/c-validation.js +6 -1
- package/src/backends/cpp-validation.js +6 -1
- package/src/backends/csharp.js +3 -0
- package/src/backends/dart-runtime.js +41 -0
- package/src/backends/dart.js +326 -0
- package/src/backends/effects.js +705 -0
- package/src/backends/go.js +14 -2
- package/src/backends/identifiers.js +160 -9
- package/src/backends/index.js +27 -2
- package/src/backends/ios.js +1612 -0
- package/src/backends/java.js +454 -36
- package/src/backends/javascript.js +389 -23
- package/src/backends/kotlin-runtime.js +29 -0
- package/src/backends/kotlin.js +141 -0
- package/src/backends/native-validation.js +1 -1
- package/src/backends/ordered-expressions.js +40 -6
- package/src/backends/php-stdlib-support.js +239 -0
- package/src/backends/php.js +469 -32
- package/src/backends/program.js +1407 -0
- package/src/backends/python.js +3 -0
- package/src/backends/ruby.js +326 -23
- package/src/backends/rust-runtime.js +34 -0
- package/src/backends/rust-validation.js +75 -0
- package/src/backends/rust.js +335 -0
- package/src/backends/scalars.js +141 -16
- package/src/backends/shared.js +2304 -124
- package/src/backends/stdlib-support.js +234 -0
- package/src/backends/swift-runtime.js +14 -0
- package/src/backends/swift.js +132 -0
- package/src/backends/typescript.js +349 -23
- package/src/backends/wasm.js +2208 -0
- package/src/backends/writer.js +520 -2
- package/src/diagnostic.js +4 -2
- package/src/frontends/c.js +4 -2
- package/src/frontends/cpp.js +4 -2
- package/src/frontends/csharp.js +4 -2
- package/src/frontends/dart.js +963 -0
- package/src/frontends/index.js +14 -4
- package/src/frontends/java.js +1977 -69
- package/src/frontends/javascript-typescript.js +2046 -102
- package/src/frontends/kotlin.js +713 -0
- package/src/frontends/php.js +1725 -79
- package/src/frontends/program.js +1062 -0
- package/src/frontends/ruby.js +1624 -98
- package/src/frontends/rust-parser.js +18 -0
- package/src/frontends/rust.js +714 -0
- package/src/frontends/scalars.js +31 -1
- package/src/frontends/swift.js +893 -0
- package/src/frontends/types.js +750 -0
- package/src/language-registry.js +172 -12
- package/src/mapping.js +33 -9
- package/src/semantic/capabilities.js +671 -0
- package/src/semantic/effects.js +737 -0
- package/src/semantic/generics.js +58 -0
- package/src/semantic/lifetime.js +517 -0
- package/src/semantic/provenance.js +412 -19
- package/src/semantic/scalars.js +17 -0
- package/src/semantic/stdlib.js +921 -0
- package/src/semantic/types.js +765 -14
- package/src/semantic/validate.js +2934 -209
- package/src/stdlib-facades.js +1211 -0
- package/src/stdlib-negotiation.js +446 -0
- package/src/stdlib-providers.js +377 -0
- 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 {
|
|
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
|
-
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
710
|
+
if (node.type != "ReturnStatement") return unsupportedSyntax(language, node.type, location)
|
|
254
711
|
|
|
255
|
-
return {
|
|
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
|
-
* @
|
|
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
|
-
|
|
281
|
-
? convertTypeScriptType(declarator.id.typeAnnotation, `Local '${declarator.id.name}'`, declaratorLocation, filename, source
|
|
282
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
|
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 =
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
|
473
|
-
* @param {import("@babel/types").
|
|
474
|
-
* @param {
|
|
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 {
|
|
478
|
-
* @
|
|
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
|
|
481
|
-
const
|
|
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 (
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
?
|
|
508
|
-
:
|
|
509
|
-
|
|
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:
|
|
516
|
-
parameters,
|
|
517
|
-
returnType
|
|
518
|
-
|
|
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
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
2380
|
+
allowAwaitOutsideFunction: true,
|
|
2381
|
+
plugins: language == "typescript" ? ["decorators-legacy", "typescript"] : ["decorators-legacy"],
|
|
691
2382
|
sourceFilename: filename,
|
|
692
|
-
sourceType
|
|
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.
|