@velarscript/compiler 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +70 -0
- package/dist/analyzer.d.ts +1175 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +12130 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/application-package-host.d.ts +28 -0
- package/dist/application-package-host.d.ts.map +1 -0
- package/dist/application-package-host.js +2 -0
- package/dist/application-package-host.js.map +1 -0
- package/dist/ast.d.ts +853 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +245 -0
- package/dist/ast.js.map +1 -0
- package/dist/class-runtime.d.ts +6 -0
- package/dist/class-runtime.d.ts.map +1 -0
- package/dist/class-runtime.js +45 -0
- package/dist/class-runtime.js.map +1 -0
- package/dist/collection-lowering-runtime.d.ts +7 -0
- package/dist/collection-lowering-runtime.d.ts.map +1 -0
- package/dist/collection-lowering-runtime.js +952 -0
- package/dist/collection-lowering-runtime.js.map +1 -0
- package/dist/collection-runtime.d.ts +16 -0
- package/dist/collection-runtime.d.ts.map +1 -0
- package/dist/collection-runtime.js +231 -0
- package/dist/collection-runtime.js.map +1 -0
- package/dist/core-vocabulary.d.ts +141 -0
- package/dist/core-vocabulary.d.ts.map +1 -0
- package/dist/core-vocabulary.js +88 -0
- package/dist/core-vocabulary.js.map +1 -0
- package/dist/diagnostic.d.ts +42 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +66 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/embedded-javascript.d.ts +74 -0
- package/dist/embedded-javascript.d.ts.map +1 -0
- package/dist/embedded-javascript.js +341 -0
- package/dist/embedded-javascript.js.map +1 -0
- package/dist/embedded-module.d.ts +11 -0
- package/dist/embedded-module.d.ts.map +1 -0
- package/dist/embedded-module.js +67 -0
- package/dist/embedded-module.js.map +1 -0
- package/dist/embedded-source.d.ts +19 -0
- package/dist/embedded-source.d.ts.map +1 -0
- package/dist/embedded-source.js +61 -0
- package/dist/embedded-source.js.map +1 -0
- package/dist/emitter.d.ts +188 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +3254 -0
- package/dist/emitter.js.map +1 -0
- package/dist/error-runtime.d.ts +17 -0
- package/dist/error-runtime.d.ts.map +1 -0
- package/dist/error-runtime.js +109 -0
- package/dist/error-runtime.js.map +1 -0
- package/dist/extension.d.ts +373 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +25 -0
- package/dist/extension.js.map +1 -0
- package/dist/formatter.d.ts +13 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +1440 -0
- package/dist/formatter.js.map +1 -0
- package/dist/framework-host.d.ts +87 -0
- package/dist/framework-host.d.ts.map +1 -0
- package/dist/framework-host.js +2 -0
- package/dist/framework-host.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +932 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolated-string.d.ts +47 -0
- package/dist/interpolated-string.d.ts.map +1 -0
- package/dist/interpolated-string.js +344 -0
- package/dist/interpolated-string.js.map +1 -0
- package/dist/json-runtime.d.ts +2 -0
- package/dist/json-runtime.d.ts.map +1 -0
- package/dist/json-runtime.js +193 -0
- package/dist/json-runtime.js.map +1 -0
- package/dist/language-guidance.d.ts +31 -0
- package/dist/language-guidance.d.ts.map +1 -0
- package/dist/language-guidance.js +187 -0
- package/dist/language-guidance.js.map +1 -0
- package/dist/lexer.d.ts +85 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +1004 -0
- package/dist/lexer.js.map +1 -0
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -0
- package/dist/mechanical-fix.d.ts +26 -0
- package/dist/mechanical-fix.d.ts.map +1 -0
- package/dist/mechanical-fix.js +61 -0
- package/dist/mechanical-fix.js.map +1 -0
- package/dist/narrowing-runtime.d.ts +5 -0
- package/dist/narrowing-runtime.d.ts.map +1 -0
- package/dist/narrowing-runtime.js +27 -0
- package/dist/narrowing-runtime.js.map +1 -0
- package/dist/number-runtime.d.ts +3 -0
- package/dist/number-runtime.d.ts.map +1 -0
- package/dist/number-runtime.js +37 -0
- package/dist/number-runtime.js.map +1 -0
- package/dist/parser.d.ts +245 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +3582 -0
- package/dist/parser.js.map +1 -0
- package/dist/primitive-runtime.d.ts +4 -0
- package/dist/primitive-runtime.d.ts.map +1 -0
- package/dist/primitive-runtime.js +39 -0
- package/dist/primitive-runtime.js.map +1 -0
- package/dist/promise-runtime.d.ts +6 -0
- package/dist/promise-runtime.d.ts.map +1 -0
- package/dist/promise-runtime.js +86 -0
- package/dist/promise-runtime.js.map +1 -0
- package/dist/reactive-bridge-runtime.d.ts +7 -0
- package/dist/reactive-bridge-runtime.d.ts.map +1 -0
- package/dist/reactive-bridge-runtime.js +112 -0
- package/dist/reactive-bridge-runtime.js.map +1 -0
- package/dist/runtime-abi.d.ts +24 -0
- package/dist/runtime-abi.d.ts.map +1 -0
- package/dist/runtime-abi.js +24 -0
- package/dist/runtime-abi.js.map +1 -0
- package/dist/semantic.d.ts +142 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +845 -0
- package/dist/semantic.js.map +1 -0
- package/dist/source-names.d.ts +32 -0
- package/dist/source-names.d.ts.map +1 -0
- package/dist/source-names.js +101 -0
- package/dist/source-names.js.map +1 -0
- package/dist/source.d.ts +19 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +64 -0
- package/dist/source.js.map +1 -0
- package/dist/text-runtime.d.ts +2 -0
- package/dist/text-runtime.d.ts.map +1 -0
- package/dist/text-runtime.js +285 -0
- package/dist/text-runtime.js.map +1 -0
- package/dist/token.d.ts +19 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +51 -0
- package/dist/token.js.map +1 -0
- package/dist/type-registry-runtime.d.ts +7 -0
- package/dist/type-registry-runtime.d.ts.map +1 -0
- package/dist/type-registry-runtime.js +51 -0
- package/dist/type-registry-runtime.js.map +1 -0
- package/dist/type-validation-runtime.d.ts +17 -0
- package/dist/type-validation-runtime.d.ts.map +1 -0
- package/dist/type-validation-runtime.js +150 -0
- package/dist/type-validation-runtime.js.map +1 -0
- package/dist/types.d.ts +356 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1170 -0
- package/dist/types.js.map +1 -0
- package/dist/utf8-runtime.d.ts +7 -0
- package/dist/utf8-runtime.d.ts.map +1 -0
- package/dist/utf8-runtime.js +43 -0
- package/dist/utf8-runtime.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
import { Analyzer, inferredResultPlaceholderType, isCorePrimitiveName } from "./analyzer.js";
|
|
2
|
+
import { astNodesOfKind, blockContainsDirectAwait, testFunctionName } from "./ast.js";
|
|
3
|
+
import { diagnostic } from "./diagnostic.js";
|
|
4
|
+
import { JavaScriptEmitter } from "./emitter.js";
|
|
5
|
+
import { programWithEmbeddedJavaScriptImports } from "./embedded-module.js";
|
|
6
|
+
import { Lexer } from "./lexer.js";
|
|
7
|
+
import { isParserComplexityFailure, Parser } from "./parser.js";
|
|
8
|
+
import { SourceText } from "./source.js";
|
|
9
|
+
import { bindingNameRestriction, memberNameRestriction } from "./source-names.js";
|
|
10
|
+
import { buildSemanticIndex } from "./semantic.js";
|
|
11
|
+
import { MAX_VELAR_SOURCE_CODE_UNITS } from "./limits.js";
|
|
12
|
+
import { bindNamedTypeParameters, boolType, genericApplicationType, invalidType, isTypeParameterBound, mergeTypes, nullType, numberType, optionalOf, resolveTypeReference, readonlyViewOf, resolvedAsyncType, stringType, unknownType, } from "./types.js";
|
|
13
|
+
export { diagnostic, formatDiagnostic, mechanicalEdits, mechanicalFix } from "./diagnostic.js";
|
|
14
|
+
export { applyMechanicalFixes } from "./mechanical-fix.js";
|
|
15
|
+
export { formatSource } from "./formatter.js";
|
|
16
|
+
export { collectionMemberGuidance, removedStandardFunctionGuidance, sourceTypeNameGuidance } from "./language-guidance.js";
|
|
17
|
+
export { SourceText } from "./source.js";
|
|
18
|
+
export { MAX_VELAR_SOURCE_CODE_UNITS } from "./limits.js";
|
|
19
|
+
export { bindingNameRestriction, isCoreReservedBinding, isForbiddenPrototypeMember, isJavaScriptReservedBinding, isSourceIdentifierPart, isSourceIdentifierStart, isValidSourceIdentifier, memberNameRestriction } from "./source-names.js";
|
|
20
|
+
export { VELAR_EXTENSION_PROTOCOL_VERSION } from "./extension.js";
|
|
21
|
+
export { CORE_EXPRESSION_CONSTRUCTS, CORE_STATEMENT_CONSTRUCTS, coreStatementConstructKey } from "./ast.js";
|
|
22
|
+
// D62 rule 157: the editor's keyword list is the lexer's table plus Core's
|
|
23
|
+
// contextual roster, so it is published rather than retyped downstream.
|
|
24
|
+
export { keywordKinds } from "./token.js";
|
|
25
|
+
export { semanticImportAt, semanticModuleReferenceAt, semanticSymbolAt, semanticVisibleSymbolsAt } from "./semantic.js";
|
|
26
|
+
export { analysisTypeIdentity, binaryStorageKind, describeType, genericApplicationType, isReadonlyView, optionalOf, readonlyViewOf, semanticTypeIdentity, unionOf, VELAR_BYTES_TYPE_IDENTITY, VELAR_FLOAT32_BUFFER_TYPE_IDENTITY, VELAR_UINT8_BUFFER_TYPE_IDENTITY, VELAR_UINT16_BUFFER_TYPE_IDENTITY, VELAR_UINT32_BUFFER_TYPE_IDENTITY } from "./types.js";
|
|
27
|
+
export { permanentNamespaceCoveringModule } from "./analyzer.js";
|
|
28
|
+
export { CORE_CONTEXTUAL_KEYWORD_WORDS, CORE_CONTEXTUAL_KEYWORDS, CORE_NUMERIC_SUFFIXES, CORE_PRELUDE_NAMES, CORE_STATEMENT_HEAD_KEYWORDS, CORE_VOCABULARY_NAMES, CORE_WORDS, PERMANENT_NAMESPACE_NAMES, TYPE_PARAMETER_DECLARATION_FORMS, typeParameterDeclarationFormsPhrase, } from "./core-vocabulary.js";
|
|
29
|
+
export function inspectModule(text, options = {}) {
|
|
30
|
+
const extensions = normalizedExtensions(options.extensions ?? []);
|
|
31
|
+
const parsed = parseModule(text, options.path ?? "<source>", extensions);
|
|
32
|
+
const semanticProgram = programWithEmbeddedJavaScriptImports(parsed.program, parsed.source.path);
|
|
33
|
+
return {
|
|
34
|
+
diagnostics: parsed.diagnostics,
|
|
35
|
+
source: parsed.source,
|
|
36
|
+
dependencies: dependenciesOf(parsed.program),
|
|
37
|
+
resources: resourcesOf(parsed.program, extensions),
|
|
38
|
+
moduleInterface: interfaceOf(semanticProgram, parsed.source.path, extensions),
|
|
39
|
+
semanticIndex: buildSemanticIndex(semanticProgram, parsed.source, new Map(), new Map(), new Map(), new Map(), new Map(), new Map(), new Map(), new Map(), new Map(), new Map(), extensions.flatMap((extension) => extension.semantic ? [extension.semantic] : [])),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function compile(text, options = {}) {
|
|
43
|
+
try {
|
|
44
|
+
return compileUnchecked(text, options);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!isJavaScriptStackOverflow(error))
|
|
48
|
+
throw error;
|
|
49
|
+
return complexityFailureResult(text, options);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function compileUnchecked(text, options) {
|
|
53
|
+
const extensions = normalizedExtensions(options.extensions ?? []);
|
|
54
|
+
const parsed = parseModule(text, options.path ?? "<source>", extensions);
|
|
55
|
+
const semanticProgram = programWithEmbeddedJavaScriptImports(parsed.program, parsed.source.path);
|
|
56
|
+
const diagnostics = [...parsed.diagnostics];
|
|
57
|
+
const analysisExtensions = extensions.flatMap((extension) => extension.analysis ? [extension.analysis] : []);
|
|
58
|
+
const analyzerExtensions = extensions.filter((extension) => extension.analyzer);
|
|
59
|
+
if (analyzerExtensions.length > 1)
|
|
60
|
+
throw new Error("Only one compiler extension may own semantic analysis");
|
|
61
|
+
const analysisResources = options.resourceContents ?? options.analysis?.resources;
|
|
62
|
+
const analysisContext = {
|
|
63
|
+
...options.analysis,
|
|
64
|
+
path: parsed.source.path,
|
|
65
|
+
...(analysisResources ? { resources: analysisResources } : {}),
|
|
66
|
+
};
|
|
67
|
+
const createAnalyzer = (inferredFunctionResults = new Map(), finalizeFunctionResultInference = false) => {
|
|
68
|
+
const context = {
|
|
69
|
+
...analysisContext,
|
|
70
|
+
inferredFunctionResults,
|
|
71
|
+
finalizeFunctionResultInference,
|
|
72
|
+
};
|
|
73
|
+
return analyzerExtensions[0]?.analyzer?.create(context, analysisExtensions)
|
|
74
|
+
?? new Analyzer(context, analysisExtensions);
|
|
75
|
+
};
|
|
76
|
+
let analyzer = createAnalyzer();
|
|
77
|
+
// Semantic analysis also runs when every earlier diagnostic is a guidance
|
|
78
|
+
// diagnostic that recovered as the guided spelling, so lexer-, parser-, and
|
|
79
|
+
// analyzer-level guidance co-reports in one compile. Compilation still
|
|
80
|
+
// fails: the emission gate below requires zero diagnostics.
|
|
81
|
+
if (diagnostics.every((item) => item.recovered)) {
|
|
82
|
+
// Omitted results use isolated semantic passes so forward and recursive
|
|
83
|
+
// calls converge before the one authoritative diagnostic/lowering pass.
|
|
84
|
+
// Intermediate diagnostics are intentionally discarded.
|
|
85
|
+
const initialDiagnostics = analyzer.analyze(semanticProgram);
|
|
86
|
+
let inferredResults = analyzer.inferredFunctionResults();
|
|
87
|
+
if (inferredResults.size === 0) {
|
|
88
|
+
diagnostics.push(...initialDiagnostics);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const maximumPasses = Math.min(Math.max(inferredResults.size + 2, 4), 256);
|
|
92
|
+
for (let pass = 0; pass < maximumPasses; pass += 1) {
|
|
93
|
+
const probe = createAnalyzer(inferredResults);
|
|
94
|
+
probe.analyze(semanticProgram);
|
|
95
|
+
const next = probe.inferredFunctionResults();
|
|
96
|
+
const stable = Analyzer.inferredFunctionResultsMatch(inferredResults, next);
|
|
97
|
+
inferredResults = next;
|
|
98
|
+
if (stable)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
analyzer = createAnalyzer(inferredResults, true);
|
|
102
|
+
diagnostics.push(...analyzer.analyze(semanticProgram));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
diagnostics.sort((left, right) => left.span.start - right.span.start || left.code.localeCompare(right.code));
|
|
106
|
+
const emitterExtensions = extensions.filter((extension) => extension.createEmitter);
|
|
107
|
+
if (emitterExtensions.length > 1)
|
|
108
|
+
throw new Error("Only one compiler extension may own JavaScript emission");
|
|
109
|
+
const emitterOptions = {
|
|
110
|
+
sourcePath: parsed.source.path,
|
|
111
|
+
...(options.sharedRuntimeModules === undefined ? {} : { sharedRuntimeModules: options.sharedRuntimeModules }),
|
|
112
|
+
};
|
|
113
|
+
const emitter = emitterExtensions[0]?.createEmitter?.(analyzer.loweringHints(), options.exportFunctions ?? new Set(), options.resourceContents ?? new Map(), options.analysis?.extensionImports ?? new Map(), emitterOptions)
|
|
114
|
+
?? new JavaScriptEmitter(analyzer.loweringHints(), options.exportFunctions, emitterOptions);
|
|
115
|
+
const code = diagnostics.length === 0 ? emitter.emit(parsed.program) : null;
|
|
116
|
+
const sourceMap = code === null ? null : emitter.sourceMap(parsed.source);
|
|
117
|
+
const embeddedModules = code === null ? [] : emitter.embeddedModules?.(parsed.source) ?? [];
|
|
118
|
+
const css = code === null ? null : emitter.css?.() ?? null;
|
|
119
|
+
const styleSegments = code === null ? null : emitter.styleSegments?.() ?? null;
|
|
120
|
+
const runtimeModules = code === null ? [] : emitter.runtimeModules?.() ?? [];
|
|
121
|
+
const semanticExpressions = analyzer.semanticExpressions();
|
|
122
|
+
const semanticIndex = buildSemanticIndex(semanticProgram, parsed.source, analyzer.semanticTypes(), analyzer.semanticMembers(), semanticExpressions.types, semanticExpressions.members, semanticExpressions.owners, semanticExpressions.objectPropertyOwners, semanticExpressions.bindingEntryOwners, semanticExpressions.jsxAttributeOwners, semanticExpressions.contexts, semanticExpressions.contextMembers, extensions.flatMap((extension) => extension.semantic ? [extension.semantic] : []));
|
|
123
|
+
return {
|
|
124
|
+
code,
|
|
125
|
+
sourceMap,
|
|
126
|
+
embeddedModules,
|
|
127
|
+
css,
|
|
128
|
+
styleSegments,
|
|
129
|
+
runtimeModules,
|
|
130
|
+
extensions: extensions.map((extension) => extension.id),
|
|
131
|
+
diagnostics,
|
|
132
|
+
source: parsed.source,
|
|
133
|
+
dependencies: dependenciesOf(parsed.program),
|
|
134
|
+
resources: resourcesOf(parsed.program, extensions),
|
|
135
|
+
moduleInterface: interfaceOf(semanticProgram, parsed.source.path, extensions, analyzer.semanticTypes(), analyzer.analyzedClasses()),
|
|
136
|
+
semanticIndex,
|
|
137
|
+
initializationImportReads: analyzer.moduleInitializationImportReads(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function complexityFailureResult(text, options) {
|
|
141
|
+
const path = options.path ?? "<source>";
|
|
142
|
+
const extensions = normalizedExtensions(options.extensions ?? []);
|
|
143
|
+
const source = new SourceText(path, text);
|
|
144
|
+
const program = { kind: "Program", body: [], span: { start: 0, end: 0 } };
|
|
145
|
+
return {
|
|
146
|
+
code: null,
|
|
147
|
+
sourceMap: null,
|
|
148
|
+
embeddedModules: [],
|
|
149
|
+
css: null,
|
|
150
|
+
styleSegments: null,
|
|
151
|
+
runtimeModules: [],
|
|
152
|
+
extensions: extensions.map((extension) => extension.id),
|
|
153
|
+
diagnostics: [diagnostic("VEL2008", "VelarScript source nesting is too complex to process safely", { start: 0, end: Math.min(1, text.length) })],
|
|
154
|
+
source,
|
|
155
|
+
dependencies: [],
|
|
156
|
+
resources: [],
|
|
157
|
+
moduleInterface: interfaceOf(program, path, extensions),
|
|
158
|
+
semanticIndex: buildSemanticIndex(program, source),
|
|
159
|
+
initializationImportReads: [],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function resourcesOf(program, extensions) {
|
|
163
|
+
const output = [];
|
|
164
|
+
const seen = new Set();
|
|
165
|
+
for (const extension of extensions) {
|
|
166
|
+
for (const resource of extension.inspection?.resources?.(program) ?? []) {
|
|
167
|
+
const key = `${resource.kind}\0${resource.source}`;
|
|
168
|
+
if (seen.has(key))
|
|
169
|
+
continue;
|
|
170
|
+
seen.add(key);
|
|
171
|
+
output.push(resource);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return output;
|
|
175
|
+
}
|
|
176
|
+
function parseModule(text, path, extensions) {
|
|
177
|
+
if (text.length > MAX_VELAR_SOURCE_CODE_UNITS) {
|
|
178
|
+
const source = new SourceText(path, text, false);
|
|
179
|
+
return {
|
|
180
|
+
source,
|
|
181
|
+
program: { kind: "Program", body: [], span: { start: 0, end: 0 } },
|
|
182
|
+
diagnostics: [diagnostic("VEL1003", `A VelarScript source module cannot exceed ${MAX_VELAR_SOURCE_CODE_UNITS / 1024 / 1024} MiB`, { start: 0, end: Math.min(1, text.length) })],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const source = new SourceText(path, text);
|
|
186
|
+
try {
|
|
187
|
+
const lexicalExtensions = extensions.flatMap((extension) => extension.lexical ? [extension.lexical] : []);
|
|
188
|
+
const lexed = new Lexer(text, lexicalExtensions).lex();
|
|
189
|
+
// Guidance diagnostics that recovered as the guided token stream do not
|
|
190
|
+
// suppress parsing; only unrecovered lexical failures gate the parser.
|
|
191
|
+
if (lexed.diagnostics.some((item) => (item.code === "VEL1005" || item.code === "VEL1006") && !item.recovered)) {
|
|
192
|
+
return {
|
|
193
|
+
source,
|
|
194
|
+
program: { kind: "Program", body: [], span: { start: 0, end: text.length } },
|
|
195
|
+
diagnostics: lexed.diagnostics,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const parserExtensions = extensions.filter((extension) => extension.parser);
|
|
199
|
+
if (parserExtensions.length > 1)
|
|
200
|
+
throw new Error("Only one compiler extension may own syntax parsing");
|
|
201
|
+
const parser = parserExtensions[0]?.parser?.create(lexed.tokens, lexicalExtensions)
|
|
202
|
+
?? new Parser(lexed.tokens, lexicalExtensions);
|
|
203
|
+
const parsed = parser.parse();
|
|
204
|
+
return { source, program: parsed.program, diagnostics: [...lexed.diagnostics, ...parsed.diagnostics] };
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (!isParserComplexityFailure(error) && !isJavaScriptStackOverflow(error))
|
|
208
|
+
throw error;
|
|
209
|
+
return {
|
|
210
|
+
source,
|
|
211
|
+
program: { kind: "Program", body: [], span: { start: 0, end: 0 } },
|
|
212
|
+
diagnostics: [diagnostic("VEL2008", "VelarScript source nesting is too complex to parse safely", { start: 0, end: Math.min(1, text.length) })],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function isJavaScriptStackOverflow(error) {
|
|
217
|
+
return error instanceof RangeError && /Maximum call stack size exceeded|too much recursion/iu.test(error.message);
|
|
218
|
+
}
|
|
219
|
+
function normalizedExtensions(extensions) {
|
|
220
|
+
const seen = new Set();
|
|
221
|
+
const capabilities = new Set();
|
|
222
|
+
const primitiveOwners = new Map();
|
|
223
|
+
const globalOwners = new Map();
|
|
224
|
+
const extensionReservedBindings = new Set(extensions.flatMap((extension) => [...extension.analysis?.reservedBindings ?? []]));
|
|
225
|
+
for (const extension of extensions) {
|
|
226
|
+
if (!extension.id || seen.has(extension.id))
|
|
227
|
+
throw new Error(`Compiler extension '${extension.id}' is invalid or duplicated`);
|
|
228
|
+
seen.add(extension.id);
|
|
229
|
+
for (const capability of extension.capabilities ?? []) {
|
|
230
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(capability) || capabilities.has(capability)) {
|
|
231
|
+
throw new Error(`Compiler capability '${capability}' is invalid or has more than one owner`);
|
|
232
|
+
}
|
|
233
|
+
capabilities.add(capability);
|
|
234
|
+
}
|
|
235
|
+
for (const name of extension.analysis?.primitiveTypes ?? []) {
|
|
236
|
+
const owner = primitiveOwners.get(name);
|
|
237
|
+
if (isCorePrimitiveName(name))
|
|
238
|
+
throw new Error(`Compiler extension '${extension.id}' cannot replace Core primitive '${name}'`);
|
|
239
|
+
if (bindingNameRestriction(name, extensionReservedBindings) || owner) {
|
|
240
|
+
throw new Error(`Compiler primitive '${name}' is invalid or has more than one owner${owner ? ` (${owner}, ${extension.id})` : ""}`);
|
|
241
|
+
}
|
|
242
|
+
primitiveOwners.set(name, extension.id);
|
|
243
|
+
}
|
|
244
|
+
for (const name of extension.analysis?.globals?.keys() ?? []) {
|
|
245
|
+
const restriction = bindingNameRestriction(name);
|
|
246
|
+
if (restriction === "core")
|
|
247
|
+
throw new Error(`Compiler extension '${extension.id}' cannot replace reserved Core binding '${name}'`);
|
|
248
|
+
if (restriction)
|
|
249
|
+
throw new Error(`Compiler extension '${extension.id}' declares invalid global '${name}'`);
|
|
250
|
+
const owner = globalOwners.get(name);
|
|
251
|
+
if (owner)
|
|
252
|
+
throw new Error(`Compiler global '${name}' has more than one owner (${owner}, ${extension.id})`);
|
|
253
|
+
globalOwners.set(name, extension.id);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const parents = new Map();
|
|
257
|
+
for (const extension of extensions) {
|
|
258
|
+
for (const [name, values] of extension.analysis?.primitiveParents ?? []) {
|
|
259
|
+
if (primitiveOwners.get(name) !== extension.id) {
|
|
260
|
+
throw new Error(`Compiler extension '${extension.id}' cannot define parents for primitive '${name}' that it does not own`);
|
|
261
|
+
}
|
|
262
|
+
const collected = parents.get(name) ?? new Set();
|
|
263
|
+
for (const parent of values) {
|
|
264
|
+
if (!primitiveOwners.has(parent))
|
|
265
|
+
throw new Error(`Compiler primitive '${name}' has unknown parent '${parent}'`);
|
|
266
|
+
collected.add(parent);
|
|
267
|
+
}
|
|
268
|
+
parents.set(name, collected);
|
|
269
|
+
}
|
|
270
|
+
for (const [name, fields] of extension.analysis?.primitiveMutableFields ?? []) {
|
|
271
|
+
if (primitiveOwners.get(name) !== extension.id) {
|
|
272
|
+
throw new Error(`Compiler extension '${extension.id}' cannot make fields writable on primitive '${name}' that it does not own`);
|
|
273
|
+
}
|
|
274
|
+
for (const field of fields) {
|
|
275
|
+
if (memberNameRestriction(field, "data"))
|
|
276
|
+
throw new Error(`Compiler primitive '${name}' has invalid writable field '${field}'`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const completed = new Set();
|
|
281
|
+
const active = new Set();
|
|
282
|
+
const visit = (name) => {
|
|
283
|
+
if (completed.has(name))
|
|
284
|
+
return;
|
|
285
|
+
if (active.has(name))
|
|
286
|
+
throw new Error(`Compiler primitive inheritance contains a cycle at '${name}'`);
|
|
287
|
+
active.add(name);
|
|
288
|
+
for (const parent of parents.get(name) ?? [])
|
|
289
|
+
visit(parent);
|
|
290
|
+
active.delete(name);
|
|
291
|
+
completed.add(name);
|
|
292
|
+
};
|
|
293
|
+
for (const name of primitiveOwners.keys())
|
|
294
|
+
visit(name);
|
|
295
|
+
return extensions;
|
|
296
|
+
}
|
|
297
|
+
// No extension list: dependency discovery asks the AST and nothing else.
|
|
298
|
+
function dependenciesOf(program) {
|
|
299
|
+
const externSources = new Set(program.body
|
|
300
|
+
.filter((statement) => statement.kind === "ExternModuleDeclaration")
|
|
301
|
+
.map((statement) => statement.source));
|
|
302
|
+
const dependencies = program.body
|
|
303
|
+
.filter((statement) => statement.kind === "ImportDeclaration")
|
|
304
|
+
.map((statement) => ({
|
|
305
|
+
source: statement.source,
|
|
306
|
+
span: statement.sourceSpan,
|
|
307
|
+
javascript: statement.javascript,
|
|
308
|
+
unsafe: statement.unsafe,
|
|
309
|
+
dynamic: false,
|
|
310
|
+
...(statement.javascript && !statement.unsafe && externSources.has(statement.source) ? { externOwned: true } : {}),
|
|
311
|
+
specifiers: statement.specifiers.map((specifier) => ({
|
|
312
|
+
imported: specifier.imported,
|
|
313
|
+
local: specifier.local,
|
|
314
|
+
namespace: specifier.namespace,
|
|
315
|
+
})),
|
|
316
|
+
}));
|
|
317
|
+
for (const statement of program.body) {
|
|
318
|
+
if (statement.kind !== "ReExportDeclaration")
|
|
319
|
+
continue;
|
|
320
|
+
dependencies.push({
|
|
321
|
+
source: statement.source,
|
|
322
|
+
span: statement.sourceSpan,
|
|
323
|
+
javascript: false,
|
|
324
|
+
unsafe: false,
|
|
325
|
+
dynamic: false,
|
|
326
|
+
reExport: true,
|
|
327
|
+
specifiers: statement.specifiers.map((specifier) => ({
|
|
328
|
+
imported: specifier.imported,
|
|
329
|
+
local: specifier.exported,
|
|
330
|
+
namespace: false,
|
|
331
|
+
})),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// Inline JavaScript is still part of the project's JavaScript dependency
|
|
335
|
+
// graph. The source spelling no longer decides whether check can see it:
|
|
336
|
+
// Acorn's module parse above supplies the same literal specifiers that the
|
|
337
|
+
// sibling module will execute after emission.
|
|
338
|
+
for (const statement of program.body) {
|
|
339
|
+
if (statement.kind !== "EmbeddedJavaScriptDeclaration")
|
|
340
|
+
continue;
|
|
341
|
+
for (const dependency of statement.dependencies) {
|
|
342
|
+
dependencies.push({
|
|
343
|
+
source: dependency.source,
|
|
344
|
+
span: dependency.span,
|
|
345
|
+
javascript: true,
|
|
346
|
+
unsafe: statement.unsafe,
|
|
347
|
+
dynamic: dependency.dynamic,
|
|
348
|
+
specifiers: [],
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Dynamic imports are found by walking the AST structurally rather than by
|
|
353
|
+
// a second switch over the statement and expression kinds this function
|
|
354
|
+
// remembers. A-010: that switch existed, and it had no case for `try`,
|
|
355
|
+
// `using`, or `test "…":`, and descended into a class only through its
|
|
356
|
+
// fields, `constructor:` and methods — so a `import("./dep.vel")` in a
|
|
357
|
+
// getter, a `@dispose:`, or a `@iterate:` left the module out of the graph
|
|
358
|
+
// while `check` reported success. `@iterate:` was missing from the day D68
|
|
359
|
+
// added it: a hand-kept copy of the AST drifts the moment the AST grows,
|
|
360
|
+
// and every future container would have drifted the same way.
|
|
361
|
+
const dynamicSources = new Set();
|
|
362
|
+
for (const expression of astNodesOfKind(program, "DynamicImportExpression")) {
|
|
363
|
+
if (dynamicSources.has(expression.source))
|
|
364
|
+
continue;
|
|
365
|
+
dynamicSources.add(expression.source);
|
|
366
|
+
dependencies.push({
|
|
367
|
+
source: expression.source,
|
|
368
|
+
span: expression.span,
|
|
369
|
+
javascript: false,
|
|
370
|
+
unsafe: false,
|
|
371
|
+
dynamic: true,
|
|
372
|
+
specifiers: [],
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return dependencies;
|
|
376
|
+
}
|
|
377
|
+
function interfaceOf(program, path, extensions, analyzedBindings = new Map(), analyzedClasses = new Map()) {
|
|
378
|
+
const classIdentities = new Map([["Error", "Error"]]);
|
|
379
|
+
for (const statement of program.body) {
|
|
380
|
+
if (statement.kind === "ClassDeclaration")
|
|
381
|
+
classIdentities.set(statement.name, `velar:${path}#${statement.name}`);
|
|
382
|
+
}
|
|
383
|
+
// Extern classes are nominal per JavaScript source and class name, so
|
|
384
|
+
// signatures that mention them stay nominal across module interfaces. A
|
|
385
|
+
// Velar class declaration owns its bare name if both exist in one module.
|
|
386
|
+
for (const statement of program.body) {
|
|
387
|
+
if (statement.kind !== "ExternModuleDeclaration")
|
|
388
|
+
continue;
|
|
389
|
+
for (const declaration of statement.classes) {
|
|
390
|
+
if (!classIdentities.has(declaration.name))
|
|
391
|
+
classIdentities.set(declaration.name, `js:${statement.source}#${declaration.name}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const enumNames = new Map(program.body
|
|
395
|
+
.filter((statement) => statement.kind === "EnumDeclaration")
|
|
396
|
+
.map((statement) => [statement.name, { identity: `${path}#enum:${statement.name}`, members: new Set(statement.members.map((member) => member.name)) }]));
|
|
397
|
+
const namedTypeIdentities = new Map(program.body
|
|
398
|
+
.filter((statement) => statement.kind === "TypeDeclaration")
|
|
399
|
+
.map((statement) => [statement.name, `velar:${path}#type:${statement.name}`]));
|
|
400
|
+
const aliasDeclarations = new Map();
|
|
401
|
+
for (const statement of program.body) {
|
|
402
|
+
if (statement.kind === "TypeAliasDeclaration")
|
|
403
|
+
aliasDeclarations.set(statement.name, statement);
|
|
404
|
+
}
|
|
405
|
+
const analysisExtensions = extensions.flatMap((extension) => extension.analysis ? [extension.analysis] : []);
|
|
406
|
+
const directAwaitExpression = (expression, contains) => {
|
|
407
|
+
for (const extension of analysisExtensions) {
|
|
408
|
+
const result = extension.directAwaitExpression?.(expression, contains);
|
|
409
|
+
if (result !== undefined)
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
return undefined;
|
|
413
|
+
};
|
|
414
|
+
const directAwaitStatement = (statement, containsExpression, containsBlock) => {
|
|
415
|
+
for (const extension of analysisExtensions) {
|
|
416
|
+
const result = extension.directAwaitStatement?.(statement, containsExpression, containsBlock);
|
|
417
|
+
if (result !== undefined)
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
return undefined;
|
|
421
|
+
};
|
|
422
|
+
const resolveRaw = (reference) => resolveTypeReference(reference, (syntax, nested) => {
|
|
423
|
+
for (const extension of analysisExtensions) {
|
|
424
|
+
const resolved = extension.resolveTypeSyntax?.(syntax, nested);
|
|
425
|
+
if (resolved)
|
|
426
|
+
return resolved;
|
|
427
|
+
}
|
|
428
|
+
return undefined;
|
|
429
|
+
});
|
|
430
|
+
const aliasCache = new Map();
|
|
431
|
+
const expandAliases = (type, seen = new Set()) => {
|
|
432
|
+
if (type.kind === "named" && aliasDeclarations.has(type.name)) {
|
|
433
|
+
if (seen.has(type.name))
|
|
434
|
+
return unknownType;
|
|
435
|
+
const cached = aliasCache.get(type.name);
|
|
436
|
+
if (cached)
|
|
437
|
+
return type.readonlyView ? readonlyViewOf(cached) : cached;
|
|
438
|
+
const declaration = aliasDeclarations.get(type.name);
|
|
439
|
+
const expanded = expandAliases(resolveRaw(declaration.target), new Set([...seen, type.name]));
|
|
440
|
+
aliasCache.set(type.name, expanded);
|
|
441
|
+
return type.readonlyView ? readonlyViewOf(expanded) : expanded;
|
|
442
|
+
}
|
|
443
|
+
// D55 rule 121: an alias inside a type argument is transparent here too,
|
|
444
|
+
// so the interface publishes the same instantiation identity the analyzer
|
|
445
|
+
// computed for the body.
|
|
446
|
+
if (type.kind === "named" && type.application) {
|
|
447
|
+
return { ...type, application: { ...type.application, arguments: type.application.arguments.map((argument) => expandAliases(argument, seen)) } };
|
|
448
|
+
}
|
|
449
|
+
if (type.kind === "optional")
|
|
450
|
+
return optionalOf(expandAliases(type.inner, seen));
|
|
451
|
+
if (type.kind === "list")
|
|
452
|
+
return { ...type, element: expandAliases(type.element, seen) };
|
|
453
|
+
if (type.kind === "set")
|
|
454
|
+
return { ...type, element: expandAliases(type.element, seen) };
|
|
455
|
+
if (type.kind === "map")
|
|
456
|
+
return { ...type, key: expandAliases(type.key, seen), value: expandAliases(type.value, seen) };
|
|
457
|
+
if (type.kind === "record")
|
|
458
|
+
return { ...type, value: expandAliases(type.value, seen) };
|
|
459
|
+
if (type.kind === "promise")
|
|
460
|
+
return { kind: "promise", value: expandAliases(type.value, seen) };
|
|
461
|
+
if (type.kind === "runtimeType")
|
|
462
|
+
return { kind: "runtimeType", value: expandAliases(type.value, seen) };
|
|
463
|
+
if (type.kind === "typeObject")
|
|
464
|
+
return type.value ? { ...type, value: expandAliases(type.value, seen) } : type;
|
|
465
|
+
if (type.kind === "object")
|
|
466
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, expandAliases(value, seen)])) };
|
|
467
|
+
if (type.kind === "extension") {
|
|
468
|
+
return {
|
|
469
|
+
...type,
|
|
470
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, expandAliases(value, seen)])),
|
|
471
|
+
arguments: type.arguments.map((argument) => expandAliases(argument, seen)),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (type.kind === "function" || type.kind === "action" || type.kind === "intrinsic")
|
|
475
|
+
return {
|
|
476
|
+
...type,
|
|
477
|
+
parameters: type.parameters.map((parameter) => expandAliases(parameter, seen)),
|
|
478
|
+
...(type.rest ? { rest: expandAliases(type.rest, seen) } : {}),
|
|
479
|
+
result: expandAliases(type.result, seen),
|
|
480
|
+
};
|
|
481
|
+
if (type.kind === "union")
|
|
482
|
+
return { kind: "union", members: type.members.map((member) => expandAliases(member, seen)) };
|
|
483
|
+
return type;
|
|
484
|
+
};
|
|
485
|
+
const resolve = (reference) => resolveNominals(expandAliases(reference ? resolveRaw(reference) : unknownType), classIdentities, enumNames, namedTypeIdentities);
|
|
486
|
+
const resolveAnalyzed = (type) => resolveNominals(expandAliases(type), classIdentities, enumNames, namedTypeIdentities);
|
|
487
|
+
const resolvedAnalyzedBindings = new Map([...analyzedBindings]
|
|
488
|
+
.map(([name, type]) => [name, resolveNominals(expandAliases(type), classIdentities, enumNames, namedTypeIdentities)]));
|
|
489
|
+
const namedTypes = new Map();
|
|
490
|
+
const namedTypeReadonlyFields = new Map();
|
|
491
|
+
const genericTypes = new Map();
|
|
492
|
+
const typeAliases = new Map();
|
|
493
|
+
const enums = new Map();
|
|
494
|
+
const classes = new Map();
|
|
495
|
+
const exports = new Map();
|
|
496
|
+
const hoistedExports = new Set();
|
|
497
|
+
const mutableExports = new Set();
|
|
498
|
+
const reactiveExports = new Map();
|
|
499
|
+
const inspectionExtensions = extensions.flatMap((extension) => extension.inspection ? [extension.inspection] : []);
|
|
500
|
+
const tests = [];
|
|
501
|
+
const extensionExports = new Map(extensions.map((extension) => [extension.id, new Map()]));
|
|
502
|
+
const extensionData = new Map();
|
|
503
|
+
for (const extension of extensions) {
|
|
504
|
+
const data = extension.inspection?.moduleData?.(program, path);
|
|
505
|
+
if (data !== undefined)
|
|
506
|
+
extensionData.set(extension.id, data);
|
|
507
|
+
const annotations = extension.inspection?.exportAnnotations?.(program);
|
|
508
|
+
if (annotations) {
|
|
509
|
+
const values = extensionExports.get(extension.id);
|
|
510
|
+
for (const [name, value] of annotations)
|
|
511
|
+
values.set(name, value);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
for (const [name, declaration] of aliasDeclarations)
|
|
515
|
+
typeAliases.set(name, resolve(declaration.target));
|
|
516
|
+
for (const statement of program.body) {
|
|
517
|
+
if (statement.kind === "TypeDeclaration") {
|
|
518
|
+
const readonlyFields = new Set(statement.fields.filter((field) => field.readonly).map((field) => field.name));
|
|
519
|
+
// D55 rule 120: a generic record crosses the boundary as a template. Its
|
|
520
|
+
// field table still has the `parameter` kinds in it, which is what lets a
|
|
521
|
+
// dependent instantiate it with an argument this module never named.
|
|
522
|
+
if (statement.typeParameters?.length) {
|
|
523
|
+
const frame = new Map(statement.typeParameters
|
|
524
|
+
.map((parameter, index) => [parameter.name, { kind: "parameter", name: parameter.name, index }]));
|
|
525
|
+
genericTypes.set(statement.name, {
|
|
526
|
+
identity: namedTypeIdentities.get(statement.name),
|
|
527
|
+
name: statement.name,
|
|
528
|
+
parameterNames: statement.typeParameters.map((parameter) => parameter.name),
|
|
529
|
+
parameterBounds: statement.typeParameters.map((parameter) => parameter.bound && isTypeParameterBound(parameter.bound) ? parameter.bound : null),
|
|
530
|
+
fields: new Map(statement.fields.map((field) => [field.name, bindNamedTypeParameters(resolve(field.type), frame)])),
|
|
531
|
+
...(readonlyFields.size > 0 ? { readonlyFields } : {}),
|
|
532
|
+
});
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
namedTypes.set(statement.name, new Map(statement.fields.map((field) => [field.name, resolve(field.type)])));
|
|
536
|
+
if (readonlyFields.size > 0)
|
|
537
|
+
namedTypeReadonlyFields.set(statement.name, readonlyFields);
|
|
538
|
+
}
|
|
539
|
+
else if (statement.kind === "EnumDeclaration") {
|
|
540
|
+
enums.set(statement.name, enumNames.get(statement.name));
|
|
541
|
+
}
|
|
542
|
+
else if (statement.kind === "ClassDeclaration") {
|
|
543
|
+
const fields = new Map([
|
|
544
|
+
...statement.parameters
|
|
545
|
+
.filter((parameter) => parameter.binding && !parameter.private)
|
|
546
|
+
.map((parameter) => [parameter.name, { mutable: parameter.binding === "let", type: resolve(parameter.type) }]),
|
|
547
|
+
...statement.fields
|
|
548
|
+
.filter((field) => !field.static && !field.private)
|
|
549
|
+
.map((field) => [field.name, { mutable: field.binding === "let", type: resolve(field.type) }]),
|
|
550
|
+
...statement.getters
|
|
551
|
+
.filter((getter) => !getter.static && !getter.private)
|
|
552
|
+
.map((getter) => [getter.name, { mutable: false, type: resolve(getter.returnType) }]),
|
|
553
|
+
]);
|
|
554
|
+
const staticFields = new Map(statement.fields
|
|
555
|
+
.filter((field) => field.static && !field.private)
|
|
556
|
+
.map((field) => [field.name, { mutable: field.binding === "let", type: resolve(field.type) }]));
|
|
557
|
+
for (const getter of statement.getters.filter((candidate) => candidate.static && !candidate.private)) {
|
|
558
|
+
staticFields.set(getter.name, { mutable: false, type: resolve(getter.returnType) });
|
|
559
|
+
}
|
|
560
|
+
const methods = new Map(statement.methods.filter((method) => !method.static && !method.private).map((method) => [method.name, functionSignature(method, resolve)]));
|
|
561
|
+
const staticMethods = new Map(statement.methods.filter((method) => method.static && !method.private).map((method) => [method.name, functionSignature(method, resolve)]));
|
|
562
|
+
const identity = classIdentities.get(statement.name);
|
|
563
|
+
classes.set(statement.name, {
|
|
564
|
+
identity,
|
|
565
|
+
// D43 item 69: the release contract crosses the module boundary with
|
|
566
|
+
// the class, so an imported handle stays usable with `using`.
|
|
567
|
+
...(statement.dispose ? {
|
|
568
|
+
dispose: blockContainsDirectAwait(statement.dispose.body, directAwaitExpression, directAwaitStatement) ? "async" : "sync",
|
|
569
|
+
} : {}),
|
|
570
|
+
parameters: statement.parameters.map((parameter) => resolve(parameter.type)),
|
|
571
|
+
parameterNames: statement.parameters.map((parameter) => parameter.name),
|
|
572
|
+
requiredParameters: statement.parameters.filter((parameter) => !parameter.defaultValue).length,
|
|
573
|
+
base: statement.base ? classIdentities.get(statement.base.name) ?? statement.base.name : null,
|
|
574
|
+
abstract: statement.abstract,
|
|
575
|
+
fields,
|
|
576
|
+
getters: new Set(statement.getters.filter((getter) => !getter.static && !getter.private).map((getter) => getter.name)),
|
|
577
|
+
abstractGetters: new Set(statement.getters.filter((getter) => getter.abstract && !getter.private).map((getter) => getter.name)),
|
|
578
|
+
methods,
|
|
579
|
+
abstractMethods: new Set(statement.methods.filter((method) => method.abstract && !method.private).map((method) => method.name)),
|
|
580
|
+
staticFields,
|
|
581
|
+
staticGetters: new Set(statement.getters.filter((getter) => getter.static && !getter.private).map((getter) => getter.name)),
|
|
582
|
+
staticMethods,
|
|
583
|
+
});
|
|
584
|
+
const analyzed = analyzedClasses.get(statement.name);
|
|
585
|
+
if (analyzed) {
|
|
586
|
+
classes.set(statement.name, {
|
|
587
|
+
...analyzed,
|
|
588
|
+
identity,
|
|
589
|
+
parameters: analyzed.parameters.map(resolveAnalyzed),
|
|
590
|
+
...(analyzed.constructorRest ? { constructorRest: resolveAnalyzed(analyzed.constructorRest) } : {}),
|
|
591
|
+
// D68 rule 177: the iteration contract crosses the module boundary
|
|
592
|
+
// with the class, so an imported Bag iterates in the importing module
|
|
593
|
+
// exactly as it does in its own.
|
|
594
|
+
...(analyzed.iterate ? { iterate: resolveAnalyzed(analyzed.iterate) } : {}),
|
|
595
|
+
base: analyzed.base ? classIdentities.get(analyzed.base) ?? analyzed.base : null,
|
|
596
|
+
fields: new Map([...analyzed.fields].map(([name, field]) => [name, { ...field, type: resolveAnalyzed(field.type) }])),
|
|
597
|
+
methods: new Map([...analyzed.methods].map(([name, type]) => [name, resolveAnalyzed(type)])),
|
|
598
|
+
staticFields: new Map([...analyzed.staticFields].map(([name, field]) => [name, { ...field, type: resolveAnalyzed(field.type) }])),
|
|
599
|
+
staticMethods: new Map([...analyzed.staticMethods].map(([name, type]) => [name, resolveAnalyzed(type)])),
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else if (statement.kind === "ExternModuleDeclaration") {
|
|
604
|
+
// Extern classes travel with the interface under their identity so a
|
|
605
|
+
// dependent module that declares the same class for the same source can
|
|
606
|
+
// verify that both declarations agree on one contract.
|
|
607
|
+
for (const declaration of statement.classes) {
|
|
608
|
+
const identity = `js:${statement.source}#${declaration.name}`;
|
|
609
|
+
const analyzed = analyzedClasses.get(identity);
|
|
610
|
+
if (analyzed) {
|
|
611
|
+
classes.set(identity, {
|
|
612
|
+
...analyzed,
|
|
613
|
+
identity,
|
|
614
|
+
parameters: analyzed.parameters.map(resolveAnalyzed),
|
|
615
|
+
...(analyzed.constructorRest ? { constructorRest: resolveAnalyzed(analyzed.constructorRest) } : {}),
|
|
616
|
+
fields: new Map([...analyzed.fields].map(([name, field]) => [name, { ...field, type: resolveAnalyzed(field.type) }])),
|
|
617
|
+
methods: new Map([...analyzed.methods].map(([name, type]) => [name, resolveAnalyzed(type)])),
|
|
618
|
+
staticFields: new Map([...analyzed.staticFields].map(([name, field]) => [name, { ...field, type: resolveAnalyzed(field.type) }])),
|
|
619
|
+
staticMethods: new Map([...analyzed.staticMethods].map(([name, type]) => [name, resolveAnalyzed(type)])),
|
|
620
|
+
});
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const fields = new Map();
|
|
624
|
+
const staticFields = new Map();
|
|
625
|
+
for (const parameter of declaration.parameters) {
|
|
626
|
+
if (parameter.binding)
|
|
627
|
+
fields.set(parameter.name, { mutable: parameter.binding === "let", type: resolve(parameter.type) });
|
|
628
|
+
}
|
|
629
|
+
for (const field of declaration.fields) {
|
|
630
|
+
(field.static ? staticFields : fields).set(field.name, { mutable: field.mutable, type: resolve(field.type) });
|
|
631
|
+
}
|
|
632
|
+
const getters = new Set();
|
|
633
|
+
const staticGetters = new Set();
|
|
634
|
+
for (const getter of declaration.getters) {
|
|
635
|
+
(getter.static ? staticFields : fields).set(getter.name, { mutable: false, type: resolve(getter.type) });
|
|
636
|
+
(getter.static ? staticGetters : getters).add(getter.name);
|
|
637
|
+
}
|
|
638
|
+
const methods = new Map();
|
|
639
|
+
const staticMethods = new Map();
|
|
640
|
+
for (const method of declaration.methods) {
|
|
641
|
+
(method.static ? staticMethods : methods).set(method.name, functionSignature(method, resolve));
|
|
642
|
+
}
|
|
643
|
+
const rest = declaration.parameters.find((parameter) => parameter.rest);
|
|
644
|
+
classes.set(identity, {
|
|
645
|
+
identity,
|
|
646
|
+
parameters: declaration.parameters.filter((parameter) => !parameter.rest).map((parameter) => resolve(parameter.type)),
|
|
647
|
+
requiredParameters: declaration.parameters.filter((parameter) => !parameter.rest && !parameter.defaultValue).length,
|
|
648
|
+
...(rest ? { constructorRest: resolve(rest.type) } : {}),
|
|
649
|
+
base: declaration.base ? `js:${statement.source}#${declaration.base}` : null,
|
|
650
|
+
abstract: false,
|
|
651
|
+
fields,
|
|
652
|
+
getters,
|
|
653
|
+
abstractGetters: new Set(),
|
|
654
|
+
methods,
|
|
655
|
+
abstractMethods: new Set(),
|
|
656
|
+
staticFields,
|
|
657
|
+
staticGetters,
|
|
658
|
+
staticMethods,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else if (statement.kind === "TestDeclaration") {
|
|
663
|
+
tests.push({ name: testFunctionName(statement), title: statement.title });
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
for (const statement of program.body) {
|
|
667
|
+
if (!("exported" in statement) || !statement.exported)
|
|
668
|
+
continue;
|
|
669
|
+
if (statement.kind === "TypeDeclaration") {
|
|
670
|
+
// D55 rule 126: a generic record's export is the instantiation factory,
|
|
671
|
+
// not a Type object — it has no `is` of its own, so it carries no value
|
|
672
|
+
// shape and the analyzer refuses to read it as one.
|
|
673
|
+
exports.set(statement.name, statement.typeParameters?.length
|
|
674
|
+
? { kind: "typeObject", name: statement.name }
|
|
675
|
+
: {
|
|
676
|
+
kind: "typeObject",
|
|
677
|
+
name: statement.name,
|
|
678
|
+
value: {
|
|
679
|
+
kind: "named",
|
|
680
|
+
name: statement.name,
|
|
681
|
+
identity: namedTypeIdentities.get(statement.name),
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
else if (statement.kind === "TypeAliasDeclaration") {
|
|
686
|
+
exports.set(statement.name, { kind: "typeObject", name: statement.name, value: typeAliases.get(statement.name) });
|
|
687
|
+
}
|
|
688
|
+
else if (statement.kind === "EnumDeclaration") {
|
|
689
|
+
const info = enums.get(statement.name);
|
|
690
|
+
exports.set(statement.name, { kind: "enumObject", name: statement.name, identity: info.identity, members: info.members });
|
|
691
|
+
}
|
|
692
|
+
else if (statement.kind === "ClassDeclaration") {
|
|
693
|
+
exports.set(statement.name, { kind: "classConstructor", name: statement.name, identity: classIdentities.get(statement.name) });
|
|
694
|
+
}
|
|
695
|
+
else if (statement.kind === "FunctionDeclaration") {
|
|
696
|
+
exports.set(statement.name, resolvedAnalyzedBindings.get(`${statement.span.start}:${statement.name}`) ?? functionSignature(statement, resolve));
|
|
697
|
+
// `def` emits a JavaScript function declaration, which the host
|
|
698
|
+
// initializes at link time; `class`, `enum`, and `const`/`let` all emit
|
|
699
|
+
// bindings that stay in their temporal dead zone until the module body
|
|
700
|
+
// reaches them.
|
|
701
|
+
hoistedExports.add(statement.name);
|
|
702
|
+
}
|
|
703
|
+
else if (statement.kind === "VariableDeclaration") {
|
|
704
|
+
exportPattern(statement.pattern, statement.type ? resolve(statement.type) : inferPublicExpression(statement.initializer, inspectionExtensions), exports, mutableExports, statement.binding === "let", namedTypes, resolvedAnalyzedBindings);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
for (const extension of extensions) {
|
|
708
|
+
if (!extension.inspection)
|
|
709
|
+
continue;
|
|
710
|
+
const context = {
|
|
711
|
+
exports,
|
|
712
|
+
reactiveExports,
|
|
713
|
+
extensionExports: extensionExports.get(extension.id),
|
|
714
|
+
resolve,
|
|
715
|
+
inferPublicExpression: (expression) => inferPublicExpression(expression, inspectionExtensions),
|
|
716
|
+
bindingType: (name, spanStart) => resolvedAnalyzedBindings.get(`${spanStart}:${name}`) ?? null,
|
|
717
|
+
unresolvedInferredResult: inferredResultPlaceholderType,
|
|
718
|
+
};
|
|
719
|
+
if (extension.inspection.contributeInterface?.(statement, context))
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const reExports = new Map();
|
|
725
|
+
for (const statement of program.body) {
|
|
726
|
+
if (statement.kind !== "ReExportDeclaration")
|
|
727
|
+
continue;
|
|
728
|
+
for (const specifier of statement.specifiers) {
|
|
729
|
+
reExports.set(specifier.exported, { source: statement.source, imported: specifier.imported });
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
exports,
|
|
734
|
+
hoistedExports,
|
|
735
|
+
mutableExports,
|
|
736
|
+
reactiveExports,
|
|
737
|
+
reExports,
|
|
738
|
+
namedTypes,
|
|
739
|
+
namedTypeReadonlyFields,
|
|
740
|
+
namedTypeIdentities,
|
|
741
|
+
...(genericTypes.size > 0 ? { genericTypes } : {}),
|
|
742
|
+
typeAliases,
|
|
743
|
+
enums,
|
|
744
|
+
classes,
|
|
745
|
+
tests,
|
|
746
|
+
extensionExports: new Map([...extensionExports].filter(([, values]) => values.size > 0)),
|
|
747
|
+
extensionData,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function functionSignature(statement, resolve) {
|
|
751
|
+
const frame = new Map();
|
|
752
|
+
// D41 item 61 risk 4: this is the cross-module export interface. A bound
|
|
753
|
+
// dropped here would silently disappear from every imported generic.
|
|
754
|
+
const bounds = [];
|
|
755
|
+
for (const declaration of statement.typeParameters ?? []) {
|
|
756
|
+
if (frame.has(declaration.name))
|
|
757
|
+
continue;
|
|
758
|
+
frame.set(declaration.name, { kind: "parameter", name: declaration.name, index: frame.size });
|
|
759
|
+
bounds.push(declaration.bound && isTypeParameterBound(declaration.bound) ? declaration.bound : null);
|
|
760
|
+
}
|
|
761
|
+
const boundVector = bounds.some((bound) => bound !== null) ? bounds : null;
|
|
762
|
+
const resolveBound = (reference) => frame.size === 0 ? resolve(reference) : bindNamedTypeParameters(resolve(reference), frame);
|
|
763
|
+
const result = statement.returnType
|
|
764
|
+
? resolveBound(statement.returnType)
|
|
765
|
+
: "abstract" in statement && statement.abstract === true ? invalidType : inferredResultPlaceholderType;
|
|
766
|
+
const rest = statement.parameters.find((parameter) => parameter.rest);
|
|
767
|
+
return {
|
|
768
|
+
kind: "function",
|
|
769
|
+
...(frame.size > 0 ? { typeParameterNames: [...frame.keys()] } : {}),
|
|
770
|
+
...(frame.size > 0 && boundVector ? { typeParameterBounds: boundVector } : {}),
|
|
771
|
+
parameters: statement.parameters.filter((parameter) => !parameter.rest).map((parameter) => resolveBound(parameter.type)),
|
|
772
|
+
parameterNames: statement.parameters.filter((parameter) => !parameter.rest).map((parameter) => parameter.name),
|
|
773
|
+
requiredParameters: statement.parameters.filter((parameter) => !parameter.rest && !parameter.defaultValue).length,
|
|
774
|
+
...(rest ? { rest: resolveBound(rest.type) } : {}),
|
|
775
|
+
result: statement.asynchronous ? { kind: "promise", value: resolvedAsyncType(result) } : result,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function resolveNominals(type, classIdentities, enumNames, namedTypeIdentities) {
|
|
779
|
+
// D55 rule 121: the interface is where a module's local names become the
|
|
780
|
+
// identities its dependents see, and an application has to make that crossing
|
|
781
|
+
// whole — the declaration *and* every argument — or the two sides of the
|
|
782
|
+
// boundary would compute two different instantiation identities for one type.
|
|
783
|
+
if (type.kind === "named" && type.application) {
|
|
784
|
+
const arguments_ = type.application.arguments.map((argument) => resolveNominals(argument, classIdentities, enumNames, namedTypeIdentities));
|
|
785
|
+
const declaration = namedTypeIdentities.get(type.application.name) ?? type.application.declaration;
|
|
786
|
+
return genericApplicationType(declaration, type.application.name, arguments_, type.readonlyView === true);
|
|
787
|
+
}
|
|
788
|
+
if (type.kind === "named" && classIdentities.has(type.name)) {
|
|
789
|
+
const identity = classIdentities.get(type.name);
|
|
790
|
+
return {
|
|
791
|
+
kind: "class",
|
|
792
|
+
name: type.name,
|
|
793
|
+
...(identity === type.name ? {} : { identity }),
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
if (type.kind === "named" && enumNames.has(type.name))
|
|
797
|
+
return { kind: "enum", name: type.name, identity: enumNames.get(type.name).identity };
|
|
798
|
+
if (type.kind === "named" && namedTypeIdentities.has(type.name)) {
|
|
799
|
+
return { ...type, identity: namedTypeIdentities.get(type.name) };
|
|
800
|
+
}
|
|
801
|
+
if ((type.kind === "class" || type.kind === "classConstructor") && classIdentities.has(type.name)
|
|
802
|
+
&& (!type.identity || type.identity === type.name)) {
|
|
803
|
+
return { ...type, identity: classIdentities.get(type.name) };
|
|
804
|
+
}
|
|
805
|
+
if ((type.kind === "enum" || type.kind === "enumMember" || type.kind === "enumObject") && enumNames.has(type.name)
|
|
806
|
+
&& type.identity === type.name) {
|
|
807
|
+
return { ...type, identity: enumNames.get(type.name).identity };
|
|
808
|
+
}
|
|
809
|
+
if (type.kind === "optional")
|
|
810
|
+
return optionalOf(resolveNominals(type.inner, classIdentities, enumNames, namedTypeIdentities));
|
|
811
|
+
if (type.kind === "list")
|
|
812
|
+
return { ...type, element: resolveNominals(type.element, classIdentities, enumNames, namedTypeIdentities) };
|
|
813
|
+
if (type.kind === "set")
|
|
814
|
+
return { ...type, element: resolveNominals(type.element, classIdentities, enumNames, namedTypeIdentities) };
|
|
815
|
+
if (type.kind === "map")
|
|
816
|
+
return { ...type, key: resolveNominals(type.key, classIdentities, enumNames, namedTypeIdentities), value: resolveNominals(type.value, classIdentities, enumNames, namedTypeIdentities) };
|
|
817
|
+
if (type.kind === "record")
|
|
818
|
+
return { ...type, value: resolveNominals(type.value, classIdentities, enumNames, namedTypeIdentities) };
|
|
819
|
+
if (type.kind === "promise")
|
|
820
|
+
return { kind: "promise", value: resolveNominals(type.value, classIdentities, enumNames, namedTypeIdentities) };
|
|
821
|
+
if (type.kind === "runtimeType")
|
|
822
|
+
return { kind: "runtimeType", value: resolveNominals(type.value, classIdentities, enumNames, namedTypeIdentities) };
|
|
823
|
+
if (type.kind === "typeObject")
|
|
824
|
+
return type.value
|
|
825
|
+
? { ...type, value: resolveNominals(type.value, classIdentities, enumNames, namedTypeIdentities) }
|
|
826
|
+
: type;
|
|
827
|
+
if (type.kind === "object")
|
|
828
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, resolveNominals(value, classIdentities, enumNames, namedTypeIdentities)])) };
|
|
829
|
+
if (type.kind === "function" || type.kind === "action" || type.kind === "intrinsic")
|
|
830
|
+
return {
|
|
831
|
+
...type,
|
|
832
|
+
parameters: type.parameters.map((parameter) => resolveNominals(parameter, classIdentities, enumNames, namedTypeIdentities)),
|
|
833
|
+
...(type.rest ? { rest: resolveNominals(type.rest, classIdentities, enumNames, namedTypeIdentities) } : {}),
|
|
834
|
+
result: resolveNominals(type.result, classIdentities, enumNames, namedTypeIdentities),
|
|
835
|
+
};
|
|
836
|
+
if (type.kind === "union")
|
|
837
|
+
return { kind: "union", members: type.members.map((member) => resolveNominals(member, classIdentities, enumNames, namedTypeIdentities)) };
|
|
838
|
+
if (type.kind === "extension")
|
|
839
|
+
return {
|
|
840
|
+
...type,
|
|
841
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, resolveNominals(value, classIdentities, enumNames, namedTypeIdentities)])),
|
|
842
|
+
arguments: type.arguments.map((argument) => resolveNominals(argument, classIdentities, enumNames, namedTypeIdentities)),
|
|
843
|
+
};
|
|
844
|
+
return type;
|
|
845
|
+
}
|
|
846
|
+
function inferPublicExpression(expression, extensions) {
|
|
847
|
+
for (const extension of extensions) {
|
|
848
|
+
const inferred = extension.inferPublicExpression?.(expression);
|
|
849
|
+
if (inferred)
|
|
850
|
+
return inferred;
|
|
851
|
+
}
|
|
852
|
+
switch (expression.kind) {
|
|
853
|
+
case "LiteralExpression":
|
|
854
|
+
return expression.value === null ? nullType : typeof expression.value === "string" ? stringType : typeof expression.value === "number" ? numberType : boolType;
|
|
855
|
+
case "FStringExpression":
|
|
856
|
+
return stringType;
|
|
857
|
+
case "ListExpression": {
|
|
858
|
+
let element = unknownType;
|
|
859
|
+
for (const item of expression.elements) {
|
|
860
|
+
const type = inferPublicExpression(item.kind === "SpreadExpression" ? item.value : item, extensions);
|
|
861
|
+
element = mergeTypes(element, item.kind === "SpreadExpression" && type.kind === "list" ? type.element : type);
|
|
862
|
+
}
|
|
863
|
+
return { kind: "list", element };
|
|
864
|
+
}
|
|
865
|
+
case "ObjectExpression": {
|
|
866
|
+
const fields = new Map();
|
|
867
|
+
const optionalFields = new Set();
|
|
868
|
+
for (const property of expression.properties) {
|
|
869
|
+
if (property.kind === "ObjectProperty") {
|
|
870
|
+
fields.set(property.name, inferPublicExpression(property.value, extensions));
|
|
871
|
+
optionalFields.delete(property.name);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
const spread = inferPublicExpression(property.value, extensions);
|
|
875
|
+
if (spread.kind === "object")
|
|
876
|
+
for (const [name, type] of spread.fields) {
|
|
877
|
+
const alreadyRequired = fields.has(name) && !optionalFields.has(name);
|
|
878
|
+
fields.set(name, type);
|
|
879
|
+
if (!alreadyRequired && spread.optionalFields?.has(name))
|
|
880
|
+
optionalFields.add(name);
|
|
881
|
+
else
|
|
882
|
+
optionalFields.delete(name);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return { kind: "object", fields, ...(optionalFields.size > 0 ? { optionalFields } : {}) };
|
|
887
|
+
}
|
|
888
|
+
case "SpreadExpression":
|
|
889
|
+
return inferPublicExpression(expression.value, extensions);
|
|
890
|
+
default:
|
|
891
|
+
return unknownType;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function exportPattern(pattern, type, exports, mutableExports, mutable, namedTypes, analyzedBindings = new Map()) {
|
|
895
|
+
if (pattern.kind === "NameBindingPattern") {
|
|
896
|
+
exports.set(pattern.name, analyzedBindings.get(`${pattern.span.start}:${pattern.name}`) ?? type);
|
|
897
|
+
if (mutable)
|
|
898
|
+
mutableExports.add(pattern.name);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (pattern.kind === "ListBindingPattern") {
|
|
902
|
+
const element = type.kind === "list" ? type.element : unknownType;
|
|
903
|
+
for (const child of pattern.elements)
|
|
904
|
+
if (child) {
|
|
905
|
+
exportPattern(child, element, exports, mutableExports, mutable, namedTypes, analyzedBindings);
|
|
906
|
+
}
|
|
907
|
+
if (pattern.rest)
|
|
908
|
+
exports.set(pattern.rest.name, analyzedBindings.get(`${pattern.rest.span.start}:${pattern.rest.name}`) ?? { kind: "list", element });
|
|
909
|
+
if (pattern.rest && mutable)
|
|
910
|
+
mutableExports.add(pattern.rest.name);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const fields = type.kind === "object" ? type.fields : type.kind === "named" ? namedTypes.get(type.name) : null;
|
|
914
|
+
const selected = new Set(pattern.entries.map((entry) => entry.property));
|
|
915
|
+
for (const entry of pattern.entries) {
|
|
916
|
+
const field = fields?.get(entry.property) ?? unknownType;
|
|
917
|
+
exportPattern(entry.pattern, type.kind === "object" && type.optionalFields?.has(entry.property) ? optionalOf(field) : field, exports, mutableExports, mutable, namedTypes, analyzedBindings);
|
|
918
|
+
}
|
|
919
|
+
if (pattern.rest) {
|
|
920
|
+
const optionalFields = type.kind === "object"
|
|
921
|
+
? new Set([...type.optionalFields ?? []].filter((name) => !selected.has(name)))
|
|
922
|
+
: new Set();
|
|
923
|
+
exports.set(pattern.rest.name, analyzedBindings.get(`${pattern.rest.span.start}:${pattern.rest.name}`) ?? {
|
|
924
|
+
kind: "object",
|
|
925
|
+
fields: new Map([...(fields ?? [])].filter(([name]) => !selected.has(name))),
|
|
926
|
+
...(optionalFields.size > 0 ? { optionalFields } : {}),
|
|
927
|
+
});
|
|
928
|
+
if (mutable)
|
|
929
|
+
mutableExports.add(pattern.rest.name);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
//# sourceMappingURL=index.js.map
|