@velarscript/cli 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 +117 -0
- package/dist/application-package-host.d.ts +5 -0
- package/dist/application-package-host.d.ts.map +1 -0
- package/dist/application-package-host.js +67 -0
- package/dist/application-package-host.js.map +1 -0
- package/dist/bounded-text.d.ts +2 -0
- package/dist/bounded-text.d.ts.map +1 -0
- package/dist/bounded-text.js +13 -0
- package/dist/bounded-text.js.map +1 -0
- package/dist/browser-process-owner.d.ts +15 -0
- package/dist/browser-process-owner.d.ts.map +1 -0
- package/dist/browser-process-owner.js +151 -0
- package/dist/browser-process-owner.js.map +1 -0
- package/dist/browser-test-runner.d.ts +11 -0
- package/dist/browser-test-runner.d.ts.map +1 -0
- package/dist/browser-test-runner.js +1022 -0
- package/dist/browser-test-runner.js.map +1 -0
- package/dist/build-engine-tool.d.ts +3 -0
- package/dist/build-engine-tool.d.ts.map +1 -0
- package/dist/build-engine-tool.js +34 -0
- package/dist/build-engine-tool.js.map +1 -0
- package/dist/build-staging.d.ts +3 -0
- package/dist/build-staging.d.ts.map +1 -0
- package/dist/build-staging.js +3 -0
- package/dist/build-staging.js.map +1 -0
- package/dist/bundled-application-package-host-registry.d.ts +10 -0
- package/dist/bundled-application-package-host-registry.d.ts.map +1 -0
- package/dist/bundled-application-package-host-registry.js +16 -0
- package/dist/bundled-application-package-host-registry.js.map +1 -0
- package/dist/bundled-extension-registry.d.ts +22 -0
- package/dist/bundled-extension-registry.d.ts.map +1 -0
- package/dist/bundled-extension-registry.js +10 -0
- package/dist/bundled-extension-registry.js.map +1 -0
- package/dist/canonical-path.d.ts +3 -0
- package/dist/canonical-path.d.ts.map +1 -0
- package/dist/canonical-path.js +30 -0
- package/dist/canonical-path.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1274 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +403 -0
- package/dist/config.js.map +1 -0
- package/dist/deployment-verifier.d.ts +34 -0
- package/dist/deployment-verifier.d.ts.map +1 -0
- package/dist/deployment-verifier.js +260 -0
- package/dist/deployment-verifier.js.map +1 -0
- package/dist/dev-server.d.ts +3 -0
- package/dist/dev-server.d.ts.map +1 -0
- package/dist/dev-server.js +464 -0
- package/dist/dev-server.js.map +1 -0
- package/dist/embedded-modules.d.ts +17 -0
- package/dist/embedded-modules.d.ts.map +1 -0
- package/dist/embedded-modules.js +33 -0
- package/dist/embedded-modules.js.map +1 -0
- package/dist/extension-metadata.d.ts +23 -0
- package/dist/extension-metadata.d.ts.map +1 -0
- package/dist/extension-metadata.js +254 -0
- package/dist/extension-metadata.js.map +1 -0
- package/dist/file-integrity.d.ts +7 -0
- package/dist/file-integrity.d.ts.map +1 -0
- package/dist/file-integrity.js +14 -0
- package/dist/file-integrity.js.map +1 -0
- package/dist/framework-host.d.ts +12 -0
- package/dist/framework-host.d.ts.map +1 -0
- package/dist/framework-host.js +46 -0
- package/dist/framework-host.js.map +1 -0
- package/dist/host-error.d.ts +6 -0
- package/dist/host-error.d.ts.map +1 -0
- package/dist/host-error.js +54 -0
- package/dist/host-error.js.map +1 -0
- package/dist/installed-package.d.ts +9 -0
- package/dist/installed-package.d.ts.map +1 -0
- package/dist/installed-package.js +23 -0
- package/dist/installed-package.js.map +1 -0
- package/dist/language-server-bundle-entry.d.ts +2 -0
- package/dist/language-server-bundle-entry.d.ts.map +1 -0
- package/dist/language-server-bundle-entry.js +7 -0
- package/dist/language-server-bundle-entry.js.map +1 -0
- package/dist/language-server-tool.d.ts +3 -0
- package/dist/language-server-tool.d.ts.map +1 -0
- package/dist/language-server-tool.js +30 -0
- package/dist/language-server-tool.js.map +1 -0
- package/dist/language-server.d.ts +9 -0
- package/dist/language-server.d.ts.map +1 -0
- package/dist/language-server.js +1733 -0
- package/dist/language-server.js.map +1 -0
- package/dist/mechanical-fixer.d.ts +30 -0
- package/dist/mechanical-fixer.d.ts.map +1 -0
- package/dist/mechanical-fixer.js +103 -0
- package/dist/mechanical-fixer.js.map +1 -0
- package/dist/module-assets.d.ts +11 -0
- package/dist/module-assets.d.ts.map +1 -0
- package/dist/module-assets.js +90 -0
- package/dist/module-assets.js.map +1 -0
- package/dist/npm.d.ts +23 -0
- package/dist/npm.d.ts.map +1 -0
- package/dist/npm.js +456 -0
- package/dist/npm.js.map +1 -0
- package/dist/official-language-server-extensions.d.ts +2 -0
- package/dist/official-language-server-extensions.d.ts.map +1 -0
- package/dist/official-language-server-extensions.js +38 -0
- package/dist/official-language-server-extensions.js.map +1 -0
- package/dist/official-script-language-service.d.ts +2 -0
- package/dist/official-script-language-service.d.ts.map +1 -0
- package/dist/official-script-language-service.js +10 -0
- package/dist/official-script-language-service.js.map +1 -0
- package/dist/official-tool-assets.d.ts +3 -0
- package/dist/official-tool-assets.d.ts.map +1 -0
- package/dist/official-tool-assets.js +41 -0
- package/dist/official-tool-assets.js.map +1 -0
- package/dist/ownership-graph.d.ts +46 -0
- package/dist/ownership-graph.d.ts.map +1 -0
- package/dist/ownership-graph.js +255 -0
- package/dist/ownership-graph.js.map +1 -0
- package/dist/package-manager.d.ts +20 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +345 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/packaged-official-tool.d.ts +8 -0
- package/dist/packaged-official-tool.d.ts.map +1 -0
- package/dist/packaged-official-tool.js +32 -0
- package/dist/packaged-official-tool.js.map +1 -0
- package/dist/preview-server.d.ts +11 -0
- package/dist/preview-server.d.ts.map +1 -0
- package/dist/preview-server.js +163 -0
- package/dist/preview-server.js.map +1 -0
- package/dist/production-build.d.ts +57 -0
- package/dist/production-build.d.ts.map +1 -0
- package/dist/production-build.js +332 -0
- package/dist/production-build.js.map +1 -0
- package/dist/production-verifier.d.ts +9 -0
- package/dist/production-verifier.d.ts.map +1 -0
- package/dist/production-verifier.js +268 -0
- package/dist/production-verifier.js.map +1 -0
- package/dist/program-runner.d.ts +7 -0
- package/dist/program-runner.d.ts.map +1 -0
- package/dist/program-runner.js +98 -0
- package/dist/program-runner.js.map +1 -0
- package/dist/project-check.d.ts +31 -0
- package/dist/project-check.d.ts.map +1 -0
- package/dist/project-check.js +95 -0
- package/dist/project-check.js.map +1 -0
- package/dist/project-format.d.ts +6 -0
- package/dist/project-format.d.ts.map +1 -0
- package/dist/project-format.js +21 -0
- package/dist/project-format.js.map +1 -0
- package/dist/project-semantic.d.ts +62 -0
- package/dist/project-semantic.d.ts.map +1 -0
- package/dist/project-semantic.js +1079 -0
- package/dist/project-semantic.js.map +1 -0
- package/dist/project-session.d.ts +36 -0
- package/dist/project-session.d.ts.map +1 -0
- package/dist/project-session.js +339 -0
- package/dist/project-session.js.map +1 -0
- package/dist/project-task-bundle-entry.d.ts +2 -0
- package/dist/project-task-bundle-entry.d.ts.map +1 -0
- package/dist/project-task-bundle-entry.js +18 -0
- package/dist/project-task-bundle-entry.js.map +1 -0
- package/dist/project-task-invocation.d.ts +16 -0
- package/dist/project-task-invocation.d.ts.map +1 -0
- package/dist/project-task-invocation.js +72 -0
- package/dist/project-task-invocation.js.map +1 -0
- package/dist/project-task-tool.d.ts +3 -0
- package/dist/project-task-tool.d.ts.map +1 -0
- package/dist/project-task-tool.js +61 -0
- package/dist/project-task-tool.js.map +1 -0
- package/dist/project.d.ts +71 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +1750 -0
- package/dist/project.js.map +1 -0
- package/dist/reproduction.d.ts +50 -0
- package/dist/reproduction.d.ts.map +1 -0
- package/dist/reproduction.js +321 -0
- package/dist/reproduction.js.map +1 -0
- package/dist/script-language-service.d.ts +82 -0
- package/dist/script-language-service.d.ts.map +1 -0
- package/dist/script-language-service.js +18 -0
- package/dist/script-language-service.js.map +1 -0
- package/dist/source-limits.d.ts +5 -0
- package/dist/source-limits.d.ts.map +1 -0
- package/dist/source-limits.js +19 -0
- package/dist/source-limits.js.map +1 -0
- package/dist/standalone-build.d.ts +15 -0
- package/dist/standalone-build.d.ts.map +1 -0
- package/dist/standalone-build.js +76 -0
- package/dist/standalone-build.js.map +1 -0
- package/dist/standard-modules.d.ts +22 -0
- package/dist/standard-modules.d.ts.map +1 -0
- package/dist/standard-modules.js +3066 -0
- package/dist/standard-modules.js.map +1 -0
- package/dist/static-deployment.d.ts +48 -0
- package/dist/static-deployment.d.ts.map +1 -0
- package/dist/static-deployment.js +142 -0
- package/dist/static-deployment.js.map +1 -0
- package/dist/test-output.d.ts +20 -0
- package/dist/test-output.d.ts.map +1 -0
- package/dist/test-output.js +119 -0
- package/dist/test-output.js.map +1 -0
- package/dist/test-runner.d.ts +13 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +226 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/typescript-declarations.d.ts +20 -0
- package/dist/typescript-declarations.d.ts.map +1 -0
- package/dist/typescript-declarations.js +1609 -0
- package/dist/typescript-declarations.js.map +1 -0
- package/dist/uncaught-program-error.d.ts +24 -0
- package/dist/uncaught-program-error.d.ts.map +1 -0
- package/dist/uncaught-program-error.js +105 -0
- package/dist/uncaught-program-error.js.map +1 -0
- package/dist/unowned-errors.d.ts +56 -0
- package/dist/unowned-errors.d.ts.map +1 -0
- package/dist/unowned-errors.js +82 -0
- package/dist/unowned-errors.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/workspace-index.d.ts +78 -0
- package/dist/workspace-index.d.ts.map +1 -0
- package/dist/workspace-index.js +493 -0
- package/dist/workspace-index.js.map +1 -0
- package/package.json +45 -0
- package/skill/ai-skill.md +749 -0
package/dist/project.js
ADDED
|
@@ -0,0 +1,1750 @@
|
|
|
1
|
+
import { createRequire, findPackageJSON, isBuiltin } from "node:module";
|
|
2
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { readdir } from "node:fs/promises";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { analysisTypeIdentity, compile, diagnostic, genericApplicationType, inspectModule, optionalOf, permanentNamespaceCoveringModule, readonlyViewOf, removedStandardFunctionGuidance, } from "@velarscript/compiler";
|
|
6
|
+
import { isNodeOnlyModule, nodeModuleDiagnostic } from "@velarscript/node/compiler";
|
|
7
|
+
import { isStandardModule, standardModuleInterface, standardModuleInterfaces } from "./standard-modules.js";
|
|
8
|
+
import { loadTypeScriptDeclarations } from "./typescript-declarations.js";
|
|
9
|
+
import { MAX_VELAR_PROJECT_MODULES, readVelarSourceFile, validateVelarSourceText } from "./source-limits.js";
|
|
10
|
+
import { readBoundedText } from "./bounded-text.js";
|
|
11
|
+
import { hostErrorMessage, isHostErrorCode } from "./host-error.js";
|
|
12
|
+
import { canonicalizePotentialPath } from "./canonical-path.js";
|
|
13
|
+
function missingExportMessage(source, name) {
|
|
14
|
+
const guidance = removedStandardFunctionGuidance(source, name);
|
|
15
|
+
if (guidance)
|
|
16
|
+
return guidance;
|
|
17
|
+
// MOD-U2: `import name from "..."` is the JavaScript default-import habit;
|
|
18
|
+
// .vel modules have no default export, so the answer teaches the named form
|
|
19
|
+
// instead of implying a default might exist.
|
|
20
|
+
if (name === "default") {
|
|
21
|
+
return `VelarScript modules have no default export; import the names you need — import {name} from ${JSON.stringify(source)}`;
|
|
22
|
+
}
|
|
23
|
+
return `Module '${source}' has no export named '${name}'`;
|
|
24
|
+
}
|
|
25
|
+
export function projectImportKey(importerPath, source) {
|
|
26
|
+
return `${resolve(importerPath)}\0${source}`;
|
|
27
|
+
}
|
|
28
|
+
export async function compileProject(entry, overrides = new Map(), options = {}, previous = null, changedPaths = new Set()) {
|
|
29
|
+
const entryPath = resolve(entry);
|
|
30
|
+
return compileProjectEntries([entryPath], entryPath, overrides, options, previous, changedPaths);
|
|
31
|
+
}
|
|
32
|
+
export async function compileProjectEntries(entries, primaryEntry, overrides = new Map(), options = {}, previous = null, changedPaths = new Set()) {
|
|
33
|
+
const startedAt = performance.now();
|
|
34
|
+
const entryPath = resolve(primaryEntry);
|
|
35
|
+
const sourceRoot = resolve(options.sourceRoot ?? dirname(entryPath));
|
|
36
|
+
const projectRoot = resolve(options.projectRoot ?? sourceRoot);
|
|
37
|
+
const publicRoot = resolve(options.publicRoot ?? join(projectRoot, "public"));
|
|
38
|
+
const compilerExtensions = options.extensions ?? [];
|
|
39
|
+
const extensionConfig = options.extensionConfig ?? new Map();
|
|
40
|
+
const framework = options.framework ?? null;
|
|
41
|
+
const capabilities = new Set(compilerExtensions.flatMap((extension) => extension.capabilities ?? []));
|
|
42
|
+
const initialEntries = [...new Set(entries.map((entry) => resolve(entry)))];
|
|
43
|
+
const pending = initialEntries.slice(0, MAX_VELAR_PROJECT_MODULES).map((inputPath) => ({ inputPath, package: null }));
|
|
44
|
+
const scheduled = new Set(pending.map((module) => module.inputPath));
|
|
45
|
+
const visited = new Set();
|
|
46
|
+
const loaded = new Map();
|
|
47
|
+
const failures = [];
|
|
48
|
+
const notices = [];
|
|
49
|
+
const declarationCache = new Map();
|
|
50
|
+
const externalTypeDependencies = new Map();
|
|
51
|
+
const interfaceCache = new Map();
|
|
52
|
+
const velarPackages = new Map();
|
|
53
|
+
const velarImports = new Map();
|
|
54
|
+
const unsafeCssOwners = new Map();
|
|
55
|
+
const resolutionDiagnostics = new Map();
|
|
56
|
+
const recordResolution = (importerPath, source, code, message) => {
|
|
57
|
+
const list = resolutionDiagnostics.get(importerPath) ?? [];
|
|
58
|
+
list.push({ code, message, source });
|
|
59
|
+
resolutionDiagnostics.set(importerPath, list);
|
|
60
|
+
};
|
|
61
|
+
// The importing statement behind each scheduled module, so a failure that
|
|
62
|
+
// only surfaces when the target is visited (a missing file, a
|
|
63
|
+
// case-divergent duplicate) can still land on the import that caused it.
|
|
64
|
+
const importOrigins = new Map();
|
|
65
|
+
// MOD-D2: one canonical file must be one module. The canonical (real-cased,
|
|
66
|
+
// symlink-resolved) path of every visited module detects a second spelling
|
|
67
|
+
// of the same file before it double-instantiates.
|
|
68
|
+
const canonicalModuleKeys = new Map();
|
|
69
|
+
const javascriptSpecifierVerdicts = new Map();
|
|
70
|
+
const canonicalBoundaries = new Map();
|
|
71
|
+
const canonicalBoundary = (boundary) => {
|
|
72
|
+
let pending = canonicalBoundaries.get(boundary);
|
|
73
|
+
if (!pending) {
|
|
74
|
+
pending = canonicalizePotentialPath(boundary);
|
|
75
|
+
canonicalBoundaries.set(boundary, pending);
|
|
76
|
+
}
|
|
77
|
+
return pending;
|
|
78
|
+
};
|
|
79
|
+
if (initialEntries.length > MAX_VELAR_PROJECT_MODULES) {
|
|
80
|
+
failures.push({ path: entryPath, message: `A VelarScript project cannot contain more than ${MAX_VELAR_PROJECT_MODULES} source modules` });
|
|
81
|
+
}
|
|
82
|
+
const enqueue = (module) => {
|
|
83
|
+
if (scheduled.has(module.inputPath))
|
|
84
|
+
return;
|
|
85
|
+
if (scheduled.size >= MAX_VELAR_PROJECT_MODULES) {
|
|
86
|
+
failures.push({ path: entryPath, message: `A VelarScript project cannot contain more than ${MAX_VELAR_PROJECT_MODULES} source modules` });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
scheduled.add(module.inputPath);
|
|
90
|
+
pending.push(module);
|
|
91
|
+
};
|
|
92
|
+
while (pending.length > 0) {
|
|
93
|
+
const pendingModule = pending.shift();
|
|
94
|
+
const inputPath = pendingModule.inputPath;
|
|
95
|
+
if (visited.has(inputPath))
|
|
96
|
+
continue;
|
|
97
|
+
if (visited.size >= MAX_VELAR_PROJECT_MODULES) {
|
|
98
|
+
failures.push({ path: entryPath, message: `A VelarScript project cannot contain more than ${MAX_VELAR_PROJECT_MODULES} source modules` });
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
visited.add(inputPath);
|
|
102
|
+
const boundary = pendingModule.package?.root ?? sourceRoot;
|
|
103
|
+
const pathWithinBoundary = relative(boundary, inputPath);
|
|
104
|
+
if (escapesRoot(pathWithinBoundary)) {
|
|
105
|
+
failures.push({ path: inputPath, message: pendingModule.package
|
|
106
|
+
? `VelarScript package '${pendingModule.package.name}' cannot load source outside its package root`
|
|
107
|
+
: "Relative VelarScript imports cannot escape the entry source directory" });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let escapesCanonicalBoundary = false;
|
|
111
|
+
let canonicalInput = null;
|
|
112
|
+
try {
|
|
113
|
+
canonicalInput = await canonicalizePotentialPath(inputPath);
|
|
114
|
+
escapesCanonicalBoundary = escapesRoot(relative(await canonicalBoundary(boundary), canonicalInput));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
failures.push({ path: inputPath, message: hostErrorMessage(error) });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (escapesCanonicalBoundary) {
|
|
121
|
+
failures.push({ path: inputPath, message: pendingModule.package
|
|
122
|
+
? `VelarScript package '${pendingModule.package.name}' cannot load source outside its package root`
|
|
123
|
+
: "Relative VelarScript imports cannot escape the entry source directory" });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// MOD-D2: two spellings of one file (differently cased on a
|
|
127
|
+
// case-insensitive filesystem, or reached through a link) would silently
|
|
128
|
+
// instantiate the module twice and split its state. The first spelling
|
|
129
|
+
// wins; the second is rejected on its import.
|
|
130
|
+
{
|
|
131
|
+
const existingSpelling = canonicalModuleKeys.get(canonicalInput);
|
|
132
|
+
if (existingSpelling !== undefined && existingSpelling !== inputPath) {
|
|
133
|
+
const origin = importOrigins.get(inputPath);
|
|
134
|
+
const message = `Module path ${JSON.stringify(origin?.source ?? inputPath)} names the same file as '${existingSpelling}' under a different spelling; a module has one instance, so import it through one spelling (match the on-disk casing)`;
|
|
135
|
+
if (origin)
|
|
136
|
+
recordResolution(origin.importer, origin.source, "VEL6005", message);
|
|
137
|
+
else
|
|
138
|
+
failures.push({ path: inputPath, message });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
canonicalModuleKeys.set(canonicalInput, inputPath);
|
|
142
|
+
}
|
|
143
|
+
let text;
|
|
144
|
+
try {
|
|
145
|
+
const overridden = overrides.get(inputPath);
|
|
146
|
+
text = overridden === undefined
|
|
147
|
+
? await readVelarSourceFile(inputPath)
|
|
148
|
+
: validateVelarSourceText(overridden, inputPath);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
// MOD-U5: a missing module lands on the import that asked for it, in
|
|
152
|
+
// owned words, with the closest on-disk name when one is near.
|
|
153
|
+
const origin = importOrigins.get(inputPath);
|
|
154
|
+
if (origin && isHostErrorCode(error, "ENOENT")) {
|
|
155
|
+
const near = await nearestModuleName(inputPath);
|
|
156
|
+
const suggestion = near === null ? null : origin.source.slice(0, origin.source.lastIndexOf("/") + 1) + near;
|
|
157
|
+
recordResolution(origin.importer, origin.source, "VEL6001", `Module ${JSON.stringify(origin.source)} does not exist${suggestion ? `; did you mean ${JSON.stringify(suggestion)}?` : ""}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
failures.push({ path: inputPath, message: hostErrorMessage(error) });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const relativePath = normalizeModulePath(pendingModule.package
|
|
164
|
+
? join("__velar_packages__", pendingModule.package.name, pathWithinBoundary)
|
|
165
|
+
: relative(sourceRoot, inputPath));
|
|
166
|
+
const inspection = inspectModule(text, { path: inputPath, extensions: compilerExtensions });
|
|
167
|
+
const resourceContents = new Map();
|
|
168
|
+
for (const resource of inspection.resources) {
|
|
169
|
+
if (!resource.source.startsWith(".")) {
|
|
170
|
+
failures.push({ path: inputPath, message: `Compiler resource '${resource.source}' must use a relative path` });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const target = resolve(dirname(inputPath), resource.source);
|
|
174
|
+
if (escapesRoot(relative(boundary, target))) {
|
|
175
|
+
failures.push({ path: inputPath, message: pendingModule.package
|
|
176
|
+
? `Resource '${resource.source}' cannot escape VelarScript package '${pendingModule.package.name}'`
|
|
177
|
+
: `Resource '${resource.source}' cannot escape the entry source directory` });
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
let resourceEscapesCanonicalBoundary = false;
|
|
181
|
+
try {
|
|
182
|
+
resourceEscapesCanonicalBoundary = escapesRoot(relative(await canonicalBoundary(boundary), await canonicalizePotentialPath(target)));
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
failures.push({ path: inputPath, message: `Cannot authorize ${resource.kind} resource '${resource.source}': ${hostErrorMessage(error)}` });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (resourceEscapesCanonicalBoundary) {
|
|
189
|
+
failures.push({ path: inputPath, message: pendingModule.package
|
|
190
|
+
? `Resource '${resource.source}' cannot escape VelarScript package '${pendingModule.package.name}'`
|
|
191
|
+
: `Resource '${resource.source}' cannot escape the entry source directory` });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (resource.kind === "unsafe CSS") {
|
|
195
|
+
const owner = unsafeCssOwners.get(target);
|
|
196
|
+
if (owner && owner !== inputPath) {
|
|
197
|
+
failures.push({
|
|
198
|
+
path: inputPath,
|
|
199
|
+
message: `Unsafe CSS resource '${resource.source}' is already imported by '${relative(sourceRoot, owner)}'; each raw stylesheet must have one project owner`,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
unsafeCssOwners.set(target, inputPath);
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
resourceContents.set(resource.source, overrides.get(target) ?? await readBoundedText(target, 4 * 1024 * 1024, `${resource.kind} resource '${resource.source}'`));
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
failures.push({ path: inputPath, message: `Cannot load ${resource.kind} resource '${resource.source}': ${hostErrorMessage(error)}` });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
loaded.set(inputPath, { inputPath, relativePath, text, inspection, package: pendingModule.package, resourceContents });
|
|
213
|
+
for (const dependency of inspection.dependencies) {
|
|
214
|
+
if (dependency.javascript) {
|
|
215
|
+
if (dependency.source.startsWith(".")) {
|
|
216
|
+
failures.push({
|
|
217
|
+
path: inputPath,
|
|
218
|
+
message: `Relative JavaScript import target '${dependency.source}' cannot be emitted; move the JavaScript module into a package and import it by package name`,
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
// BRG-U2: a bare `import js` specifier resolves at check time. A
|
|
223
|
+
// mistyped package name used to pass check and crash the run with a
|
|
224
|
+
// raw ERR_MODULE_NOT_FOUND pointing at emitted artifacts, and a
|
|
225
|
+
// VelarScript package imported through `import js` crashed the same
|
|
226
|
+
// way while the mirror mistake had teaching.
|
|
227
|
+
if (options.resolveJavaScriptSpecifiers !== false
|
|
228
|
+
&& !dependency.source.startsWith("data:") && !isBuiltin(dependency.source)) {
|
|
229
|
+
const key = projectImportKey(inputPath, dependency.source);
|
|
230
|
+
let verdict = javascriptSpecifierVerdicts.get(key);
|
|
231
|
+
if (verdict === undefined) {
|
|
232
|
+
verdict = await judgeJavaScriptSpecifier(dependency.source, inputPath);
|
|
233
|
+
javascriptSpecifierVerdicts.set(key, verdict);
|
|
234
|
+
}
|
|
235
|
+
if (verdict)
|
|
236
|
+
recordResolution(inputPath, dependency.source, verdict.code, verdict.message);
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (isNodeOnlyModule(dependency.source) && (capabilities.has("web") || framework?.host.target === "browser")
|
|
241
|
+
&& !extensionOwnsStandardModule(dependency.source, compilerExtensions)) {
|
|
242
|
+
failures.push({ path: inputPath, message: nodeModuleDiagnostic(dependency.source) });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (!dependency.source.startsWith(".")) {
|
|
246
|
+
if (isStandardModule(dependency.source, compilerExtensions))
|
|
247
|
+
continue;
|
|
248
|
+
// MOD-U6: `velar/` is the language's own prefix; an unknown name in
|
|
249
|
+
// it lists the modules that exist instead of npm-subpath noise.
|
|
250
|
+
if (dependency.source === "velar" || dependency.source.startsWith("velar/")) {
|
|
251
|
+
const migratedStandard = migratedStandardPackageDiagnostic(dependency.source);
|
|
252
|
+
if (migratedStandard) {
|
|
253
|
+
failures.push({ path: inputPath, message: migratedStandard });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const interfaces = standardModuleInterfaces(compilerExtensions);
|
|
257
|
+
const available = [...interfaces.keys()].sort();
|
|
258
|
+
const near = nearestName(dependency.source, available);
|
|
259
|
+
recordResolution(inputPath, dependency.source, "VEL6003", `Unknown standard module ${JSON.stringify(dependency.source)}${near ? `; did you mean ${JSON.stringify(near)}?` : ""} The standard modules are: ${available.map((module) => standardModuleListing(module, interfaces)).join(", ")}`);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const migrated = migratedStandardPackageDiagnostic(dependency.source);
|
|
263
|
+
if (migrated) {
|
|
264
|
+
failures.push({ path: inputPath, message: migrated });
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
// MOD-U5: the two malformed non-package shapes each teach the
|
|
268
|
+
// relative spelling instead of falling into package resolution.
|
|
269
|
+
if (isAbsolute(dependency.source)) {
|
|
270
|
+
recordResolution(inputPath, dependency.source, "VEL6002", `Module paths are relative to the importing file; write './name.vel' — an absolute path is not a portable project input`);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (dependency.source.endsWith(".vel")) {
|
|
274
|
+
recordResolution(inputPath, dependency.source, "VEL6002", `Import ${JSON.stringify(`./${dependency.source}`)}; a module path without './' names an installed package, not a file`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const package_ = await resolveVelarSourcePackage(dependency.source, inputPath);
|
|
279
|
+
const existing = velarPackages.get(package_.name);
|
|
280
|
+
if (existing && existing.root !== package_.root) {
|
|
281
|
+
recordResolution(inputPath, dependency.source, "VEL6002", `VelarScript package '${package_.name}' resolves to multiple installed versions; use one package instance per application build`);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
velarPackages.set(package_.name, package_);
|
|
285
|
+
velarImports.set(projectImportKey(inputPath, dependency.source), package_.entryPath);
|
|
286
|
+
importOrigins.set(package_.entryPath, { importer: inputPath, source: dependency.source });
|
|
287
|
+
enqueue({ inputPath: package_.entryPath, package: package_ });
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error instanceof JavaScriptOnlyPackageError) {
|
|
291
|
+
recordResolution(inputPath, dependency.source, "VEL6002", `'${dependency.source}' is a JavaScript package, not a VelarScript package; reach it across the bridge — import js {name} from ${JSON.stringify(dependency.source)}, and declare 'extern module ${JSON.stringify(dependency.source)}:' when you want the contract checked`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
recordResolution(inputPath, dependency.source, "VEL6002", `Cannot resolve VelarScript package import '${dependency.source}': ${hostErrorMessage(error)}`);
|
|
295
|
+
}
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (extname(dependency.source) !== ".vel") {
|
|
299
|
+
recordResolution(inputPath, dependency.source, "VEL6001", `VelarScript import '${dependency.source}' must use the .vel extension`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const target = resolve(dirname(inputPath), dependency.source);
|
|
303
|
+
// MOD-D3 / MOD-U8: a module cannot import (or re-export) from itself.
|
|
304
|
+
// The self edge evades the initialization-cycle checker — evaluation
|
|
305
|
+
// order cannot place a module after itself — so the binding crashed
|
|
306
|
+
// with a raw ReferenceError at run time.
|
|
307
|
+
if (target === inputPath) {
|
|
308
|
+
recordResolution(inputPath, dependency.source, "VEL6004", dependency.reExport
|
|
309
|
+
? "A module cannot re-export from itself; declare the binding under the exported name instead"
|
|
310
|
+
: "A module cannot import from itself; use the declaration directly (rename it if the import was an alias)");
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (escapesRoot(relative(boundary, target))) {
|
|
314
|
+
failures.push({ path: inputPath, message: pendingModule.package
|
|
315
|
+
? `Relative import '${dependency.source}' cannot escape VelarScript package '${pendingModule.package.name}'`
|
|
316
|
+
: `Relative import '${dependency.source}' cannot escape the entry source directory` });
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!importOrigins.has(target))
|
|
320
|
+
importOrigins.set(target, { importer: inputPath, source: dependency.source });
|
|
321
|
+
enqueue({ inputPath: target, package: pendingModule.package });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const modules = [];
|
|
325
|
+
const affected = previous ? affectedModules(loaded, velarImports, previous, changedPaths) : new Set(loaded.keys());
|
|
326
|
+
const previousModules = new Map(previous?.modules.map((module) => [module.inputPath, module]));
|
|
327
|
+
for (const [dependency, importers] of previous?.externalTypeDependencies ?? []) {
|
|
328
|
+
for (const importer of importers) {
|
|
329
|
+
if (!loaded.has(importer) || affected.has(importer))
|
|
330
|
+
continue;
|
|
331
|
+
const preserved = externalTypeDependencies.get(dependency) ?? new Set();
|
|
332
|
+
preserved.add(importer);
|
|
333
|
+
externalTypeDependencies.set(dependency, preserved);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const compiledInterfaces = new Map();
|
|
337
|
+
for (const [path, module] of previousModules) {
|
|
338
|
+
if (loaded.has(path) && !affected.has(path))
|
|
339
|
+
compiledInterfaces.set(path, module.result.moduleInterface);
|
|
340
|
+
}
|
|
341
|
+
let compiledModules = 0;
|
|
342
|
+
let reusedModules = 0;
|
|
343
|
+
for (const group of dependencyFirstCompilationGroups(loaded, velarImports, compilerExtensions)) {
|
|
344
|
+
const reusable = group.every((module) => !affected.has(module.inputPath));
|
|
345
|
+
if (reusable) {
|
|
346
|
+
for (const module of group) {
|
|
347
|
+
const previousModule = previousModules.get(module.inputPath);
|
|
348
|
+
modules.push({ ...previousModule, relativePath: module.relativePath });
|
|
349
|
+
reusedModules += 1;
|
|
350
|
+
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
compiledModules += group.length;
|
|
354
|
+
const cyclic = group.length > 1 || group.some((module) => moduleDependencies(module, loaded, velarImports, compilerExtensions).includes(module.inputPath));
|
|
355
|
+
const maximumPasses = cyclic ? group.length + 2 : 1;
|
|
356
|
+
let previousIdentity = "";
|
|
357
|
+
let passResults = new Map();
|
|
358
|
+
for (let pass = 0; pass < maximumPasses; pass += 1) {
|
|
359
|
+
// Dependency-first compilation makes every interface outside this SCC
|
|
360
|
+
// stable. Retain those resolved entries: clearing the whole cache made
|
|
361
|
+
// each parent recursively rebuild the complete transitive chain, so a
|
|
362
|
+
// legal 3000-module line still consumed 3000 host stack frames after
|
|
363
|
+
// the graph algorithms themselves had become iterative. Members of the
|
|
364
|
+
// current SCC are the only entries whose pass can change them.
|
|
365
|
+
for (const module of group)
|
|
366
|
+
interfaceCache.delete(module.inputPath);
|
|
367
|
+
const nextResults = new Map();
|
|
368
|
+
for (const module of group) {
|
|
369
|
+
const analysis = await createAnalysisContext(module, loaded, velarImports, failures, notices, declarationCache, externalTypeDependencies, interfaceCache, compiledInterfaces, compilerExtensions);
|
|
370
|
+
const result = importedReactiveAssignmentDiagnostics(compile(module.text, {
|
|
371
|
+
path: module.inputPath,
|
|
372
|
+
analysis,
|
|
373
|
+
extensions: compilerExtensions,
|
|
374
|
+
resourceContents: module.resourceContents,
|
|
375
|
+
sharedRuntimeModules: true,
|
|
376
|
+
...(options.exportTestFunctions ? { exportFunctions: new Set(module.inspection.moduleInterface.tests.map((item) => item.name)) } : {}),
|
|
377
|
+
}), analysis.reactiveImports ?? new Map());
|
|
378
|
+
nextResults.set(module.inputPath, { inputPath: module.inputPath, relativePath: module.relativePath, result });
|
|
379
|
+
}
|
|
380
|
+
passResults = nextResults;
|
|
381
|
+
for (const [path, compiled] of nextResults)
|
|
382
|
+
compiledInterfaces.set(path, compiled.result.moduleInterface);
|
|
383
|
+
let identity;
|
|
384
|
+
try {
|
|
385
|
+
identity = group
|
|
386
|
+
.map((module) => moduleInterfaceIdentity(nextResults.get(module.inputPath).result.moduleInterface, compilerExtensions))
|
|
387
|
+
.join("\0");
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
failures.push({ path: group[0].inputPath, message: hostErrorMessage(error) });
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
if (!cyclic || identity === previousIdentity)
|
|
394
|
+
break;
|
|
395
|
+
previousIdentity = identity;
|
|
396
|
+
if (pass === maximumPasses - 1) {
|
|
397
|
+
failures.push({ path: group[0].inputPath, message: "Cyclic module interfaces did not converge to a stable type contract" });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
modules.push(...group.map((module) => passResults.get(module.inputPath)));
|
|
401
|
+
}
|
|
402
|
+
appendInitializationCycleDiagnostics(modules, loaded, velarImports, entryPath, resolutionDiagnostics);
|
|
403
|
+
modules.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
404
|
+
if (framework?.host.validateProject) {
|
|
405
|
+
try {
|
|
406
|
+
const messages = framework.host.validateProject({
|
|
407
|
+
config: framework.config,
|
|
408
|
+
modules: modules.map((module) => ({
|
|
409
|
+
path: module.inputPath,
|
|
410
|
+
imports: module.result.dependencies.filter((item) => !item.javascript).map((item) => item.source),
|
|
411
|
+
})),
|
|
412
|
+
});
|
|
413
|
+
for (const message of messages)
|
|
414
|
+
failures.push({ path: entryPath, message });
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
failures.push({ path: entryPath, message: `Application host validation failed: ${hostErrorMessage(error)}` });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
entryPath,
|
|
422
|
+
sourceRoot,
|
|
423
|
+
projectRoot,
|
|
424
|
+
publicRoot,
|
|
425
|
+
compilerExtensions,
|
|
426
|
+
extensionConfig,
|
|
427
|
+
framework,
|
|
428
|
+
capabilities,
|
|
429
|
+
modules,
|
|
430
|
+
failures: uniqueFailures(failures),
|
|
431
|
+
notices: uniqueNotices(notices),
|
|
432
|
+
velarPackages: [...velarPackages.values()],
|
|
433
|
+
velarImports,
|
|
434
|
+
externalTypeDependencies,
|
|
435
|
+
stats: {
|
|
436
|
+
moduleCount: modules.length,
|
|
437
|
+
compiledModules,
|
|
438
|
+
reusedModules,
|
|
439
|
+
affectedModules: [...affected].filter((path) => loaded.has(path)).length,
|
|
440
|
+
durationMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/** Levenshtein distance capped at 3 — enough to answer "is this a near miss". */
|
|
445
|
+
function editDistance(left, right) {
|
|
446
|
+
if (Math.abs(left.length - right.length) > 3)
|
|
447
|
+
return 4;
|
|
448
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
449
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
450
|
+
const current = [leftIndex];
|
|
451
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
452
|
+
current[rightIndex] = Math.min(previous[rightIndex] + 1, current[rightIndex - 1] + 1, previous[rightIndex - 1] + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
453
|
+
}
|
|
454
|
+
previous = current;
|
|
455
|
+
}
|
|
456
|
+
return previous[right.length];
|
|
457
|
+
}
|
|
458
|
+
function nearestName(requested, candidates) {
|
|
459
|
+
let best = null;
|
|
460
|
+
let bestDistance = 3;
|
|
461
|
+
for (const candidate of candidates) {
|
|
462
|
+
if (candidate === requested)
|
|
463
|
+
continue;
|
|
464
|
+
const distance = editDistance(requested, candidate);
|
|
465
|
+
if (distance < bestDistance || (distance === bestDistance && best === null)) {
|
|
466
|
+
best = candidate;
|
|
467
|
+
bestDistance = distance;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return best;
|
|
471
|
+
}
|
|
472
|
+
/** The nearest .vel file name next to a missing module target, if any. */
|
|
473
|
+
async function nearestModuleName(targetPath) {
|
|
474
|
+
try {
|
|
475
|
+
const entries = await readdir(dirname(targetPath), { withFileTypes: true });
|
|
476
|
+
const names = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".vel")).map((entry) => entry.name);
|
|
477
|
+
const wanted = basename(targetPath);
|
|
478
|
+
return nearestName(wanted, names);
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// BRG-U2: judge a bare `import js` specifier at check time. The package must
|
|
485
|
+
// exist next to the importer, and a VelarScript package reached through
|
|
486
|
+
// `import js` gets the reverse-direction teaching.
|
|
487
|
+
async function judgeJavaScriptSpecifier(source, importerPath) {
|
|
488
|
+
if (source.startsWith("velar/compiler-runtime-") && source.endsWith("-v1")) {
|
|
489
|
+
return {
|
|
490
|
+
code: "VEL6006",
|
|
491
|
+
message: `JavaScript import ${JSON.stringify(source)} names a compiler-internal runtime module; it is emitted only as generated implementation support and cannot be imported from VelarScript source`,
|
|
492
|
+
source,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (source.startsWith("node:")) {
|
|
496
|
+
return {
|
|
497
|
+
code: "VEL6006",
|
|
498
|
+
message: `JavaScript builtin import ${JSON.stringify(source)} is not a Node builtin; fix the specifier`,
|
|
499
|
+
source,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (source.startsWith("#")) {
|
|
503
|
+
try {
|
|
504
|
+
createRequire(pathToFileURL(importerPath)).resolve(source);
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
return {
|
|
509
|
+
code: "VEL6006",
|
|
510
|
+
message: `JavaScript package import ${JSON.stringify(source)} is not defined by the importing project's package.json#imports map, or its target does not resolve: ${hostErrorMessage(error)}`,
|
|
511
|
+
source,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
let manifestPath;
|
|
516
|
+
try {
|
|
517
|
+
manifestPath = findPackageJSON(source, pathToFileURL(importerPath)) ?? undefined;
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
manifestPath = undefined;
|
|
521
|
+
}
|
|
522
|
+
if (manifestPath === undefined) {
|
|
523
|
+
return {
|
|
524
|
+
code: "VEL6006",
|
|
525
|
+
message: `JavaScript package import ${JSON.stringify(source)} does not resolve to an installed package; install it, or fix the specifier`,
|
|
526
|
+
source,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
const manifest = JSON.parse(await readBoundedText(manifestPath, 1024 * 1024, `Package manifest for '${source}'`));
|
|
531
|
+
if (typeof manifest.velar?.entry === "string" && manifest.velar.entry.length > 0) {
|
|
532
|
+
const packageName = packageNameOf(source);
|
|
533
|
+
return {
|
|
534
|
+
code: "VEL6006",
|
|
535
|
+
message: `'${packageName}' is a VelarScript package; import it without 'js' — import {name} from ${JSON.stringify(packageName)}`,
|
|
536
|
+
source,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
// An unreadable manifest is the package's own problem; the import stands.
|
|
542
|
+
}
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* D57 rule 136: how one standard module appears in the VEL6003 listing. A
|
|
547
|
+
* module whose every export retired into a permanent namespace is still real —
|
|
548
|
+
* the capability is there, only the spelling changed — so it stays listed and
|
|
549
|
+
* says where its members went. Listing it bare sends the author to an import
|
|
550
|
+
* VEL3008 refuses on the next step, which is the worst kind of diagnostic.
|
|
551
|
+
* The annotation is derived from the migration roster the compiler rejects
|
|
552
|
+
* those imports with, so the listing cannot fall behind a future migration.
|
|
553
|
+
*/
|
|
554
|
+
function standardModuleListing(source, interfaces) {
|
|
555
|
+
const namespace = permanentNamespaceCoveringModule(source, interfaces.get(source)?.exports.keys() ?? []);
|
|
556
|
+
return namespace ? `${source} (its members read as ${namespace}.name and need no import)` : source;
|
|
557
|
+
}
|
|
558
|
+
function migratedStandardPackageDiagnostic(source) {
|
|
559
|
+
if (source === "velar/javascript") {
|
|
560
|
+
return "Standard module 'velar/javascript' moved to package '@velarscript/script-analysis'; install it, then import from '@velarscript/script-analysis'";
|
|
561
|
+
}
|
|
562
|
+
if (source === "velar/text-buffer") {
|
|
563
|
+
return "Standard module 'velar/text-buffer' moved to package '@velarscript/text-buffer'; install it, then import from '@velarscript/text-buffer'";
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
function extensionOwnsStandardModule(source, extensions) {
|
|
568
|
+
return extensions.some((extension) => extension.id !== "@velarscript/node" && extension.modules?.interfaces.has(source));
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* D71 rule 184 widened what `reactiveExports` means without widening its marker:
|
|
572
|
+
* both an exported `state` and an exported `computed` are published as `"state"`
|
|
573
|
+
* so that an imported bare read lowers through `.get()`. The marker therefore
|
|
574
|
+
* says *reactive*, not which word the author wrote — printing it as a noun
|
|
575
|
+
* called an exported `computed` a "state binding" and offered it a mutator it
|
|
576
|
+
* can never have. The message names only what this map establishes, and points
|
|
577
|
+
* at `action`, which is the vocabulary the language actually has; the derived
|
|
578
|
+
* half gets its own sharper VEL5063 from the Web analyzer before it ever
|
|
579
|
+
* reaches here.
|
|
580
|
+
*/
|
|
581
|
+
function importedReactiveAssignmentDiagnostics(result, reactiveImports) {
|
|
582
|
+
if (reactiveImports.size === 0)
|
|
583
|
+
return result;
|
|
584
|
+
const diagnostics = result.diagnostics.map((item) => {
|
|
585
|
+
if (item.code !== "VEL3002" || !item.message.startsWith("Cannot assign to imported binding '"))
|
|
586
|
+
return item;
|
|
587
|
+
const reference = result.semanticIndex.references.find((candidate) => candidate.write
|
|
588
|
+
&& candidate.span.start === item.span.start
|
|
589
|
+
&& candidate.span.end === item.span.end);
|
|
590
|
+
const imported = reference?.symbolId
|
|
591
|
+
? result.semanticIndex.imports.find((candidate) => candidate.localSymbolId === reference.symbolId)
|
|
592
|
+
: null;
|
|
593
|
+
const kind = imported ? reactiveImports.get(imported.local) : null;
|
|
594
|
+
if (!kind)
|
|
595
|
+
return item;
|
|
596
|
+
return {
|
|
597
|
+
...item,
|
|
598
|
+
message: `Cannot assign to imported reactive binding '${imported.local}'; it is read-only here. Export an action from the owning module that changes it and call that instead`,
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
return diagnostics.some((item, index) => item !== result.diagnostics[index])
|
|
602
|
+
? { ...result, diagnostics }
|
|
603
|
+
: result;
|
|
604
|
+
}
|
|
605
|
+
function affectedModules(loaded, velarImports, previous, changedPaths) {
|
|
606
|
+
const affected = new Set([...changedPaths].map((path) => resolve(path)));
|
|
607
|
+
const reverse = new Map();
|
|
608
|
+
const dependencies = (path, values, imports) => {
|
|
609
|
+
for (const dependency of values) {
|
|
610
|
+
if (dependency.javascript)
|
|
611
|
+
continue;
|
|
612
|
+
const target = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
|
|
613
|
+
? resolve(dirname(path), dependency.source)
|
|
614
|
+
: imports.get(projectImportKey(path, dependency.source));
|
|
615
|
+
if (!target)
|
|
616
|
+
continue;
|
|
617
|
+
const dependents = reverse.get(target) ?? new Set();
|
|
618
|
+
dependents.add(path);
|
|
619
|
+
reverse.set(target, dependents);
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
for (const module of loaded.values())
|
|
623
|
+
dependencies(module.inputPath, module.inspection.dependencies, velarImports);
|
|
624
|
+
for (const module of previous.modules)
|
|
625
|
+
dependencies(module.inputPath, module.result.dependencies, previous.velarImports);
|
|
626
|
+
const resources = (path, values) => {
|
|
627
|
+
for (const resource of values) {
|
|
628
|
+
if (!resource.source.startsWith("."))
|
|
629
|
+
continue;
|
|
630
|
+
const target = resolve(dirname(path), resource.source);
|
|
631
|
+
const dependents = reverse.get(target) ?? new Set();
|
|
632
|
+
dependents.add(path);
|
|
633
|
+
reverse.set(target, dependents);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
for (const module of loaded.values())
|
|
637
|
+
resources(module.inputPath, module.inspection.resources);
|
|
638
|
+
for (const module of previous.modules)
|
|
639
|
+
resources(module.inputPath, module.result.resources);
|
|
640
|
+
for (const [dependency, importers] of previous.externalTypeDependencies) {
|
|
641
|
+
const dependents = reverse.get(dependency) ?? new Set();
|
|
642
|
+
for (const importer of importers)
|
|
643
|
+
dependents.add(importer);
|
|
644
|
+
reverse.set(dependency, dependents);
|
|
645
|
+
}
|
|
646
|
+
const pending = [...affected];
|
|
647
|
+
while (pending.length > 0) {
|
|
648
|
+
const path = pending.shift();
|
|
649
|
+
for (const dependent of reverse.get(path) ?? []) {
|
|
650
|
+
if (affected.has(dependent))
|
|
651
|
+
continue;
|
|
652
|
+
affected.add(dependent);
|
|
653
|
+
pending.push(dependent);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const module of loaded.keys())
|
|
657
|
+
if (!previous.modules.some((item) => item.inputPath === module))
|
|
658
|
+
affected.add(module);
|
|
659
|
+
return affected;
|
|
660
|
+
}
|
|
661
|
+
function dependencyFirstCompilationGroups(loaded, velarImports, compilerExtensions) {
|
|
662
|
+
return stronglyConnectedPaths(loaded.keys(), (path) => moduleDependencies(loaded.get(path), loaded, velarImports, compilerExtensions)).map((group) => group
|
|
663
|
+
.map((path) => loaded.get(path))
|
|
664
|
+
.sort((left, right) => left.inputPath.localeCompare(right.inputPath)));
|
|
665
|
+
}
|
|
666
|
+
/** Iterative Tarjan: the public 4096-module bound must not depend on host stack depth. */
|
|
667
|
+
function stronglyConnectedPaths(paths, dependencies) {
|
|
668
|
+
let nextIndex = 0;
|
|
669
|
+
const indexes = new Map();
|
|
670
|
+
const lowLinks = new Map();
|
|
671
|
+
const componentStack = [];
|
|
672
|
+
const active = new Set();
|
|
673
|
+
const groups = [];
|
|
674
|
+
const frames = [];
|
|
675
|
+
const begin = (path, parent) => {
|
|
676
|
+
const index = nextIndex++;
|
|
677
|
+
indexes.set(path, index);
|
|
678
|
+
lowLinks.set(path, index);
|
|
679
|
+
componentStack.push(path);
|
|
680
|
+
active.add(path);
|
|
681
|
+
frames.push({ path, parent, dependencies: dependencies(path), next: 0 });
|
|
682
|
+
};
|
|
683
|
+
for (const root of paths) {
|
|
684
|
+
if (indexes.has(root))
|
|
685
|
+
continue;
|
|
686
|
+
begin(root, null);
|
|
687
|
+
while (frames.length > 0) {
|
|
688
|
+
const frame = frames.at(-1);
|
|
689
|
+
const dependency = frame.dependencies[frame.next];
|
|
690
|
+
if (dependency !== undefined) {
|
|
691
|
+
frame.next += 1;
|
|
692
|
+
if (!indexes.has(dependency)) {
|
|
693
|
+
begin(dependency, frame.path);
|
|
694
|
+
}
|
|
695
|
+
else if (active.has(dependency)) {
|
|
696
|
+
lowLinks.set(frame.path, Math.min(lowLinks.get(frame.path), indexes.get(dependency)));
|
|
697
|
+
}
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
frames.pop();
|
|
701
|
+
if (frame.parent !== null) {
|
|
702
|
+
lowLinks.set(frame.parent, Math.min(lowLinks.get(frame.parent), lowLinks.get(frame.path)));
|
|
703
|
+
}
|
|
704
|
+
if (lowLinks.get(frame.path) !== indexes.get(frame.path))
|
|
705
|
+
continue;
|
|
706
|
+
const group = [];
|
|
707
|
+
while (componentStack.length > 0) {
|
|
708
|
+
const member = componentStack.pop();
|
|
709
|
+
active.delete(member);
|
|
710
|
+
group.push(member);
|
|
711
|
+
if (member === frame.path)
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
groups.push(group);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return groups;
|
|
718
|
+
}
|
|
719
|
+
function moduleDependencies(module, loaded, velarImports, compilerExtensions) {
|
|
720
|
+
const output = new Set();
|
|
721
|
+
for (const dependency of module.inspection.dependencies) {
|
|
722
|
+
if (dependency.javascript || isStandardModule(dependency.source, compilerExtensions))
|
|
723
|
+
continue;
|
|
724
|
+
const target = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
|
|
725
|
+
? resolve(dirname(module.inputPath), dependency.source)
|
|
726
|
+
: velarImports.get(projectImportKey(module.inputPath, dependency.source));
|
|
727
|
+
if (target && loaded.has(target))
|
|
728
|
+
output.add(target);
|
|
729
|
+
}
|
|
730
|
+
return [...output].sort();
|
|
731
|
+
}
|
|
732
|
+
const INITIALIZATION_CYCLE_DIAGNOSTIC = "VEL3019";
|
|
733
|
+
/** MOD-I5: the module-resolution diagnostic family (VEL6xxx). */
|
|
734
|
+
const MODULE_RESOLUTION_DIAGNOSTIC_PREFIX = "VEL6";
|
|
735
|
+
// D31 item 23: module initialization cycles are rejected at compile time.
|
|
736
|
+
// The modules of a static import cycle evaluate in the emitted ESM
|
|
737
|
+
// post-order, so a module-initialization-position read of a binding whose
|
|
738
|
+
// source module evaluates later observes an uninitialized live binding and
|
|
739
|
+
// crashes with a bare ReferenceError. The module graph is fully known here,
|
|
740
|
+
// so the defect is diagnosed on the reading line instead. Reads inside
|
|
741
|
+
// function bodies stay legal — pure function cycles are a proper shape — and
|
|
742
|
+
// cross-module mutually recursive record types never read a binding at all.
|
|
743
|
+
//
|
|
744
|
+
// The verdict is a property of the sources alone: it is computed from one
|
|
745
|
+
// evaluation order over the whole loaded graph, seeded by the project's own
|
|
746
|
+
// entry and then by every remaining evaluation root, never from the caller's
|
|
747
|
+
// entry list. `velar check` (one entry) and the language server (every file is
|
|
748
|
+
// an entry) therefore agree on the same project, and modules a build reaches
|
|
749
|
+
// only through `await import(...)` — additional roots the host does evaluate —
|
|
750
|
+
// are ordered instead of skipped.
|
|
751
|
+
function appendInitializationCycleDiagnostics(modules, loaded, velarImports, entryPath, resolutions = new Map()) {
|
|
752
|
+
const resolveDependency = (importerPath, source) => {
|
|
753
|
+
const target = source.startsWith(".") && extname(source) === ".vel"
|
|
754
|
+
? resolve(dirname(importerPath), source)
|
|
755
|
+
: velarImports.get(projectImportKey(importerPath, source)) ?? null;
|
|
756
|
+
return target !== null && loaded.has(target) ? target : null;
|
|
757
|
+
};
|
|
758
|
+
const carriesCycleDiagnostic = (module) => module.result.diagnostics.some((item) => item.code === INITIALIZATION_CYCLE_DIAGNOSTIC);
|
|
759
|
+
const carriesResolutionDiagnostic = (module) => module.result.diagnostics.some((item) => item.code.startsWith(MODULE_RESOLUTION_DIAGNOSTIC_PREFIX));
|
|
760
|
+
// Nothing to decide and nothing stale to clear: the common project pays only
|
|
761
|
+
// this scan. Every other exit still recomputes from the module's own compile
|
|
762
|
+
// output, so a diagnostic never outlives the cycle (or the resolution
|
|
763
|
+
// failure) that produced it.
|
|
764
|
+
const cycleRelevant = !modules.every((module) => module.result.initializationImportReads.length === 0 && !carriesCycleDiagnostic(module));
|
|
765
|
+
const resolutionRelevant = resolutions.size > 0 || modules.some(carriesResolutionDiagnostic);
|
|
766
|
+
if (!cycleRelevant && !resolutionRelevant)
|
|
767
|
+
return;
|
|
768
|
+
// Static evaluation edges in source order: import and re-export
|
|
769
|
+
// declarations, excluding dynamic imports (they defer evaluation) and
|
|
770
|
+
// JavaScript or standard modules (they are not .vel graph members).
|
|
771
|
+
const staticDependencies = new Map();
|
|
772
|
+
const dynamicRoots = [];
|
|
773
|
+
if (cycleRelevant) {
|
|
774
|
+
for (const [path, module] of loaded) {
|
|
775
|
+
const output = [];
|
|
776
|
+
const seen = new Set();
|
|
777
|
+
for (const reference of module.inspection.semanticIndex.moduleReferences) {
|
|
778
|
+
const target = resolveDependency(path, reference.source);
|
|
779
|
+
if (target === null)
|
|
780
|
+
continue;
|
|
781
|
+
if (reference.dynamic) {
|
|
782
|
+
dynamicRoots.push(target);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (seen.has(target))
|
|
786
|
+
continue;
|
|
787
|
+
seen.add(target);
|
|
788
|
+
output.push(target);
|
|
789
|
+
}
|
|
790
|
+
staticDependencies.set(path, output);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
// Tarjan over the static edges: only strongly connected members can read a
|
|
794
|
+
// later-evaluating module, so everything else is skipped immediately.
|
|
795
|
+
const componentOf = new Map();
|
|
796
|
+
if (cycleRelevant) {
|
|
797
|
+
stronglyConnectedPaths(loaded.keys(), (path) => staticDependencies.get(path) ?? [])
|
|
798
|
+
.forEach((members, component) => {
|
|
799
|
+
for (const member of members)
|
|
800
|
+
componentOf.set(member, component);
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
const componentSizes = new Map();
|
|
804
|
+
for (const component of componentOf.values())
|
|
805
|
+
componentSizes.set(component, (componentSizes.get(component) ?? 0) + 1);
|
|
806
|
+
const cyclic = [...componentSizes.values()].some((size) => size > 1);
|
|
807
|
+
// The ESM evaluation order: dependency-first post-order following
|
|
808
|
+
// declaration order, with in-progress modules skipped exactly as the host
|
|
809
|
+
// module loader skips cycle back-edges. Roots are visited entry first, then
|
|
810
|
+
// dynamic-import targets, then anything else the graph holds, each in a
|
|
811
|
+
// stable order so the same sources always produce the same order.
|
|
812
|
+
const order = new Map();
|
|
813
|
+
if (cyclic) {
|
|
814
|
+
const visiting = new Set();
|
|
815
|
+
const roots = [entryPath, ...[...new Set(dynamicRoots)].sort(), ...[...loaded.keys()].sort()];
|
|
816
|
+
for (const root of roots) {
|
|
817
|
+
if (!loaded.has(root) || order.has(root))
|
|
818
|
+
continue;
|
|
819
|
+
const frames = [];
|
|
820
|
+
const begin = (path) => {
|
|
821
|
+
visiting.add(path);
|
|
822
|
+
frames.push({ path, dependencies: staticDependencies.get(path) ?? [], next: 0 });
|
|
823
|
+
};
|
|
824
|
+
begin(root);
|
|
825
|
+
while (frames.length > 0) {
|
|
826
|
+
const frame = frames.at(-1);
|
|
827
|
+
const dependency = frame.dependencies[frame.next];
|
|
828
|
+
if (dependency !== undefined) {
|
|
829
|
+
frame.next += 1;
|
|
830
|
+
if (!order.has(dependency) && !visiting.has(dependency))
|
|
831
|
+
begin(dependency);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
frames.pop();
|
|
835
|
+
visiting.delete(frame.path);
|
|
836
|
+
if (!order.has(frame.path))
|
|
837
|
+
order.set(frame.path, order.size);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
for (let index = 0; index < modules.length; index += 1) {
|
|
842
|
+
const module = modules[index];
|
|
843
|
+
const path = module.inputPath;
|
|
844
|
+
// The overlay is rebuilt from the module's own compile output every time,
|
|
845
|
+
// so it is idempotent under incremental reuse in both directions: a
|
|
846
|
+
// diagnostic is never duplicated, and a module that leaves a cycle gets its
|
|
847
|
+
// emitted code back.
|
|
848
|
+
const compiled = module.compiledResult ?? module.result;
|
|
849
|
+
const additions = [];
|
|
850
|
+
if (cyclic && (componentSizes.get(componentOf.get(path) ?? -1) ?? 0) > 1) {
|
|
851
|
+
for (const read of compiled.initializationImportReads) {
|
|
852
|
+
const target = originModule(resolveDependency(path, read.source), read.imported, loaded, resolveDependency);
|
|
853
|
+
if (target === null || target === path)
|
|
854
|
+
continue;
|
|
855
|
+
if (componentOf.get(target) !== componentOf.get(path))
|
|
856
|
+
continue;
|
|
857
|
+
// A `def` emits a hoisted function declaration that the host
|
|
858
|
+
// initializes at link time, so a cycle member may call one before the
|
|
859
|
+
// defining module evaluates. Every other export shape is in its
|
|
860
|
+
// temporal dead zone until then.
|
|
861
|
+
if (read.imported !== null && loaded.get(target)?.inspection.moduleInterface.hoistedExports?.has(read.imported))
|
|
862
|
+
continue;
|
|
863
|
+
const modulePosition = order.get(path);
|
|
864
|
+
const targetPosition = order.get(target);
|
|
865
|
+
if (modulePosition === undefined || targetPosition === undefined || targetPosition < modulePosition)
|
|
866
|
+
continue;
|
|
867
|
+
additions.push(diagnostic(INITIALIZATION_CYCLE_DIAGNOSTIC, `Move this read into a function, or extract the shared value into a third module; '${read.source}' has not initialized when this line runs`, read.span));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
// MOD-I5: resolution failures overlay the same way, on the import
|
|
871
|
+
// statement that caused them, rebuilt from the clean compile output so
|
|
872
|
+
// reuse can never duplicate or orphan them.
|
|
873
|
+
{
|
|
874
|
+
const pending = resolutions.get(path) ?? [];
|
|
875
|
+
const seen = new Set();
|
|
876
|
+
for (const item of pending) {
|
|
877
|
+
const reference = compiled.semanticIndex.moduleReferences.find((candidate) => candidate.source === item.source)
|
|
878
|
+
?? compiled.semanticIndex.moduleReferences[0];
|
|
879
|
+
const span = reference?.span ?? { start: 0, end: Math.min(1, compiled.source.text.length) };
|
|
880
|
+
const key = `${item.code}\0${item.message}\0${span.start}`;
|
|
881
|
+
if (seen.has(key))
|
|
882
|
+
continue;
|
|
883
|
+
seen.add(key);
|
|
884
|
+
additions.push(diagnostic(item.code, item.message, span));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (additions.length === 0) {
|
|
888
|
+
if (module.compiledResult === undefined)
|
|
889
|
+
continue;
|
|
890
|
+
modules[index] = { inputPath: module.inputPath, relativePath: module.relativePath, result: compiled };
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
modules[index] = {
|
|
894
|
+
...module,
|
|
895
|
+
compiledResult: compiled,
|
|
896
|
+
result: {
|
|
897
|
+
...compiled,
|
|
898
|
+
// The compile() contract keeps diagnostics ordered by span.
|
|
899
|
+
diagnostics: [...compiled.diagnostics, ...additions]
|
|
900
|
+
.sort((left, right) => left.span.start - right.span.start || left.code.localeCompare(right.code)),
|
|
901
|
+
// The zero-diagnostics gate for code generation holds after the
|
|
902
|
+
// project-level check as well.
|
|
903
|
+
code: null,
|
|
904
|
+
sourceMap: null,
|
|
905
|
+
embeddedModules: [],
|
|
906
|
+
css: null,
|
|
907
|
+
styleSegments: null,
|
|
908
|
+
runtimeModules: [],
|
|
909
|
+
},
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* The module that declares an imported name, following `export {name} from
|
|
915
|
+
* "source"` barrels. Judging a cycle read by the module the import names would
|
|
916
|
+
* let a barrel hide the defining module, whose binding is the one that is
|
|
917
|
+
* actually uninitialized at run time.
|
|
918
|
+
*/
|
|
919
|
+
function originModule(target, imported, loaded, resolveDependency) {
|
|
920
|
+
let current = target;
|
|
921
|
+
let name = imported;
|
|
922
|
+
const seen = new Set();
|
|
923
|
+
while (current !== null && name !== null && !seen.has(`${current}\0${name}`)) {
|
|
924
|
+
seen.add(`${current}\0${name}`);
|
|
925
|
+
const reExport = loaded.get(current)?.inspection.moduleInterface.reExports.get(name);
|
|
926
|
+
if (reExport === undefined)
|
|
927
|
+
return current;
|
|
928
|
+
const next = resolveDependency(current, reExport.source);
|
|
929
|
+
if (next === null)
|
|
930
|
+
return current;
|
|
931
|
+
current = next;
|
|
932
|
+
name = reExport.imported;
|
|
933
|
+
}
|
|
934
|
+
return current;
|
|
935
|
+
}
|
|
936
|
+
export function moduleInterfaceIdentity(interface_, extensions = []) {
|
|
937
|
+
const node = (kind, parts = []) => (`${kind.length}:${kind}${parts.map((part) => `${part.length}:${part}`).join("")}`);
|
|
938
|
+
const typeMap = (values) => node("type-map", [...values]
|
|
939
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
940
|
+
.map(([name, type]) => node("type-entry", [name, analysisTypeIdentity(type)])));
|
|
941
|
+
const names = (values) => node("names", [...values].sort());
|
|
942
|
+
const types = (values) => node("types", values.map(analysisTypeIdentity));
|
|
943
|
+
const namedTypes = node("named-types", [...interface_.namedTypes]
|
|
944
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
945
|
+
.map(([name, fields]) => node("named-type", [name, typeMap(fields)])));
|
|
946
|
+
const namedTypeReadonlyFields = node("named-type-readonly-fields", [...(interface_.namedTypeReadonlyFields ?? new Map())]
|
|
947
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
948
|
+
.map(([name, fields]) => node("named-type-readonly", [name, names(fields)])));
|
|
949
|
+
const namedTypeIdentities = node("named-type-identities", [...interface_.namedTypeIdentities]
|
|
950
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
951
|
+
.map(([name, identity]) => node("named-type-identity", [name, identity])));
|
|
952
|
+
// D55 rule 120, and batch M's lesson one layer out: a dependent compiled
|
|
953
|
+
// against the parameter list, the bounds, *and* the template's field types.
|
|
954
|
+
// A change to any of the three has to invalidate that dependent's cache — a
|
|
955
|
+
// bound that does not enter this hash is a constraint that silently
|
|
956
|
+
// disappears from every module already built against it.
|
|
957
|
+
const genericTypes = node("generic-types", [...(interface_.genericTypes ?? new Map())]
|
|
958
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
959
|
+
.map(([name, info]) => node("generic-type", [
|
|
960
|
+
name,
|
|
961
|
+
info.identity,
|
|
962
|
+
node("parameter-names", info.parameterNames),
|
|
963
|
+
node("parameter-bounds", info.parameterBounds.map((bound) => bound ?? "")),
|
|
964
|
+
typeMap(info.fields),
|
|
965
|
+
names(info.readonlyFields ?? new Set()),
|
|
966
|
+
])));
|
|
967
|
+
const enums = node("enums", [...interface_.enums]
|
|
968
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
969
|
+
.map(([name, info]) => node("enum", [name, info.identity, names(info.members)])));
|
|
970
|
+
const classes = node("classes", [...interface_.classes]
|
|
971
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
972
|
+
.map(([name, info]) => node("class", [
|
|
973
|
+
name,
|
|
974
|
+
info.identity ?? "",
|
|
975
|
+
info.base ?? "",
|
|
976
|
+
info.abstract ? "abstract" : "",
|
|
977
|
+
// A dependent's `using` analysis consumes both the presence of the
|
|
978
|
+
// release contract and whether it must await. Neither is represented by
|
|
979
|
+
// the ordinary class members below, so both states belong here.
|
|
980
|
+
info.dispose ?? "",
|
|
981
|
+
node("parameter-names", info.parameterNames ?? []),
|
|
982
|
+
String(info.requiredParameters),
|
|
983
|
+
types(info.parameters),
|
|
984
|
+
info.constructorRest ? analysisTypeIdentity(info.constructorRest) : "",
|
|
985
|
+
// D68 rule 177: the iteration contract is part of what a dependent
|
|
986
|
+
// compiled against, so changing it has to invalidate the dependent.
|
|
987
|
+
info.iterate ? analysisTypeIdentity(info.iterate) : "",
|
|
988
|
+
names(info.getters),
|
|
989
|
+
names(info.abstractGetters),
|
|
990
|
+
names(info.abstractMethods),
|
|
991
|
+
names(info.staticGetters),
|
|
992
|
+
typeMap(new Map([
|
|
993
|
+
...[...info.fields].map(([field, value]) => [`field:${field}:${value.mutable ? "let" : "const"}`, value.type]),
|
|
994
|
+
...[...info.methods].map(([method, type]) => [`method:${method}`, type]),
|
|
995
|
+
...[...info.staticFields].map(([field, value]) => [`static-field:${field}:${value.mutable ? "let" : "const"}`, value.type]),
|
|
996
|
+
...[...info.staticMethods].map(([method, type]) => [`static-method:${method}`, type]),
|
|
997
|
+
])),
|
|
998
|
+
])));
|
|
999
|
+
const extensionOwners = new Map(extensions.map((extension) => [extension.id, extension]));
|
|
1000
|
+
let extensionIdentitySize = 0;
|
|
1001
|
+
const extensionSegment = (value) => {
|
|
1002
|
+
extensionIdentitySize += value.length;
|
|
1003
|
+
if (extensionIdentitySize > 1024 * 1024) {
|
|
1004
|
+
throw new Error("Compiler extension interface identities cannot exceed 1 MiB per module");
|
|
1005
|
+
}
|
|
1006
|
+
return `${value.length}:${value}`;
|
|
1007
|
+
};
|
|
1008
|
+
const extensionExports = node("extension-exports", [...interface_.extensionExports]
|
|
1009
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1010
|
+
.map(([extensionId, values]) => {
|
|
1011
|
+
const identify = extensionOwners.get(extensionId)?.inspection?.interfaceExportIdentity;
|
|
1012
|
+
if (!identify) {
|
|
1013
|
+
throw new Error(`Compiler extension '${extensionId}' exports cross-module interface data without an interfaceExportIdentity contract`);
|
|
1014
|
+
}
|
|
1015
|
+
const entries = [...values]
|
|
1016
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1017
|
+
.map(([name, value]) => {
|
|
1018
|
+
const identity = identify(name, value);
|
|
1019
|
+
if (typeof identity !== "string" || identity.length > 1024 * 1024) {
|
|
1020
|
+
throw new Error(`Compiler extension '${extensionId}' returned an invalid interface identity for '${name}'`);
|
|
1021
|
+
}
|
|
1022
|
+
return node("extension-export", [extensionSegment(name), extensionSegment(identity)]);
|
|
1023
|
+
});
|
|
1024
|
+
return node("extension", [extensionSegment(extensionId), ...entries]);
|
|
1025
|
+
}));
|
|
1026
|
+
return node("module-interface", [
|
|
1027
|
+
typeMap(interface_.exports),
|
|
1028
|
+
names(interface_.mutableExports),
|
|
1029
|
+
namedTypes,
|
|
1030
|
+
namedTypeReadonlyFields,
|
|
1031
|
+
namedTypeIdentities,
|
|
1032
|
+
genericTypes,
|
|
1033
|
+
typeMap(interface_.typeAliases),
|
|
1034
|
+
enums,
|
|
1035
|
+
classes,
|
|
1036
|
+
node("reactive", [...interface_.reactiveExports]
|
|
1037
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1038
|
+
.map(([name, kind]) => node("reactive-entry", [name, kind]))),
|
|
1039
|
+
node("re-exports", [...interface_.reExports]
|
|
1040
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1041
|
+
.map(([name, target]) => node("re-export", [name, target.source, target.imported]))),
|
|
1042
|
+
node("tests", interface_.tests.map((item) => `${item.name}\u0000${item.title}`).sort()),
|
|
1043
|
+
extensionExports,
|
|
1044
|
+
]);
|
|
1045
|
+
}
|
|
1046
|
+
async function createAnalysisContext(module, loaded, velarImports, failures, notices, declarationCache, externalTypeDependencies, interfaceCache, compiledInterfaces, compilerExtensions) {
|
|
1047
|
+
const imports = new Map();
|
|
1048
|
+
const dynamicImports = new Map();
|
|
1049
|
+
const reactiveImports = new Map();
|
|
1050
|
+
const namedTypes = new Map();
|
|
1051
|
+
const namedTypeReadonlyFields = new Map();
|
|
1052
|
+
const namedTypeIdentities = new Map();
|
|
1053
|
+
const genericTypes = new Map();
|
|
1054
|
+
const typeAliases = new Map();
|
|
1055
|
+
const enums = new Map();
|
|
1056
|
+
const classes = new Map();
|
|
1057
|
+
const extensionImports = new Map();
|
|
1058
|
+
const extensionModules = new Map();
|
|
1059
|
+
for (const loadedModule of loaded.values()) {
|
|
1060
|
+
for (const [extensionId, data] of loadedModule.inspection.moduleInterface.extensionData) {
|
|
1061
|
+
const values = extensionModules.get(extensionId) ?? [];
|
|
1062
|
+
values.push(data);
|
|
1063
|
+
extensionModules.set(extensionId, values);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
for (const dependency of module.inspection.dependencies) {
|
|
1067
|
+
if (dependency.dynamic) {
|
|
1068
|
+
const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
|
|
1069
|
+
? resolve(dirname(module.inputPath), dependency.source)
|
|
1070
|
+
: null;
|
|
1071
|
+
const target = targetPath ? loaded.get(targetPath) : null;
|
|
1072
|
+
if (!target)
|
|
1073
|
+
continue;
|
|
1074
|
+
const interface_ = resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions);
|
|
1075
|
+
if (interface_.reactiveExports.size > 0) {
|
|
1076
|
+
failures.push({
|
|
1077
|
+
path: module.inputPath,
|
|
1078
|
+
message: `Dynamically imported module '${dependency.source}' exports reactive values; expose behavior through functions or components instead`,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
dynamicImports.set(dependency.source, {
|
|
1082
|
+
kind: "object",
|
|
1083
|
+
fields: new Map(interface_.exports),
|
|
1084
|
+
readonlyFields: new Set(interface_.exports.keys()),
|
|
1085
|
+
});
|
|
1086
|
+
importHiddenTypeMetadata(interface_, namedTypes, namedTypeReadonlyFields, genericTypes, enums, classes);
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
if (dependency.reExport) {
|
|
1090
|
+
const interface_ = standardModuleInterface(dependency.source, compilerExtensions) ?? (() => {
|
|
1091
|
+
const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
|
|
1092
|
+
? resolve(dirname(module.inputPath), dependency.source)
|
|
1093
|
+
: velarImports.get(projectImportKey(module.inputPath, dependency.source));
|
|
1094
|
+
const target = targetPath ? loaded.get(targetPath) : null;
|
|
1095
|
+
return target ? resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions) : null;
|
|
1096
|
+
})();
|
|
1097
|
+
if (interface_) {
|
|
1098
|
+
for (const specifier of dependency.specifiers) {
|
|
1099
|
+
if (!interface_.exports.has(specifier.imported)) {
|
|
1100
|
+
failures.push({ path: module.inputPath, message: missingExportMessage(dependency.source, specifier.imported) });
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (dependency.javascript) {
|
|
1107
|
+
// A manual extern module owns the source contract completely, so the
|
|
1108
|
+
// automatic TypeScript-declaration probe stays silent for that source.
|
|
1109
|
+
if (dependency.unsafe || dependency.externOwned)
|
|
1110
|
+
continue;
|
|
1111
|
+
const key = projectImportKey(module.inputPath, dependency.source);
|
|
1112
|
+
let pending = declarationCache.get(key);
|
|
1113
|
+
if (!pending) {
|
|
1114
|
+
pending = loadTypeScriptDeclarations(dependency.source, module.inputPath);
|
|
1115
|
+
declarationCache.set(key, pending);
|
|
1116
|
+
}
|
|
1117
|
+
const declarations = await pending;
|
|
1118
|
+
if (!declarations)
|
|
1119
|
+
continue;
|
|
1120
|
+
for (const path of declarations.dependencies) {
|
|
1121
|
+
const importers = externalTypeDependencies.get(path) ?? new Set();
|
|
1122
|
+
importers.add(module.inputPath);
|
|
1123
|
+
externalTypeDependencies.set(path, importers);
|
|
1124
|
+
}
|
|
1125
|
+
for (const warning of declarations.warnings) {
|
|
1126
|
+
notices.push({ path: module.inputPath, message: `${dependency.source}: ${warning}` });
|
|
1127
|
+
}
|
|
1128
|
+
// BRG-U3: the broken-types notice is the whole story; per-name
|
|
1129
|
+
// "declaration has no export" noise would blame the import lines for
|
|
1130
|
+
// the package's defect.
|
|
1131
|
+
if (declarations.unreadableDeclaredTypes)
|
|
1132
|
+
continue;
|
|
1133
|
+
const aliases = new Map(dependency.specifiers
|
|
1134
|
+
.filter((specifier) => !specifier.namespace)
|
|
1135
|
+
.map((specifier) => [specifier.imported, specifier.local]));
|
|
1136
|
+
for (const [identity, info] of declarations.classRegistry)
|
|
1137
|
+
classes.set(identity, info);
|
|
1138
|
+
for (const [name, info] of declarations.classes) {
|
|
1139
|
+
const renamed = renameClass(info, aliases);
|
|
1140
|
+
const localName = aliases.get(name) ?? name;
|
|
1141
|
+
classes.set(localName, renamed);
|
|
1142
|
+
if (info.identity)
|
|
1143
|
+
classes.set(info.identity, renamed);
|
|
1144
|
+
}
|
|
1145
|
+
for (const specifier of dependency.specifiers) {
|
|
1146
|
+
if (specifier.namespace) {
|
|
1147
|
+
const fields = new Map([...declarations.exports].map(([name, type]) => [name, renameType(type, aliases)]));
|
|
1148
|
+
imports.set(specifier.local, { kind: "object", fields, readonlyFields: new Set(fields.keys()) });
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
const type = declarations.exports.get(specifier.imported);
|
|
1152
|
+
if (!type)
|
|
1153
|
+
notices.push({ path: module.inputPath, message: `${dependency.source}: declaration has no export '${specifier.imported}'` });
|
|
1154
|
+
imports.set(specifier.local, type ? renameType(type, aliases) : { kind: "unknown" });
|
|
1155
|
+
}
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
const standard = standardModuleInterface(dependency.source, compilerExtensions);
|
|
1159
|
+
if (standard) {
|
|
1160
|
+
importInterface(module, dependency, standard, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, genericTypes, typeAliases, enums, classes, extensionImports, failures);
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
|
|
1164
|
+
? resolve(dirname(module.inputPath), dependency.source)
|
|
1165
|
+
: velarImports.get(projectImportKey(module.inputPath, dependency.source));
|
|
1166
|
+
if (!targetPath)
|
|
1167
|
+
continue;
|
|
1168
|
+
const target = loaded.get(targetPath);
|
|
1169
|
+
if (!target)
|
|
1170
|
+
continue;
|
|
1171
|
+
importInterface(module, dependency, resolvedModuleInterface(target, loaded, velarImports, interfaceCache, compiledInterfaces, compilerExtensions), imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, genericTypes, typeAliases, enums, classes, extensionImports, failures);
|
|
1172
|
+
}
|
|
1173
|
+
return {
|
|
1174
|
+
imports,
|
|
1175
|
+
dynamicImports,
|
|
1176
|
+
reactiveImports,
|
|
1177
|
+
namedTypes,
|
|
1178
|
+
namedTypeReadonlyFields,
|
|
1179
|
+
namedTypeIdentities,
|
|
1180
|
+
genericTypes,
|
|
1181
|
+
typeAliases,
|
|
1182
|
+
enums,
|
|
1183
|
+
classes,
|
|
1184
|
+
extensionImports,
|
|
1185
|
+
extensionModules,
|
|
1186
|
+
resources: module.resourceContents,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function resolvedModuleInterface(module, loaded, velarImports, cache, compiledInterfaces, compilerExtensions) {
|
|
1190
|
+
const cached = cache.get(module.inputPath);
|
|
1191
|
+
if (cached)
|
|
1192
|
+
return cached;
|
|
1193
|
+
const own = compiledInterfaces.get(module.inputPath) ?? module.inspection.moduleInterface;
|
|
1194
|
+
const exports = new Map(own.exports);
|
|
1195
|
+
const mutableExports = new Set(own.mutableExports);
|
|
1196
|
+
const reactiveExports = new Map(own.reactiveExports);
|
|
1197
|
+
const namedTypes = new Map(own.namedTypes);
|
|
1198
|
+
const namedTypeReadonlyFields = new Map(own.namedTypeReadonlyFields ?? []);
|
|
1199
|
+
const namedTypeIdentities = new Map(own.namedTypeIdentities);
|
|
1200
|
+
const genericTypes = new Map(own.genericTypes ?? []);
|
|
1201
|
+
const typeAliases = new Map(own.typeAliases);
|
|
1202
|
+
const enums = new Map(own.enums);
|
|
1203
|
+
const classes = new Map(own.classes);
|
|
1204
|
+
for (const info of own.genericTypes?.values() ?? [])
|
|
1205
|
+
genericTypes.set(info.identity, info);
|
|
1206
|
+
for (const [name, identity] of own.namedTypeIdentities) {
|
|
1207
|
+
const fields = own.namedTypes.get(name);
|
|
1208
|
+
if (fields)
|
|
1209
|
+
namedTypes.set(identity, fields);
|
|
1210
|
+
const readonlyFields = own.namedTypeReadonlyFields?.get(name);
|
|
1211
|
+
if (readonlyFields)
|
|
1212
|
+
namedTypeReadonlyFields.set(identity, readonlyFields);
|
|
1213
|
+
}
|
|
1214
|
+
for (const info of own.enums.values())
|
|
1215
|
+
enums.set(info.identity, info);
|
|
1216
|
+
for (const info of own.classes.values())
|
|
1217
|
+
if (info.identity)
|
|
1218
|
+
classes.set(info.identity, info);
|
|
1219
|
+
const extensionExports = new Map([...own.extensionExports].map(([id, values]) => [id, new Map(values)]));
|
|
1220
|
+
const resolved = { ...own, exports, mutableExports, reactiveExports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, genericTypes, typeAliases, enums, classes, extensionExports };
|
|
1221
|
+
cache.set(module.inputPath, resolved);
|
|
1222
|
+
for (const dependency of module.inspection.dependencies) {
|
|
1223
|
+
if (dependency.javascript)
|
|
1224
|
+
continue;
|
|
1225
|
+
let dependencyInterface = standardModuleInterface(dependency.source, compilerExtensions);
|
|
1226
|
+
if (!dependencyInterface) {
|
|
1227
|
+
const targetPath = dependency.source.startsWith(".") && extname(dependency.source) === ".vel"
|
|
1228
|
+
? resolve(dirname(module.inputPath), dependency.source)
|
|
1229
|
+
: velarImports.get(projectImportKey(module.inputPath, dependency.source));
|
|
1230
|
+
const target = targetPath ? loaded.get(targetPath) : null;
|
|
1231
|
+
if (!target)
|
|
1232
|
+
continue;
|
|
1233
|
+
dependencyInterface = resolvedModuleInterface(target, loaded, velarImports, cache, compiledInterfaces, compilerExtensions);
|
|
1234
|
+
}
|
|
1235
|
+
const aliases = new Map(dependency.specifiers
|
|
1236
|
+
.filter((specifier) => !specifier.namespace && specifier.imported !== "default")
|
|
1237
|
+
.map((specifier) => [specifier.imported, specifier.local]));
|
|
1238
|
+
const dependencyTypeIdentities = new Set(dependencyInterface.namedTypeIdentities.values());
|
|
1239
|
+
for (const [name, fields] of dependencyInterface.namedTypes) {
|
|
1240
|
+
const identity = dependencyInterface.namedTypeIdentities.get(name) ?? (dependencyTypeIdentities.has(name) ? name : null);
|
|
1241
|
+
if (!identity)
|
|
1242
|
+
continue;
|
|
1243
|
+
const renamedFields = new Map([...fields].map(([field, type]) => [field, renameType(type, aliases)]));
|
|
1244
|
+
if (!namedTypes.has(identity))
|
|
1245
|
+
namedTypes.set(identity, renamedFields);
|
|
1246
|
+
if (!namedTypeIdentities.has(identity))
|
|
1247
|
+
namedTypeIdentities.set(identity, identity);
|
|
1248
|
+
const readonlyFields = dependencyInterface.namedTypeReadonlyFields?.get(name)
|
|
1249
|
+
?? dependencyInterface.namedTypeReadonlyFields?.get(identity);
|
|
1250
|
+
if (readonlyFields && !namedTypeReadonlyFields.has(identity))
|
|
1251
|
+
namedTypeReadonlyFields.set(identity, readonlyFields);
|
|
1252
|
+
const localName = aliases.get(name);
|
|
1253
|
+
if (localName && dependencyInterface.exports.has(name)) {
|
|
1254
|
+
namedTypes.set(localName, renamedFields);
|
|
1255
|
+
if (readonlyFields)
|
|
1256
|
+
namedTypeReadonlyFields.set(localName, readonlyFields);
|
|
1257
|
+
namedTypeIdentities.set(localName, identity);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
// D55 rule 120: a re-exported generic record travels with the barrel that
|
|
1261
|
+
// re-exports it, under the name the barrel gives it.
|
|
1262
|
+
for (const [name, info] of dependencyInterface.genericTypes ?? []) {
|
|
1263
|
+
const template = { ...info, fields: new Map([...info.fields].map(([field, type]) => [field, renameType(type, aliases)])) };
|
|
1264
|
+
if (!genericTypes.has(info.identity))
|
|
1265
|
+
genericTypes.set(info.identity, template);
|
|
1266
|
+
const localName = aliases.get(name);
|
|
1267
|
+
if (localName && dependencyInterface.exports.has(name))
|
|
1268
|
+
genericTypes.set(localName, template);
|
|
1269
|
+
}
|
|
1270
|
+
for (const [name, type] of dependencyInterface.typeAliases) {
|
|
1271
|
+
const localName = aliases.get(name);
|
|
1272
|
+
if (localName && dependencyInterface.exports.has(name) && !typeAliases.has(localName)) {
|
|
1273
|
+
typeAliases.set(localName, renameType(type, aliases));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
for (const [name, members] of dependencyInterface.enums) {
|
|
1277
|
+
if (!enums.has(members.identity))
|
|
1278
|
+
enums.set(members.identity, members);
|
|
1279
|
+
const localName = aliases.get(name);
|
|
1280
|
+
if (localName && dependencyInterface.exports.has(name))
|
|
1281
|
+
enums.set(localName, members);
|
|
1282
|
+
}
|
|
1283
|
+
for (const [name, info] of dependencyInterface.classes) {
|
|
1284
|
+
const renamed = renameClass(info, aliases);
|
|
1285
|
+
if (renamed.identity && !classes.has(renamed.identity))
|
|
1286
|
+
classes.set(renamed.identity, renamed);
|
|
1287
|
+
const localName = aliases.get(name);
|
|
1288
|
+
if (localName && dependencyInterface.exports.has(name))
|
|
1289
|
+
classes.set(localName, renamed);
|
|
1290
|
+
}
|
|
1291
|
+
if (dependency.reExport) {
|
|
1292
|
+
// Re-exported names become part of this module's own interface under
|
|
1293
|
+
// their aliases; live-export mutability and reactivity flags propagate.
|
|
1294
|
+
for (const specifier of dependency.specifiers) {
|
|
1295
|
+
const type = dependencyInterface.exports.get(specifier.imported);
|
|
1296
|
+
if (type)
|
|
1297
|
+
exports.set(specifier.local, renameType(type, aliases));
|
|
1298
|
+
if (dependencyInterface.mutableExports.has(specifier.imported))
|
|
1299
|
+
mutableExports.add(specifier.local);
|
|
1300
|
+
const reactive = dependencyInterface.reactiveExports.get(specifier.imported);
|
|
1301
|
+
if (reactive)
|
|
1302
|
+
reactiveExports.set(specifier.local, reactive);
|
|
1303
|
+
}
|
|
1304
|
+
for (const [extensionId, values] of dependencyInterface.extensionExports) {
|
|
1305
|
+
const target = extensionExports.get(extensionId) ?? new Map();
|
|
1306
|
+
for (const specifier of dependency.specifiers) {
|
|
1307
|
+
const value = values.get(specifier.imported);
|
|
1308
|
+
if (value !== undefined)
|
|
1309
|
+
target.set(specifier.local, value);
|
|
1310
|
+
}
|
|
1311
|
+
if (target.size > 0)
|
|
1312
|
+
extensionExports.set(extensionId, target);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const enumNames = enums;
|
|
1317
|
+
const resolveType = (type) => resolveKnownNominals(expandKnownAliases(type, typeAliases), classes, enumNames, namedTypeIdentities);
|
|
1318
|
+
for (const [name, type] of exports)
|
|
1319
|
+
exports.set(name, resolveType(type));
|
|
1320
|
+
for (const [name, fields] of namedTypes) {
|
|
1321
|
+
namedTypes.set(name, new Map([...fields].map(([field, type]) => [field, resolveType(type)])));
|
|
1322
|
+
}
|
|
1323
|
+
for (const [name, type] of typeAliases)
|
|
1324
|
+
typeAliases.set(name, resolveType(type));
|
|
1325
|
+
for (const [name, info] of classes) {
|
|
1326
|
+
classes.set(name, mapClassInfo(info, resolveType, (base) => classes.get(base)?.identity ?? base));
|
|
1327
|
+
}
|
|
1328
|
+
return resolved;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* MOD-U7: the package is installed and its manifest reads fine — it is simply
|
|
1332
|
+
* a JavaScript package. That is the mirror image of BRG-U2 (a VelarScript
|
|
1333
|
+
* package reached through `import js`), and it earns the same
|
|
1334
|
+
* reverse-direction teaching instead of a manifest-field complaint that never
|
|
1335
|
+
* mentions the bridge the author actually needs.
|
|
1336
|
+
*/
|
|
1337
|
+
class JavaScriptOnlyPackageError extends Error {
|
|
1338
|
+
}
|
|
1339
|
+
async function resolveVelarSourcePackage(source, importerPath) {
|
|
1340
|
+
const name = packageNameOf(source);
|
|
1341
|
+
if (source !== name)
|
|
1342
|
+
throw new Error("package subpaths are not supported; import the package entry by name");
|
|
1343
|
+
let directory = dirname(importerPath);
|
|
1344
|
+
while (true) {
|
|
1345
|
+
const root = join(directory, "node_modules", ...name.split("/"));
|
|
1346
|
+
try {
|
|
1347
|
+
const manifest = JSON.parse(await readBoundedText(join(root, "package.json"), 1024 * 1024, `Package manifest for '${name}'`));
|
|
1348
|
+
if (manifest.name !== undefined && manifest.name !== name)
|
|
1349
|
+
throw new Error(`package name is '${String(manifest.name)}', expected '${name}'`);
|
|
1350
|
+
const entry = manifest.velar?.entry;
|
|
1351
|
+
if (typeof entry !== "string" || entry.length === 0)
|
|
1352
|
+
throw new JavaScriptOnlyPackageError("package.json must declare 'velar.entry'");
|
|
1353
|
+
if (isAbsolute(entry))
|
|
1354
|
+
throw new Error("'velar.entry' must be relative to the package root");
|
|
1355
|
+
const entryPath = resolve(root, entry);
|
|
1356
|
+
if (escapesRoot(relative(root, entryPath)))
|
|
1357
|
+
throw new Error("'velar.entry' cannot escape the package root");
|
|
1358
|
+
if (extname(entryPath) !== ".vel")
|
|
1359
|
+
throw new Error("'velar.entry' must point to a .vel source file");
|
|
1360
|
+
return { name, root, entryPath };
|
|
1361
|
+
}
|
|
1362
|
+
catch (error) {
|
|
1363
|
+
if (error instanceof SyntaxError)
|
|
1364
|
+
throw error;
|
|
1365
|
+
if (!isHostErrorCode(error, "ENOENT"))
|
|
1366
|
+
throw error;
|
|
1367
|
+
}
|
|
1368
|
+
const parent = dirname(directory);
|
|
1369
|
+
if (parent === directory)
|
|
1370
|
+
throw new Error(`package '${name}' is not installed`);
|
|
1371
|
+
directory = parent;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
function packageNameOf(source) {
|
|
1375
|
+
const parts = source.split("/");
|
|
1376
|
+
if (source.startsWith("@")) {
|
|
1377
|
+
if (parts.length < 2 || !parts[0] || !parts[1])
|
|
1378
|
+
throw new Error(`invalid package name '${source}'`);
|
|
1379
|
+
return parts.slice(0, 2).join("/");
|
|
1380
|
+
}
|
|
1381
|
+
if (!parts[0])
|
|
1382
|
+
throw new Error(`invalid package name '${source}'`);
|
|
1383
|
+
return parts[0];
|
|
1384
|
+
}
|
|
1385
|
+
function escapesRoot(relativePath) {
|
|
1386
|
+
return relativePath === ".." || relativePath.startsWith("../") || relativePath.startsWith("..\\") || isAbsolute(relativePath);
|
|
1387
|
+
}
|
|
1388
|
+
function normalizeModulePath(path) {
|
|
1389
|
+
return path.replaceAll("\\", "/");
|
|
1390
|
+
}
|
|
1391
|
+
function importHiddenTypeMetadata(interface_, namedTypes, namedTypeReadonlyFields, genericTypes, enums, classes) {
|
|
1392
|
+
// D55 rule 120: a dynamically imported module's generic records are reachable
|
|
1393
|
+
// through its exported signatures, so their templates travel with the rest of
|
|
1394
|
+
// the hidden metadata — keyed by identity, which is how a type reached
|
|
1395
|
+
// without a local name is found.
|
|
1396
|
+
for (const info of interface_.genericTypes?.values() ?? []) {
|
|
1397
|
+
if (!genericTypes.has(info.identity))
|
|
1398
|
+
genericTypes.set(info.identity, info);
|
|
1399
|
+
}
|
|
1400
|
+
const identities = new Set(interface_.namedTypeIdentities.values());
|
|
1401
|
+
for (const [name, fields] of interface_.namedTypes) {
|
|
1402
|
+
const identity = interface_.namedTypeIdentities.get(name) ?? (identities.has(name) ? name : null);
|
|
1403
|
+
if (identity && !namedTypes.has(identity))
|
|
1404
|
+
namedTypes.set(identity, fields);
|
|
1405
|
+
const readonlyFields = interface_.namedTypeReadonlyFields?.get(name) ?? (identity ? interface_.namedTypeReadonlyFields?.get(identity) : undefined);
|
|
1406
|
+
if (identity && readonlyFields && !namedTypeReadonlyFields.has(identity))
|
|
1407
|
+
namedTypeReadonlyFields.set(identity, readonlyFields);
|
|
1408
|
+
}
|
|
1409
|
+
for (const info of interface_.enums.values())
|
|
1410
|
+
if (!enums.has(info.identity))
|
|
1411
|
+
enums.set(info.identity, info);
|
|
1412
|
+
for (const info of interface_.classes.values()) {
|
|
1413
|
+
if (info.identity && !classes.has(info.identity))
|
|
1414
|
+
classes.set(info.identity, info);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function importInterface(module, dependency, interface_, imports, reactiveImports, namedTypes, namedTypeReadonlyFields, namedTypeIdentities, genericTypes, typeAliases, enums, classes, extensionImports, failures) {
|
|
1418
|
+
const aliases = new Map(dependency.specifiers
|
|
1419
|
+
.filter((specifier) => !specifier.namespace && specifier.imported !== "default")
|
|
1420
|
+
.map((specifier) => [specifier.imported, specifier.local]));
|
|
1421
|
+
for (const [name, members] of interface_.enums) {
|
|
1422
|
+
enums.set(members.identity, members);
|
|
1423
|
+
const localName = aliases.get(name);
|
|
1424
|
+
if (localName && interface_.exports.get(name)?.kind === "enumObject")
|
|
1425
|
+
enums.set(localName, members);
|
|
1426
|
+
}
|
|
1427
|
+
for (const [name, info] of interface_.classes) {
|
|
1428
|
+
const renamed = renameClass(info, aliases);
|
|
1429
|
+
if (renamed.identity)
|
|
1430
|
+
classes.set(renamed.identity, renamed);
|
|
1431
|
+
const localName = aliases.get(name);
|
|
1432
|
+
if (localName && interface_.exports.get(name)?.kind === "classConstructor")
|
|
1433
|
+
classes.set(localName, renamed);
|
|
1434
|
+
}
|
|
1435
|
+
const importedNamedTypeIdentities = new Map([...interface_.namedTypeIdentities]
|
|
1436
|
+
.map(([name, identity]) => [aliases.get(name) ?? name, identity]));
|
|
1437
|
+
const resolveImportedType = (type) => resolveKnownNominals(renameType(type, aliases), classes, enums, importedNamedTypeIdentities);
|
|
1438
|
+
const interfaceTypeIdentities = new Set(interface_.namedTypeIdentities.values());
|
|
1439
|
+
for (const [name, fields] of interface_.namedTypes) {
|
|
1440
|
+
const identity = interface_.namedTypeIdentities.get(name) ?? (interfaceTypeIdentities.has(name) ? name : null);
|
|
1441
|
+
if (!identity)
|
|
1442
|
+
continue;
|
|
1443
|
+
const renamedFields = new Map([...fields].map(([field, type]) => [field, resolveImportedType(type)]));
|
|
1444
|
+
namedTypes.set(identity, renamedFields);
|
|
1445
|
+
const readonlyFields = interface_.namedTypeReadonlyFields?.get(name) ?? interface_.namedTypeReadonlyFields?.get(identity);
|
|
1446
|
+
if (readonlyFields)
|
|
1447
|
+
namedTypeReadonlyFields.set(identity, readonlyFields);
|
|
1448
|
+
const localName = aliases.get(name);
|
|
1449
|
+
if (localName && interface_.exports.get(name)?.kind === "typeObject") {
|
|
1450
|
+
namedTypes.set(localName, renamedFields);
|
|
1451
|
+
if (readonlyFields)
|
|
1452
|
+
namedTypeReadonlyFields.set(localName, readonlyFields);
|
|
1453
|
+
namedTypeIdentities.set(localName, identity);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
// D55 rule 120: an imported generic record is bound under the name this
|
|
1457
|
+
// module writes, and its template's field types are renamed and nominally
|
|
1458
|
+
// resolved exactly like an imported record's — the arguments a dependent
|
|
1459
|
+
// supplies are its own, but everything the declaration already fixed has to
|
|
1460
|
+
// arrive meaning what it meant where it was written.
|
|
1461
|
+
for (const [name, info] of interface_.genericTypes ?? []) {
|
|
1462
|
+
const template = {
|
|
1463
|
+
...info,
|
|
1464
|
+
fields: new Map([...info.fields].map(([field, type]) => [field, resolveImportedType(type)])),
|
|
1465
|
+
};
|
|
1466
|
+
if (!genericTypes.has(info.identity))
|
|
1467
|
+
genericTypes.set(info.identity, template);
|
|
1468
|
+
const localName = aliases.get(name);
|
|
1469
|
+
if (localName && interface_.exports.get(name)?.kind === "typeObject")
|
|
1470
|
+
genericTypes.set(localName, template);
|
|
1471
|
+
}
|
|
1472
|
+
for (const [name, type] of interface_.typeAliases) {
|
|
1473
|
+
const localName = aliases.get(name);
|
|
1474
|
+
if (localName && interface_.exports.get(name)?.kind === "typeObject") {
|
|
1475
|
+
typeAliases.set(localName, resolveImportedType(type));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
for (const [extensionId, exportedValues] of interface_.extensionExports) {
|
|
1479
|
+
const importedValues = extensionImports.get(extensionId) ?? new Map();
|
|
1480
|
+
for (const specifier of dependency.specifiers) {
|
|
1481
|
+
if (specifier.namespace)
|
|
1482
|
+
continue;
|
|
1483
|
+
const value = exportedValues.get(specifier.imported);
|
|
1484
|
+
if (value !== undefined)
|
|
1485
|
+
importedValues.set(specifier.local, value);
|
|
1486
|
+
}
|
|
1487
|
+
if (importedValues.size > 0)
|
|
1488
|
+
extensionImports.set(extensionId, importedValues);
|
|
1489
|
+
}
|
|
1490
|
+
for (const specifier of dependency.specifiers) {
|
|
1491
|
+
if (specifier.namespace) {
|
|
1492
|
+
if (interface_.reactiveExports.size > 0 || interface_.mutableExports.size > 0) {
|
|
1493
|
+
failures.push({
|
|
1494
|
+
path: module.inputPath,
|
|
1495
|
+
message: `Module '${dependency.source}' exports live values; import them by name instead of using a namespace import`,
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
const fields = new Map([...interface_.exports].map(([name, type]) => [name, resolveImportedType(type)]));
|
|
1499
|
+
imports.set(specifier.local, {
|
|
1500
|
+
kind: "object",
|
|
1501
|
+
fields,
|
|
1502
|
+
readonlyFields: new Set(fields.keys()),
|
|
1503
|
+
});
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
const exported = interface_.exports.get(specifier.imported);
|
|
1507
|
+
if (!exported) {
|
|
1508
|
+
failures.push({ path: module.inputPath, message: missingExportMessage(dependency.source, specifier.imported) });
|
|
1509
|
+
imports.set(specifier.local, { kind: "unknown" });
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
imports.set(specifier.local, resolveImportedType(exported));
|
|
1513
|
+
const reactive = interface_.reactiveExports.get(specifier.imported);
|
|
1514
|
+
if (reactive)
|
|
1515
|
+
reactiveImports.set(specifier.local, reactive);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
function renameClass(info, aliases) {
|
|
1519
|
+
return mapClassInfo(info, (type) => renameType(type, aliases), (base) => aliases.get(base) ?? base);
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Rebuilds the type-bearing parts of a class interface while preserving every
|
|
1523
|
+
* other contract field by construction. ClassInfo has gained independent
|
|
1524
|
+
* fields such as `dispose` and `iterate`; spelling the whole object at each
|
|
1525
|
+
* import/re-export seam made every addition an easy silent omission.
|
|
1526
|
+
*/
|
|
1527
|
+
function mapClassInfo(info, mapType, mapBase) {
|
|
1528
|
+
return {
|
|
1529
|
+
...info,
|
|
1530
|
+
// D68 rule 177: the iteration contract is part of the class, so its answer
|
|
1531
|
+
// is transformed with every other type-bearing member.
|
|
1532
|
+
...(info.iterate ? { iterate: mapType(info.iterate) } : {}),
|
|
1533
|
+
parameters: info.parameters.map(mapType),
|
|
1534
|
+
...(info.constructorRest ? { constructorRest: mapType(info.constructorRest) } : {}),
|
|
1535
|
+
base: info.base ? mapBase(info.base) : null,
|
|
1536
|
+
fields: new Map([...info.fields].map(([name, field]) => [name, { ...field, type: mapType(field.type) }])),
|
|
1537
|
+
methods: new Map([...info.methods].map(([name, type]) => [name, mapType(type)])),
|
|
1538
|
+
staticFields: new Map([...info.staticFields].map(([name, field]) => [name, { ...field, type: mapType(field.type) }])),
|
|
1539
|
+
staticMethods: new Map([...info.staticMethods].map(([name, type]) => [name, mapType(type)])),
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
function renameType(type, aliases) {
|
|
1543
|
+
switch (type.kind) {
|
|
1544
|
+
// D55 rule 121: an application renames through the declaration it applies,
|
|
1545
|
+
// not through its display text — `Box<string>` is not a name an import can
|
|
1546
|
+
// alias, but `Box` is, and its arguments rename like any other type.
|
|
1547
|
+
case "named":
|
|
1548
|
+
if (type.application) {
|
|
1549
|
+
const renamed = aliases.get(type.application.name) ?? type.application.name;
|
|
1550
|
+
return genericApplicationType(type.application.declaration, renamed, type.application.arguments.map((argument) => renameType(argument, aliases)), type.readonlyView === true);
|
|
1551
|
+
}
|
|
1552
|
+
return { ...type, name: aliases.get(type.name) ?? type.name };
|
|
1553
|
+
case "class":
|
|
1554
|
+
case "enum":
|
|
1555
|
+
case "enumMember":
|
|
1556
|
+
case "classConstructor":
|
|
1557
|
+
return { ...type, name: aliases.get(type.name) ?? type.name };
|
|
1558
|
+
case "typeObject":
|
|
1559
|
+
return {
|
|
1560
|
+
...type,
|
|
1561
|
+
name: aliases.get(type.name) ?? type.name,
|
|
1562
|
+
...(type.value ? { value: renameType(type.value, aliases) } : {}),
|
|
1563
|
+
};
|
|
1564
|
+
case "enumObject":
|
|
1565
|
+
return { ...type, name: aliases.get(type.name) ?? type.name };
|
|
1566
|
+
case "optional":
|
|
1567
|
+
return optionalOf(renameType(type.inner, aliases));
|
|
1568
|
+
case "list":
|
|
1569
|
+
return { ...type, element: renameType(type.element, aliases) };
|
|
1570
|
+
case "set":
|
|
1571
|
+
return { ...type, element: renameType(type.element, aliases) };
|
|
1572
|
+
case "map":
|
|
1573
|
+
return { ...type, key: renameType(type.key, aliases), value: renameType(type.value, aliases) };
|
|
1574
|
+
case "record":
|
|
1575
|
+
return { ...type, value: renameType(type.value, aliases) };
|
|
1576
|
+
case "promise":
|
|
1577
|
+
return { kind: "promise", value: renameType(type.value, aliases) };
|
|
1578
|
+
case "runtimeType":
|
|
1579
|
+
return { kind: "runtimeType", value: renameType(type.value, aliases) };
|
|
1580
|
+
case "object":
|
|
1581
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, renameType(value, aliases)])) };
|
|
1582
|
+
case "function":
|
|
1583
|
+
case "action":
|
|
1584
|
+
return {
|
|
1585
|
+
...type,
|
|
1586
|
+
parameters: type.parameters.map((parameter) => renameType(parameter, aliases)),
|
|
1587
|
+
...(type.rest ? { rest: renameType(type.rest, aliases) } : {}),
|
|
1588
|
+
result: renameType(type.result, aliases),
|
|
1589
|
+
};
|
|
1590
|
+
case "intrinsic":
|
|
1591
|
+
return {
|
|
1592
|
+
...type,
|
|
1593
|
+
parameters: type.parameters.map((parameter) => renameType(parameter, aliases)),
|
|
1594
|
+
...(type.rest ? { rest: renameType(type.rest, aliases) } : {}),
|
|
1595
|
+
result: renameType(type.result, aliases),
|
|
1596
|
+
};
|
|
1597
|
+
case "extension":
|
|
1598
|
+
return {
|
|
1599
|
+
...type,
|
|
1600
|
+
...(type.nominal ? { nominal: aliases.get(type.nominal) ?? type.nominal } : {}),
|
|
1601
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, renameType(value, aliases)])),
|
|
1602
|
+
arguments: type.arguments.map((argument) => renameType(argument, aliases)),
|
|
1603
|
+
};
|
|
1604
|
+
case "union":
|
|
1605
|
+
return { kind: "union", members: type.members.map((member) => renameType(member, aliases)) };
|
|
1606
|
+
default:
|
|
1607
|
+
return type;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function resolveKnownNominals(type, classes, enums, namedTypeIdentities) {
|
|
1611
|
+
// D55 rule 121: the importing side of the same crossing `resolveNominals`
|
|
1612
|
+
// makes on the exporting side. Both call one constructor, so the identity
|
|
1613
|
+
// computed here and the identity published there are the same string.
|
|
1614
|
+
if (type.kind === "named" && type.application) {
|
|
1615
|
+
const arguments_ = type.application.arguments.map((argument) => resolveKnownNominals(argument, classes, enums, namedTypeIdentities));
|
|
1616
|
+
const declaration = namedTypeIdentities.get(type.application.name) ?? type.application.declaration;
|
|
1617
|
+
return genericApplicationType(declaration, type.application.name, arguments_, type.readonlyView === true);
|
|
1618
|
+
}
|
|
1619
|
+
if (type.kind === "named" && classes.has(type.name)) {
|
|
1620
|
+
const identity = classes.get(type.name)?.identity;
|
|
1621
|
+
return {
|
|
1622
|
+
kind: "class",
|
|
1623
|
+
name: type.name,
|
|
1624
|
+
...(identity ? { identity } : {}),
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (type.kind === "named" && enums.has(type.name))
|
|
1628
|
+
return { kind: "enum", name: type.name, identity: enums.get(type.name).identity };
|
|
1629
|
+
if (type.kind === "enumMember" && enums.has(type.name)
|
|
1630
|
+
&& type.identity === type.name)
|
|
1631
|
+
return { ...type, identity: enums.get(type.name).identity };
|
|
1632
|
+
if (type.kind === "named" && !type.identity && namedTypeIdentities.has(type.name)) {
|
|
1633
|
+
return { ...type, identity: namedTypeIdentities.get(type.name) };
|
|
1634
|
+
}
|
|
1635
|
+
switch (type.kind) {
|
|
1636
|
+
case "optional":
|
|
1637
|
+
return optionalOf(resolveKnownNominals(type.inner, classes, enums, namedTypeIdentities));
|
|
1638
|
+
case "list":
|
|
1639
|
+
return { ...type, element: resolveKnownNominals(type.element, classes, enums, namedTypeIdentities) };
|
|
1640
|
+
case "set":
|
|
1641
|
+
return { ...type, element: resolveKnownNominals(type.element, classes, enums, namedTypeIdentities) };
|
|
1642
|
+
case "map":
|
|
1643
|
+
return { ...type, key: resolveKnownNominals(type.key, classes, enums, namedTypeIdentities), value: resolveKnownNominals(type.value, classes, enums, namedTypeIdentities) };
|
|
1644
|
+
case "record":
|
|
1645
|
+
return { ...type, value: resolveKnownNominals(type.value, classes, enums, namedTypeIdentities) };
|
|
1646
|
+
case "promise":
|
|
1647
|
+
return { kind: "promise", value: resolveKnownNominals(type.value, classes, enums, namedTypeIdentities) };
|
|
1648
|
+
case "runtimeType":
|
|
1649
|
+
return { kind: "runtimeType", value: resolveKnownNominals(type.value, classes, enums, namedTypeIdentities) };
|
|
1650
|
+
case "typeObject":
|
|
1651
|
+
return type.value
|
|
1652
|
+
? { ...type, value: resolveKnownNominals(type.value, classes, enums, namedTypeIdentities) }
|
|
1653
|
+
: type;
|
|
1654
|
+
case "object":
|
|
1655
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, resolveKnownNominals(value, classes, enums, namedTypeIdentities)])) };
|
|
1656
|
+
case "function":
|
|
1657
|
+
case "action":
|
|
1658
|
+
case "intrinsic":
|
|
1659
|
+
return {
|
|
1660
|
+
...type,
|
|
1661
|
+
parameters: type.parameters.map((parameter) => resolveKnownNominals(parameter, classes, enums, namedTypeIdentities)),
|
|
1662
|
+
...(type.rest ? { rest: resolveKnownNominals(type.rest, classes, enums, namedTypeIdentities) } : {}),
|
|
1663
|
+
result: resolveKnownNominals(type.result, classes, enums, namedTypeIdentities),
|
|
1664
|
+
};
|
|
1665
|
+
case "extension":
|
|
1666
|
+
return {
|
|
1667
|
+
...type,
|
|
1668
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, resolveKnownNominals(value, classes, enums, namedTypeIdentities)])),
|
|
1669
|
+
arguments: type.arguments.map((argument) => resolveKnownNominals(argument, classes, enums, namedTypeIdentities)),
|
|
1670
|
+
};
|
|
1671
|
+
case "union":
|
|
1672
|
+
return { kind: "union", members: type.members.map((member) => resolveKnownNominals(member, classes, enums, namedTypeIdentities)) };
|
|
1673
|
+
default:
|
|
1674
|
+
return type;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function expandKnownAliases(type, aliases, seen = new Set()) {
|
|
1678
|
+
if (type.kind === "named" && aliases.has(type.name)) {
|
|
1679
|
+
if (seen.has(type.name))
|
|
1680
|
+
return { kind: "unknown" };
|
|
1681
|
+
const expanded = expandKnownAliases(aliases.get(type.name), aliases, new Set([...seen, type.name]));
|
|
1682
|
+
return type.readonlyView ? readonlyViewOf(expanded) : expanded;
|
|
1683
|
+
}
|
|
1684
|
+
switch (type.kind) {
|
|
1685
|
+
// D55 rule 121: aliases stay transparent inside a type argument, on this
|
|
1686
|
+
// side of the boundary as on the other.
|
|
1687
|
+
case "named":
|
|
1688
|
+
return type.application
|
|
1689
|
+
? { ...type, application: { ...type.application, arguments: type.application.arguments.map((argument) => expandKnownAliases(argument, aliases, seen)) } }
|
|
1690
|
+
: type;
|
|
1691
|
+
case "optional":
|
|
1692
|
+
return optionalOf(expandKnownAliases(type.inner, aliases, seen));
|
|
1693
|
+
case "list":
|
|
1694
|
+
return { ...type, element: expandKnownAliases(type.element, aliases, seen) };
|
|
1695
|
+
case "set":
|
|
1696
|
+
return { ...type, element: expandKnownAliases(type.element, aliases, seen) };
|
|
1697
|
+
case "map":
|
|
1698
|
+
return { ...type, key: expandKnownAliases(type.key, aliases, seen), value: expandKnownAliases(type.value, aliases, seen) };
|
|
1699
|
+
case "record":
|
|
1700
|
+
return { ...type, value: expandKnownAliases(type.value, aliases, seen) };
|
|
1701
|
+
case "promise":
|
|
1702
|
+
return { kind: "promise", value: expandKnownAliases(type.value, aliases, seen) };
|
|
1703
|
+
case "runtimeType":
|
|
1704
|
+
return { kind: "runtimeType", value: expandKnownAliases(type.value, aliases, seen) };
|
|
1705
|
+
case "typeObject":
|
|
1706
|
+
return type.value ? { ...type, value: expandKnownAliases(type.value, aliases, seen) } : type;
|
|
1707
|
+
case "object":
|
|
1708
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, expandKnownAliases(value, aliases, seen)])) };
|
|
1709
|
+
case "function":
|
|
1710
|
+
case "action":
|
|
1711
|
+
case "intrinsic":
|
|
1712
|
+
return {
|
|
1713
|
+
...type,
|
|
1714
|
+
parameters: type.parameters.map((parameter) => expandKnownAliases(parameter, aliases, seen)),
|
|
1715
|
+
...(type.rest ? { rest: expandKnownAliases(type.rest, aliases, seen) } : {}),
|
|
1716
|
+
result: expandKnownAliases(type.result, aliases, seen),
|
|
1717
|
+
};
|
|
1718
|
+
case "extension":
|
|
1719
|
+
return {
|
|
1720
|
+
...type,
|
|
1721
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, expandKnownAliases(value, aliases, seen)])),
|
|
1722
|
+
arguments: type.arguments.map((argument) => expandKnownAliases(argument, aliases, seen)),
|
|
1723
|
+
};
|
|
1724
|
+
case "union":
|
|
1725
|
+
return { kind: "union", members: type.members.map((member) => expandKnownAliases(member, aliases, seen)) };
|
|
1726
|
+
default:
|
|
1727
|
+
return type;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
function uniqueFailures(failures) {
|
|
1731
|
+
const seen = new Set();
|
|
1732
|
+
return failures.filter((failure) => {
|
|
1733
|
+
const key = `${failure.path}\0${failure.message}`;
|
|
1734
|
+
if (seen.has(key))
|
|
1735
|
+
return false;
|
|
1736
|
+
seen.add(key);
|
|
1737
|
+
return true;
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
function uniqueNotices(notices) {
|
|
1741
|
+
const seen = new Set();
|
|
1742
|
+
return notices.filter((notice) => {
|
|
1743
|
+
const key = `${notice.path}\0${notice.message}`;
|
|
1744
|
+
if (seen.has(key))
|
|
1745
|
+
return false;
|
|
1746
|
+
seen.add(key);
|
|
1747
|
+
return true;
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
//# sourceMappingURL=project.js.map
|