correctly 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -2
- package/README.md +21 -15
- package/dist/ajv.d.mts +11 -0
- package/dist/ajv.d.mts.map +1 -0
- package/dist/ajv.mjs +221 -0
- package/dist/ajv.mjs.map +1 -0
- package/dist/check-BI7U3ZLc.mjs +88 -0
- package/dist/check-BI7U3ZLc.mjs.map +1 -0
- package/dist/cli.mjs +14 -62
- package/dist/cli.mjs.map +1 -1
- package/dist/config-Bx1-XXeo.d.mts +189 -0
- package/dist/config-Bx1-XXeo.d.mts.map +1 -0
- package/dist/config-Cb6o4YnJ.mjs +348 -0
- package/dist/config-Cb6o4YnJ.mjs.map +1 -0
- package/dist/core.d.mts +27 -125
- package/dist/core.d.mts.map +1 -1
- package/dist/core.mjs +5 -2
- package/dist/diagnostics-BqD1WOy4.mjs +70 -0
- package/dist/diagnostics-BqD1WOy4.mjs.map +1 -0
- package/dist/engine-VST0hoSk.mjs +111 -0
- package/dist/engine-VST0hoSk.mjs.map +1 -0
- package/dist/extensions-ClclWslY.d.mts +11 -0
- package/dist/extensions-ClclWslY.d.mts.map +1 -0
- package/dist/hints-HvSIhuMM.mjs +57 -0
- package/dist/hints-HvSIhuMM.mjs.map +1 -0
- package/dist/lsp.mjs +40 -91
- package/dist/lsp.mjs.map +1 -1
- package/dist/renovate.d.mts +5 -0
- package/dist/renovate.d.mts.map +1 -0
- package/dist/renovate.mjs +11 -0
- package/dist/renovate.mjs.map +1 -0
- package/dist/schemas-UxCtIdo2.mjs +265 -0
- package/dist/schemas-UxCtIdo2.mjs.map +1 -0
- package/dist/session-DCFav56x.mjs +113 -0
- package/dist/session-DCFav56x.mjs.map +1 -0
- package/dist/worker.mjs +80 -0
- package/dist/worker.mjs.map +1 -0
- package/docs/guide.md +166 -34
- package/examples/basic/correctly.config.ts +13 -0
- package/package.json +69 -60
- package/src/cli/check.ts +41 -11
- package/src/cli/main.ts +3 -2
- package/src/cli/readable.ts +24 -5
- package/src/cli/vsix.ts +1 -0
- package/src/core/adapters.ts +54 -0
- package/src/core/config.ts +201 -64
- package/src/core/engine.ts +118 -218
- package/src/core/extensions.ts +144 -0
- package/src/core/index.ts +3 -0
- package/src/core/parse.ts +26 -2
- package/src/core/parsers.ts +22 -0
- package/src/core/schema-walk.ts +43 -0
- package/src/core/schemas.ts +21 -41
- package/src/core/types.ts +33 -6
- package/src/extensions/renovate.ts +4 -0
- package/src/lsp/hints.ts +22 -12
- package/src/lsp/server.ts +9 -2
- package/src/lsp/workspace.ts +49 -54
- package/src/runtime/session.ts +173 -0
- package/src/runtime/worker.ts +99 -0
- package/src/validators/ajv.ts +257 -0
- package/src/vscode/README.md +1 -1
- package/src/vscode/extension.ts +2 -4
- package/src/vscode/package.json +9 -2
- package/dist/engine-BgWBnb1A.mjs +0 -746
- package/dist/engine-BgWBnb1A.mjs.map +0 -1
- package/examples/basic/correctly.config.json +0 -10
- package/src/core/config.schema.json +0 -47
package/dist/lsp.mjs
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { b as diagnostic,
|
|
2
|
+
import { b as diagnostic, h as failure, i as contains, m as CorrectlyError, o as discoverConfig } from "./config-Cb6o4YnJ.mjs";
|
|
3
|
+
import { t as ProjectSession } from "./session-DCFav56x.mjs";
|
|
4
|
+
import { n as diagnosticViews, t as diagnosticDetails } from "./diagnostics-BqD1WOy4.mjs";
|
|
5
|
+
import { t as Hints } from "./hints-HvSIhuMM.mjs";
|
|
3
6
|
import path from "node:path";
|
|
4
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
8
|
import { DidChangeWatchedFilesNotification, LSPErrorCodes, ResponseError, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node";
|
|
6
9
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
7
|
-
import { getLanguageService } from "vscode-json-languageservice";
|
|
8
10
|
//#region src/lsp/workspace.ts
|
|
9
11
|
var Workspace = class {
|
|
10
12
|
onSchema;
|
|
11
13
|
roots;
|
|
12
|
-
read;
|
|
13
14
|
documents = /* @__PURE__ */ new Map();
|
|
14
15
|
engines = /* @__PURE__ */ new Map();
|
|
15
|
-
controller = new AbortController();
|
|
16
16
|
generation = 0;
|
|
17
|
-
constructor(roots
|
|
17
|
+
constructor(roots) {
|
|
18
18
|
this.roots = roots;
|
|
19
|
-
this.read = read;
|
|
20
19
|
}
|
|
21
20
|
invalidate() {
|
|
22
21
|
this.generation++;
|
|
23
|
-
this.
|
|
24
|
-
this.controller = new AbortController();
|
|
22
|
+
for (const session of this.engines.values()) session.dispose();
|
|
25
23
|
this.engines.clear();
|
|
26
24
|
}
|
|
27
25
|
open(document) {
|
|
28
26
|
this.documents.set(document.uri, document);
|
|
29
|
-
const resource =
|
|
27
|
+
const resource = this.isSchema(document.uri) && !this.moduleUris.has(document.uri);
|
|
30
28
|
if (resource) this.invalidate();
|
|
31
29
|
return resource;
|
|
32
30
|
}
|
|
33
31
|
close(uri) {
|
|
34
|
-
const resource =
|
|
32
|
+
const resource = this.isSchema(uri) && !this.moduleUris.has(uri);
|
|
35
33
|
this.documents.delete(uri);
|
|
36
34
|
if (resource) this.invalidate();
|
|
37
35
|
return resource;
|
|
@@ -40,47 +38,42 @@ var Workspace = class {
|
|
|
40
38
|
isSchema(uri) {
|
|
41
39
|
return this.schemaUris.has(uri);
|
|
42
40
|
}
|
|
41
|
+
moduleUris = /* @__PURE__ */ new Set();
|
|
43
42
|
resourceChanged(uri) {
|
|
44
|
-
return uri.endsWith(
|
|
43
|
+
return uri.endsWith(`/correctly.config.ts`) || this.isSchema(uri) || this.moduleUris.has(uri) || /\/(?:package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock)$/.test(uri);
|
|
45
44
|
}
|
|
46
|
-
readBuffer = (uri) => {
|
|
47
|
-
const document = this.documents.get(uri);
|
|
48
|
-
return document ? Promise.resolve(document.getText()) : this.read(uri);
|
|
49
|
-
};
|
|
50
45
|
async engineFor(uri) {
|
|
51
46
|
const file = fileURLToPath(uri);
|
|
52
47
|
const root = this.roots.filter((r) => contains(r, file)).sort((a, b) => b.length - a.length)[0];
|
|
53
48
|
if (this.roots.length && !root) throw new CorrectlyError("config", `Document is outside the editor workspace: ${file}`);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
66
|
-
const engine = new Engine(project, {
|
|
67
|
-
read: readSchema,
|
|
68
|
-
signal
|
|
69
|
-
});
|
|
70
|
-
await engine.prepare();
|
|
71
|
-
for (const resource of engine.store.resources.values()) this.schemaUris.add(resource.uri);
|
|
72
|
-
signal.throwIfAborted();
|
|
73
|
-
return engine;
|
|
74
|
-
})();
|
|
75
|
-
this.engines.set(configPath, promise);
|
|
49
|
+
const configPath = await discoverConfig(path.dirname(file), root);
|
|
50
|
+
let session = this.engines.get(configPath);
|
|
51
|
+
if (!session) {
|
|
52
|
+
session = new ProjectSession(configPath, {
|
|
53
|
+
buffers: [...this.documents].map(([uri, document]) => [uri, document.getText()]),
|
|
54
|
+
onDependency: (dependency, kind) => {
|
|
55
|
+
(kind === "module" ? this.moduleUris : this.schemaUris).add(dependency);
|
|
56
|
+
this.onSchema?.(dependency);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
this.engines.set(configPath, session);
|
|
76
60
|
}
|
|
77
|
-
|
|
61
|
+
await session.ready;
|
|
62
|
+
return session;
|
|
78
63
|
}
|
|
79
64
|
async validate(document) {
|
|
80
65
|
try {
|
|
81
66
|
return await (await this.engineFor(document.uri)).validate(fileURLToPath(document.uri), document.getText());
|
|
82
67
|
} catch (error) {
|
|
83
68
|
const problem = failure(error, fileURLToPath(document.uri));
|
|
69
|
+
if (problem.code === "config" && problem.message.startsWith(`No correctly.config.ts found`) && !["json", "jsonc"].includes(document.languageId)) return {
|
|
70
|
+
file: fileURLToPath(document.uri),
|
|
71
|
+
mode: document.languageId,
|
|
72
|
+
association: null,
|
|
73
|
+
coverage: "excluded",
|
|
74
|
+
diagnostics: [],
|
|
75
|
+
failures: []
|
|
76
|
+
};
|
|
84
77
|
return {
|
|
85
78
|
file: fileURLToPath(document.uri),
|
|
86
79
|
mode: document.languageId === "jsonc" ? "jsonc" : "json",
|
|
@@ -99,57 +92,6 @@ var Workspace = class {
|
|
|
99
92
|
}
|
|
100
93
|
};
|
|
101
94
|
//#endregion
|
|
102
|
-
//#region src/lsp/hints.ts
|
|
103
|
-
function externalSelection(document) {
|
|
104
|
-
return new Proxy(document, { get(target, key) {
|
|
105
|
-
if (key === "root") return void 0;
|
|
106
|
-
const value = Reflect.get(target, key);
|
|
107
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
108
|
-
} });
|
|
109
|
-
}
|
|
110
|
-
var Hints = class {
|
|
111
|
-
services = /* @__PURE__ */ new WeakMap();
|
|
112
|
-
async service(engine, document) {
|
|
113
|
-
const file = fileURLToPath(document.uri);
|
|
114
|
-
if (!isIncluded(engine.project, file)) return void 0;
|
|
115
|
-
const association = associationFor(engine.project, file);
|
|
116
|
-
if (!association?.schema) return void 0;
|
|
117
|
-
await engine.validator(association.schema);
|
|
118
|
-
let services = this.services.get(engine);
|
|
119
|
-
if (!services) {
|
|
120
|
-
services = /* @__PURE__ */ new Map();
|
|
121
|
-
this.services.set(engine, services);
|
|
122
|
-
}
|
|
123
|
-
let service = services.get(association.schema);
|
|
124
|
-
if (!service) {
|
|
125
|
-
service = getLanguageService({
|
|
126
|
-
schemaRequestService: async (uri) => JSON.stringify((await engine.store.load(uri)).schema),
|
|
127
|
-
workspaceContext: { resolveRelativePath: (relative, resource) => new URL(relative, resource).href }
|
|
128
|
-
});
|
|
129
|
-
service.configure({
|
|
130
|
-
validate: false,
|
|
131
|
-
schemas: [{
|
|
132
|
-
uri: association.schema,
|
|
133
|
-
fileMatch: ["*"]
|
|
134
|
-
}]
|
|
135
|
-
});
|
|
136
|
-
services.set(association.schema, service);
|
|
137
|
-
}
|
|
138
|
-
return service;
|
|
139
|
-
}
|
|
140
|
-
async complete(engine, document, position) {
|
|
141
|
-
const service = await this.service(engine, document);
|
|
142
|
-
return service ? await service.doComplete(document, position, externalSelection(service.parseJSONDocument(document))) : {
|
|
143
|
-
isIncomplete: false,
|
|
144
|
-
items: []
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
async hover(engine, document, position) {
|
|
148
|
-
const service = await this.service(engine, document);
|
|
149
|
-
return service ? await service.doHover(document, position, externalSelection(service.parseJSONDocument(document))) : null;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
//#endregion
|
|
153
95
|
//#region src/lsp/server.ts
|
|
154
96
|
function lspDiagnostics(result, text) {
|
|
155
97
|
return diagnosticViews([...result.diagnostics, ...result.failures.filter((f) => !result.diagnostics.some((d) => d.code === f.code && d.message === f.message)).map((f) => diagnostic(text, f.code, f.message))]).map((d) => ({
|
|
@@ -190,7 +132,11 @@ function startServer() {
|
|
|
190
132
|
dynamicWatch = params.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration ?? false;
|
|
191
133
|
return {
|
|
192
134
|
capabilities: {
|
|
193
|
-
textDocumentSync:
|
|
135
|
+
textDocumentSync: {
|
|
136
|
+
openClose: true,
|
|
137
|
+
change: TextDocumentSyncKind.Incremental,
|
|
138
|
+
save: { includeText: false }
|
|
139
|
+
},
|
|
194
140
|
completionProvider: { triggerCharacters: ["\"", ":"] },
|
|
195
141
|
hoverProvider: true,
|
|
196
142
|
workspace: { workspaceFolders: {
|
|
@@ -205,7 +151,7 @@ function startServer() {
|
|
|
205
151
|
};
|
|
206
152
|
});
|
|
207
153
|
connection.onInitialized(() => {
|
|
208
|
-
if (dynamicWatch) connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: "
|
|
154
|
+
if (dynamicWatch) connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: "**/*" }] }).catch((error) => connection.console.error(String(error)));
|
|
209
155
|
connection.workspace.onDidChangeWorkspaceFolders((event) => {
|
|
210
156
|
const removed = new Set(event.removed.map((f) => fileURLToPath(f.uri)));
|
|
211
157
|
workspace.roots = [...workspace.roots.filter((r) => !removed.has(r)), ...event.added.map((f) => fileURLToPath(f.uri))];
|
|
@@ -261,6 +207,9 @@ function startServer() {
|
|
|
261
207
|
});
|
|
262
208
|
if (document.uri.startsWith("file:") && workspace.close(document.uri)) scheduleAll();
|
|
263
209
|
});
|
|
210
|
+
documents.onDidSave(({ document }) => {
|
|
211
|
+
if (workspace.resourceChanged(document.uri)) refresh();
|
|
212
|
+
});
|
|
264
213
|
connection.onDidChangeWatchedFiles((params) => {
|
|
265
214
|
if (params.changes.some((change) => workspace.resourceChanged(change.uri))) refresh();
|
|
266
215
|
});
|
package/dist/lsp.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lsp.mjs","names":[],"sources":["../src/lsp/workspace.ts","../src/lsp/hints.ts","../src/lsp/server.ts"],"sourcesContent":["import path from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport {\n\tcontains,\n\tdiscoverConfig,\n\tloadProject,\n\treadText,\n\ttype ReadText,\n} from \"../core/config.ts\"\nimport { Engine } from \"../core/engine.ts\"\nimport { CorrectlyError, failure, type FileResult } from \"../core/types.ts\"\nimport { diagnostic } from \"../core/parse.ts\"\n\nexport class Workspace {\n\tonSchema: ((uri: string) => void) | undefined\n\troots: string[]\n\tprivate readonly read: ReadText\n\treadonly documents = new Map<string, TextDocument>()\n\tprivate readonly engines = new Map<string, Promise<Engine>>()\n\tprivate controller = new AbortController()\n\tgeneration = 0\n\tconstructor(roots: string[], read: ReadText = readText) {\n\t\tthis.roots = roots\n\t\tthis.read = read\n\t}\n\n\tinvalidate() {\n\t\tthis.generation++\n\t\tthis.controller.abort()\n\t\tthis.controller = new AbortController()\n\t\tthis.engines.clear()\n\t}\n\n\topen(document: TextDocument): boolean {\n\t\tthis.documents.set(document.uri, document)\n\t\tconst resource =\n\t\t\tpath.basename(fileURLToPath(document.uri)) === \"correctly.config.json\" ||\n\t\t\tthis.isSchema(document.uri)\n\t\tif (resource) this.invalidate()\n\t\treturn resource\n\t}\n\tclose(uri: string): boolean {\n\t\tconst resource =\n\t\t\turi.endsWith(\"/correctly.config.json\") || this.isSchema(uri)\n\t\tthis.documents.delete(uri)\n\t\tif (resource) this.invalidate()\n\t\treturn resource\n\t}\n\tprivate readonly schemaUris = new Set<string>()\n\tprivate isSchema(uri: string): boolean {\n\t\treturn this.schemaUris.has(uri)\n\t}\n\tresourceChanged(uri: string): boolean {\n\t\treturn uri.endsWith(\"/correctly.config.json\") || this.isSchema(uri)\n\t}\n\tprivate readonly readBuffer: ReadText = (uri) => {\n\t\tconst document = this.documents.get(uri)\n\t\treturn document ? Promise.resolve(document.getText()) : this.read(uri)\n\t}\n\n\tasync engineFor(uri: string): Promise<Engine> {\n\t\tconst file = fileURLToPath(uri)\n\t\tconst root = this.roots\n\t\t\t.filter((r) => contains(r, file))\n\t\t\t.sort((a, b) => b.length - a.length)[0]\n\t\tif (this.roots.length && !root)\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"config\",\n\t\t\t\t`Document is outside the editor workspace: ${file}`,\n\t\t\t)\n\t\tconst virtualConfigs = new Set(\n\t\t\t[...this.documents.keys()]\n\t\t\t\t.filter((key) => key.endsWith(\"/correctly.config.json\"))\n\t\t\t\t.map((key) => fileURLToPath(key)),\n\t\t)\n\t\tconst configPath = await discoverConfig(\n\t\t\tpath.dirname(file),\n\t\t\troot,\n\t\t\tvirtualConfigs,\n\t\t)\n\t\tlet promise = this.engines.get(configPath)\n\t\tif (!promise) {\n\t\t\tconst signal = this.controller.signal\n\t\t\tpromise = (async () => {\n\t\t\t\tconst project = await loadProject(configPath, this.readBuffer)\n\t\t\t\tconst readSchema: ReadText = async (schema) => {\n\t\t\t\t\tthis.schemaUris.add(schema)\n\t\t\t\t\tthis.onSchema?.(schema)\n\t\t\t\t\treturn this.readBuffer(schema)\n\t\t\t\t}\n\t\t\t\tconst engine = new Engine(project, { read: readSchema, signal })\n\t\t\t\tawait engine.prepare()\n\t\t\t\tfor (const resource of engine.store.resources.values())\n\t\t\t\t\tthis.schemaUris.add(resource.uri)\n\t\t\t\tsignal.throwIfAborted()\n\t\t\t\treturn engine\n\t\t\t})()\n\t\t\tthis.engines.set(configPath, promise)\n\t\t}\n\t\treturn promise\n\t}\n\n\tasync validate(document: TextDocument): Promise<FileResult> {\n\t\ttry {\n\t\t\tconst engine = await this.engineFor(document.uri)\n\t\t\treturn await engine.validate(\n\t\t\t\tfileURLToPath(document.uri),\n\t\t\t\tdocument.getText(),\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tconst problem = failure(error, fileURLToPath(document.uri))\n\t\t\treturn {\n\t\t\t\tfile: fileURLToPath(document.uri),\n\t\t\t\tmode: document.languageId === \"jsonc\" ? \"jsonc\" : \"json\",\n\t\t\t\tassociation: null,\n\t\t\t\tcoverage: \"syntax-only\",\n\t\t\t\tdiagnostics: [\n\t\t\t\t\tdiagnostic(document.getText(), problem.code, problem.message),\n\t\t\t\t],\n\t\t\t\tfailures: [problem],\n\t\t\t}\n\t\t}\n\t}\n\n\tdocumentPath(uri: string): string {\n\t\treturn fileURLToPath(uri)\n\t}\n\trootUris(): string[] {\n\t\treturn this.roots.map((root) => pathToFileURL(root).href)\n\t}\n}\n","import {\n\tgetLanguageService,\n\ttype JSONDocument,\n\ttype LanguageService,\n\ttype Position,\n} from \"vscode-json-languageservice\"\nimport type { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { fileURLToPath } from \"node:url\"\nimport { associationFor, isIncluded } from \"../core/config.ts\"\nimport type { Engine } from \"../core/engine.ts\"\n\n// Only the schema-selection accessor is hidden. The original AST (including\n// $schema as ordinary data) is retained by every bound traversal method.\nexport function externalSelection(document: JSONDocument): JSONDocument {\n\treturn new Proxy(document, {\n\t\tget(target, key) {\n\t\t\tif (key === \"root\") return undefined\n\t\t\tconst value: unknown = Reflect.get(target, key)\n\t\t\treturn typeof value === \"function\" ? value.bind(target) : value\n\t\t},\n\t})\n}\n\nexport class Hints {\n\tprivate readonly services = new WeakMap<\n\t\tEngine,\n\t\tMap<string, LanguageService>\n\t>()\n\tprivate async service(\n\t\tengine: Engine,\n\t\tdocument: TextDocument,\n\t): Promise<LanguageService | undefined> {\n\t\tconst file = fileURLToPath(document.uri)\n\t\tif (!isIncluded(engine.project, file)) return undefined\n\t\tconst association = associationFor(engine.project, file)\n\t\tif (!association?.schema) return undefined\n\t\tawait engine.validator(association.schema)\n\t\tlet services = this.services.get(engine)\n\t\tif (!services) {\n\t\t\tservices = new Map()\n\t\t\tthis.services.set(engine, services)\n\t\t}\n\t\tlet service = services.get(association.schema)\n\t\tif (!service) {\n\t\t\tservice = getLanguageService({\n\t\t\t\tschemaRequestService: async (uri) =>\n\t\t\t\t\tJSON.stringify((await engine.store.load(uri)).schema),\n\t\t\t\tworkspaceContext: {\n\t\t\t\t\tresolveRelativePath: (relative, resource) =>\n\t\t\t\t\t\tnew URL(relative, resource).href,\n\t\t\t\t},\n\t\t\t})\n\t\t\t// A per-association service prevents overlapping rules being combined.\n\t\t\tservice.configure({\n\t\t\t\tvalidate: false,\n\t\t\t\tschemas: [{ uri: association.schema, fileMatch: [\"*\"] }],\n\t\t\t})\n\t\t\tservices.set(association.schema, service)\n\t\t}\n\t\treturn service\n\t}\n\tasync complete(engine: Engine, document: TextDocument, position: Position) {\n\t\tconst service = await this.service(engine, document)\n\t\treturn service\n\t\t\t? await service.doComplete(\n\t\t\t\t\tdocument,\n\t\t\t\t\tposition,\n\t\t\t\t\texternalSelection(service.parseJSONDocument(document)),\n\t\t\t\t)\n\t\t\t: { isIncomplete: false, items: [] }\n\t}\n\tasync hover(engine: Engine, document: TextDocument, position: Position) {\n\t\tconst service = await this.service(engine, document)\n\t\treturn service\n\t\t\t? await service.doHover(\n\t\t\t\t\tdocument,\n\t\t\t\t\tposition,\n\t\t\t\t\texternalSelection(service.parseJSONDocument(document)),\n\t\t\t\t)\n\t\t\t: null\n\t}\n}\n","#!/usr/bin/env node\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport path from \"node:path\"\nimport {\n\tcreateConnection,\n\tTextDocuments,\n\tTextDocumentSyncKind,\n\tDidChangeWatchedFilesNotification,\n\tResponseError,\n\tLSPErrorCodes,\n\ttype CancellationToken,\n} from \"vscode-languageserver/node\"\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { Workspace } from \"./workspace.ts\"\nimport { Hints } from \"./hints.ts\"\nimport type { FileResult } from \"../core/types.ts\"\nimport { diagnostic } from \"../core/parse.ts\"\nimport { diagnosticViews, diagnosticDetails } from \"../core/diagnostics.ts\"\n\nexport function lspDiagnostics(result: FileResult, text: string) {\n\treturn diagnosticViews([\n\t\t...result.diagnostics,\n\t\t...result.failures\n\t\t\t.filter(\n\t\t\t\t(f) =>\n\t\t\t\t\t!result.diagnostics.some(\n\t\t\t\t\t\t(d) => d.code === f.code && d.message === f.message,\n\t\t\t\t\t),\n\t\t\t)\n\t\t\t.map((f) => diagnostic(text, f.code, f.message)),\n\t]).map((d) => ({\n\t\trange: d.range,\n\t\tmessage: d.message,\n\t\tcode: d.code,\n\t\tseverity: 1 as const,\n\t\tsource: \"correctly\",\n\t\tdata: { pointer: d.pointer },\n\t\t...(d.branches.length\n\t\t\t? {\n\t\t\t\t\trelatedInformation: diagnosticDetails(d).map(\n\t\t\t\t\t\t({ label, diagnostic }) => ({\n\t\t\t\t\t\t\tlocation: {\n\t\t\t\t\t\t\t\turi: pathToFileURL(result.file).href,\n\t\t\t\t\t\t\t\trange: diagnostic.range,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tmessage: `${label}: ${diagnostic.message}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: {}),\n\t}))\n}\n\nexport function startServer() {\n\tconst connection = createConnection()\n\tconst documents = new TextDocuments(TextDocument)\n\tconst workspace = new Workspace([])\n\tconst hints = new Hints()\n\tconst jobs = new Map<\n\t\tstring,\n\t\t{ controller: AbortController; timer: ReturnType<typeof setTimeout> }\n\t>()\n\tlet dynamicWatch = false\n\tconst watchedSchemas = new Set<string>()\n\tworkspace.onSchema = (uri) => {\n\t\tif (!dynamicWatch || !uri.startsWith(\"file:\") || watchedSchemas.has(uri))\n\t\t\treturn\n\t\twatchedSchemas.add(uri)\n\t\tconst file = fileURLToPath(uri)\n\t\tvoid connection.client\n\t\t\t.register(DidChangeWatchedFilesNotification.type, {\n\t\t\t\twatchers: [\n\t\t\t\t\t{\n\t\t\t\t\t\tglobPattern: {\n\t\t\t\t\t\t\tbaseUri: pathToFileURL(path.dirname(file)).href,\n\t\t\t\t\t\t\tpattern: path.basename(file),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t\t.catch((error: unknown) => connection.console.error(String(error)))\n\t}\n\tconnection.onInitialize((params) => {\n\t\tworkspace.roots = (\n\t\t\tparams.workspaceFolders?.map((f) => f.uri) ??\n\t\t\t(params.rootUri ? [params.rootUri] : [])\n\t\t)\n\t\t\t.filter((uri) => uri.startsWith(\"file:\"))\n\t\t\t.map((uri) => fileURLToPath(uri))\n\t\tdynamicWatch =\n\t\t\tparams.capabilities.workspace?.didChangeWatchedFiles\n\t\t\t\t?.dynamicRegistration ?? false\n\t\treturn {\n\t\t\tcapabilities: {\n\t\t\t\ttextDocumentSync: TextDocumentSyncKind.Incremental,\n\t\t\t\tcompletionProvider: { triggerCharacters: ['\"', \":\"] },\n\t\t\t\thoverProvider: true,\n\t\t\t\tworkspace: {\n\t\t\t\t\tworkspaceFolders: { supported: true, changeNotifications: true },\n\t\t\t\t},\n\t\t\t},\n\t\t\tserverInfo: { name: \"correctly\", version: \"0.0.0\" },\n\t\t}\n\t})\n\tconnection.onInitialized(() => {\n\t\tif (dynamicWatch)\n\t\t\tvoid connection.client\n\t\t\t\t.register(DidChangeWatchedFilesNotification.type, {\n\t\t\t\t\twatchers: [{ globPattern: \"**/*.{json,jsonc}\" }],\n\t\t\t\t})\n\t\t\t\t.catch((error: unknown) => connection.console.error(String(error)))\n\t\tconnection.workspace.onDidChangeWorkspaceFolders((event) => {\n\t\t\tconst removed = new Set(event.removed.map((f) => fileURLToPath(f.uri)))\n\t\t\tworkspace.roots = [\n\t\t\t\t...workspace.roots.filter((r) => !removed.has(r)),\n\t\t\t\t...event.added.map((f) => fileURLToPath(f.uri)),\n\t\t\t]\n\t\t\trefresh()\n\t\t})\n\t})\n\tfunction schedule(document: TextDocument) {\n\t\tif (!document.uri.startsWith(\"file:\")) return\n\t\tconst previous = jobs.get(document.uri)\n\t\tif (previous) {\n\t\t\tclearTimeout(previous.timer)\n\t\t\tprevious.controller.abort()\n\t\t}\n\t\tconst controller = new AbortController()\n\t\tconst generation = workspace.generation\n\t\tconst timer = setTimeout(() => {\n\t\t\tvoid workspace\n\t\t\t\t.validate(document)\n\t\t\t\t.then((result) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcontroller.signal.aborted ||\n\t\t\t\t\t\tworkspace.generation !== generation ||\n\t\t\t\t\t\tdocuments.get(document.uri)?.version !== document.version\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\treturn connection.sendDiagnostics({\n\t\t\t\t\t\turi: document.uri,\n\t\t\t\t\t\tversion: document.version,\n\t\t\t\t\t\tdiagnostics: lspDiagnostics(result, document.getText()),\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t.catch((error: unknown) => connection.console.error(String(error)))\n\t\t}, 40)\n\t\tjobs.set(document.uri, { controller, timer })\n\t}\n\tfunction scheduleAll() {\n\t\tfor (const document of documents.all()) schedule(document)\n\t}\n\tfunction refresh() {\n\t\tworkspace.invalidate()\n\t\tscheduleAll()\n\t}\n\tdocuments.onDidChangeContent(({ document }) => {\n\t\tif (!document.uri.startsWith(\"file:\")) return\n\t\tconst resourceChanged = workspace.open(document)\n\t\tif (resourceChanged) scheduleAll()\n\t\telse schedule(document)\n\t})\n\tdocuments.onDidClose(async ({ document }) => {\n\t\tconst job = jobs.get(document.uri)\n\t\tif (job) {\n\t\t\tclearTimeout(job.timer)\n\t\t\tjob.controller.abort()\n\t\t\tjobs.delete(document.uri)\n\t\t}\n\t\tawait connection.sendDiagnostics({ uri: document.uri, diagnostics: [] })\n\t\tif (document.uri.startsWith(\"file:\") && workspace.close(document.uri))\n\t\t\tscheduleAll()\n\t})\n\tconnection.onDidChangeWatchedFiles((params) => {\n\t\t// Cache writes and unrelated JSON files must not trigger schema reloads.\n\t\tif (params.changes.some((change) => workspace.resourceChanged(change.uri)))\n\t\t\trefresh()\n\t})\n\tconnection.onDidChangeConfiguration(refresh)\n\tasync function request<T>(\n\t\turi: string,\n\t\ttoken: CancellationToken,\n\t\taction: (\n\t\t\tengine: Awaited<ReturnType<Workspace[\"engineFor\"]>>,\n\t\t\tdocument: TextDocument,\n\t\t) => Promise<T>,\n\t): Promise<T | null> {\n\t\tconst document = documents.get(uri)\n\t\tif (!document || !uri.startsWith(\"file:\")) return null\n\t\tconst generation = workspace.generation\n\t\tconst cancelled = () =>\n\t\t\ttoken.isCancellationRequested ||\n\t\t\tworkspace.generation !== generation ||\n\t\t\tdocuments.get(uri)?.version !== document.version\n\t\tif (cancelled())\n\t\t\tthrow new ResponseError(\n\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\"Request cancelled\",\n\t\t\t)\n\t\tconst operation = (async () => {\n\t\t\ttry {\n\t\t\t\tconst engine = await workspace.engineFor(uri)\n\t\t\t\tif (cancelled())\n\t\t\t\t\tthrow new ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t)\n\t\t\t\tconst result = await action(engine, document)\n\t\t\t\tif (cancelled())\n\t\t\t\t\tthrow new ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t)\n\t\t\t\treturn result\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ResponseError) throw error\n\t\t\t\tif (cancelled())\n\t\t\t\t\tthrow new ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t)\n\t\t\t\tconnection.console.error(String(error))\n\t\t\t\treturn null\n\t\t\t}\n\t\t})()\n\t\tlet listener: { dispose(): void } | undefined\n\t\tconst cancellation = new Promise<never>((_resolve, reject) => {\n\t\t\tlistener = token.onCancellationRequested(() =>\n\t\t\t\treject(\n\t\t\t\t\tnew ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t})\n\t\ttry {\n\t\t\treturn await Promise.race([operation, cancellation])\n\t\t} finally {\n\t\t\tlistener?.dispose()\n\t\t}\n\t}\n\tconnection.onCompletion((params, token) =>\n\t\trequest(params.textDocument.uri, token, (engine, document) =>\n\t\t\thints.complete(engine, document, params.position),\n\t\t),\n\t)\n\tconnection.onHover((params, token) =>\n\t\trequest(params.textDocument.uri, token, (engine, document) =>\n\t\t\thints.hover(engine, document, params.position),\n\t\t),\n\t)\n\tconnection.onShutdown(() => {\n\t\tworkspace.invalidate()\n\t\tfor (const job of jobs.values()) {\n\t\t\tclearTimeout(job.timer)\n\t\t\tjob.controller.abort()\n\t\t}\n\t\tjobs.clear()\n\t})\n\tdocuments.listen(connection)\n\tconnection.listen()\n}\n\nstartServer()\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lsp.mjs","names":[],"sources":["../src/lsp/workspace.ts","../src/lsp/server.ts"],"sourcesContent":["import path from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { contains, discoverConfig, CONFIG_NAME } from \"../core/config.ts\"\nimport { ProjectSession } from \"../runtime/session.ts\"\nimport { CorrectlyError, failure, type FileResult } from \"../core/types.ts\"\nimport { diagnostic } from \"../core/parse.ts\"\n\nexport class Workspace {\n\tonSchema: ((uri: string) => void) | undefined\n\troots: string[]\n\treadonly documents = new Map<string, TextDocument>()\n\tprivate readonly engines = new Map<string, ProjectSession>()\n\tgeneration = 0\n\tconstructor(roots: string[]) {\n\t\tthis.roots = roots\n\t}\n\n\tinvalidate() {\n\t\tthis.generation++\n\t\tfor (const session of this.engines.values()) void session.dispose()\n\t\tthis.engines.clear()\n\t}\n\n\topen(document: TextDocument): boolean {\n\t\tthis.documents.set(document.uri, document)\n\t\tconst resource =\n\t\t\tthis.isSchema(document.uri) && !this.moduleUris.has(document.uri)\n\t\tif (resource) this.invalidate()\n\t\treturn resource\n\t}\n\tclose(uri: string): boolean {\n\t\tconst resource = this.isSchema(uri) && !this.moduleUris.has(uri)\n\t\tthis.documents.delete(uri)\n\t\tif (resource) this.invalidate()\n\t\treturn resource\n\t}\n\tprivate readonly schemaUris = new Set<string>()\n\tprivate isSchema(uri: string): boolean {\n\t\treturn this.schemaUris.has(uri)\n\t}\n\tprivate readonly moduleUris = new Set<string>()\n\tresourceChanged(uri: string): boolean {\n\t\treturn (\n\t\t\turi.endsWith(`/${CONFIG_NAME}`) ||\n\t\t\tthis.isSchema(uri) ||\n\t\t\tthis.moduleUris.has(uri) ||\n\t\t\t/\\/(?:package\\.json|pnpm-lock\\.yaml|package-lock\\.json|yarn\\.lock)$/.test(\n\t\t\t\turi,\n\t\t\t)\n\t\t)\n\t}\n\n\tasync engineFor(uri: string): Promise<ProjectSession> {\n\t\tconst file = fileURLToPath(uri)\n\t\tconst root = this.roots\n\t\t\t.filter((r) => contains(r, file))\n\t\t\t.sort((a, b) => b.length - a.length)[0]\n\t\tif (this.roots.length && !root)\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"config\",\n\t\t\t\t`Document is outside the editor workspace: ${file}`,\n\t\t\t)\n\t\tconst configPath = await discoverConfig(path.dirname(file), root)\n\t\tlet session = this.engines.get(configPath)\n\t\tif (!session) {\n\t\t\tsession = new ProjectSession(configPath, {\n\t\t\t\tbuffers: [...this.documents].map(([uri, document]) => [\n\t\t\t\t\turi,\n\t\t\t\t\tdocument.getText(),\n\t\t\t\t]),\n\t\t\t\tonDependency: (dependency, kind) => {\n\t\t\t\t\t;(kind === \"module\" ? this.moduleUris : this.schemaUris).add(\n\t\t\t\t\t\tdependency,\n\t\t\t\t\t)\n\t\t\t\t\tthis.onSchema?.(dependency)\n\t\t\t\t},\n\t\t\t})\n\t\t\tthis.engines.set(configPath, session)\n\t\t}\n\t\tawait session.ready\n\t\treturn session\n\t}\n\n\tasync validate(document: TextDocument): Promise<FileResult> {\n\t\ttry {\n\t\t\tconst engine = await this.engineFor(document.uri)\n\t\t\treturn await engine.validate(\n\t\t\t\tfileURLToPath(document.uri),\n\t\t\t\tdocument.getText(),\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tconst problem = failure(error, fileURLToPath(document.uri))\n\t\t\tif (\n\t\t\t\tproblem.code === \"config\" &&\n\t\t\t\tproblem.message.startsWith(`No ${CONFIG_NAME} found`) &&\n\t\t\t\t![\"json\", \"jsonc\"].includes(document.languageId)\n\t\t\t)\n\t\t\t\treturn {\n\t\t\t\t\tfile: fileURLToPath(document.uri),\n\t\t\t\t\tmode: document.languageId,\n\t\t\t\t\tassociation: null,\n\t\t\t\t\tcoverage: \"excluded\",\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\tfailures: [],\n\t\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tfile: fileURLToPath(document.uri),\n\t\t\t\tmode: document.languageId === \"jsonc\" ? \"jsonc\" : \"json\",\n\t\t\t\tassociation: null,\n\t\t\t\tcoverage: \"syntax-only\",\n\t\t\t\tdiagnostics: [\n\t\t\t\t\tdiagnostic(document.getText(), problem.code, problem.message),\n\t\t\t\t],\n\t\t\t\tfailures: [problem],\n\t\t\t}\n\t\t}\n\t}\n\n\tdocumentPath(uri: string): string {\n\t\treturn fileURLToPath(uri)\n\t}\n\trootUris(): string[] {\n\t\treturn this.roots.map((root) => pathToFileURL(root).href)\n\t}\n}\n","#!/usr/bin/env node\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport path from \"node:path\"\nimport {\n\tcreateConnection,\n\tTextDocuments,\n\tTextDocumentSyncKind,\n\tDidChangeWatchedFilesNotification,\n\tResponseError,\n\tLSPErrorCodes,\n\ttype CancellationToken,\n} from \"vscode-languageserver/node\"\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { Workspace } from \"./workspace.ts\"\nimport { Hints } from \"./hints.ts\"\nimport type { FileResult } from \"../core/types.ts\"\nimport { diagnostic } from \"../core/parse.ts\"\nimport { diagnosticViews, diagnosticDetails } from \"../core/diagnostics.ts\"\n\nexport function lspDiagnostics(result: FileResult, text: string) {\n\treturn diagnosticViews([\n\t\t...result.diagnostics,\n\t\t...result.failures\n\t\t\t.filter(\n\t\t\t\t(f) =>\n\t\t\t\t\t!result.diagnostics.some(\n\t\t\t\t\t\t(d) => d.code === f.code && d.message === f.message,\n\t\t\t\t\t),\n\t\t\t)\n\t\t\t.map((f) => diagnostic(text, f.code, f.message)),\n\t]).map((d) => ({\n\t\trange: d.range,\n\t\tmessage: d.message,\n\t\tcode: d.code,\n\t\tseverity: 1 as const,\n\t\tsource: \"correctly\",\n\t\tdata: { pointer: d.pointer },\n\t\t...(d.branches.length\n\t\t\t? {\n\t\t\t\t\trelatedInformation: diagnosticDetails(d).map(\n\t\t\t\t\t\t({ label, diagnostic }) => ({\n\t\t\t\t\t\t\tlocation: {\n\t\t\t\t\t\t\t\turi: pathToFileURL(result.file).href,\n\t\t\t\t\t\t\t\trange: diagnostic.range,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tmessage: `${label}: ${diagnostic.message}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t: {}),\n\t}))\n}\n\nexport function startServer() {\n\tconst connection = createConnection()\n\tconst documents = new TextDocuments(TextDocument)\n\tconst workspace = new Workspace([])\n\tconst hints = new Hints()\n\tconst jobs = new Map<\n\t\tstring,\n\t\t{ controller: AbortController; timer: ReturnType<typeof setTimeout> }\n\t>()\n\tlet dynamicWatch = false\n\tconst watchedSchemas = new Set<string>()\n\tworkspace.onSchema = (uri) => {\n\t\tif (!dynamicWatch || !uri.startsWith(\"file:\") || watchedSchemas.has(uri))\n\t\t\treturn\n\t\twatchedSchemas.add(uri)\n\t\tconst file = fileURLToPath(uri)\n\t\tvoid connection.client\n\t\t\t.register(DidChangeWatchedFilesNotification.type, {\n\t\t\t\twatchers: [\n\t\t\t\t\t{\n\t\t\t\t\t\tglobPattern: {\n\t\t\t\t\t\t\tbaseUri: pathToFileURL(path.dirname(file)).href,\n\t\t\t\t\t\t\tpattern: path.basename(file),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t})\n\t\t\t.catch((error: unknown) => connection.console.error(String(error)))\n\t}\n\tconnection.onInitialize((params) => {\n\t\tworkspace.roots = (\n\t\t\tparams.workspaceFolders?.map((f) => f.uri) ??\n\t\t\t(params.rootUri ? [params.rootUri] : [])\n\t\t)\n\t\t\t.filter((uri) => uri.startsWith(\"file:\"))\n\t\t\t.map((uri) => fileURLToPath(uri))\n\t\tdynamicWatch =\n\t\t\tparams.capabilities.workspace?.didChangeWatchedFiles\n\t\t\t\t?.dynamicRegistration ?? false\n\t\treturn {\n\t\t\tcapabilities: {\n\t\t\t\ttextDocumentSync: {\n\t\t\t\t\topenClose: true,\n\t\t\t\t\tchange: TextDocumentSyncKind.Incremental,\n\t\t\t\t\tsave: { includeText: false },\n\t\t\t\t},\n\t\t\t\tcompletionProvider: { triggerCharacters: ['\"', \":\"] },\n\t\t\t\thoverProvider: true,\n\t\t\t\tworkspace: {\n\t\t\t\t\tworkspaceFolders: { supported: true, changeNotifications: true },\n\t\t\t\t},\n\t\t\t},\n\t\t\tserverInfo: { name: \"correctly\", version: \"0.0.0\" },\n\t\t}\n\t})\n\tconnection.onInitialized(() => {\n\t\tif (dynamicWatch)\n\t\t\tvoid connection.client\n\t\t\t\t.register(DidChangeWatchedFilesNotification.type, {\n\t\t\t\t\twatchers: [{ globPattern: \"**/*\" }],\n\t\t\t\t})\n\t\t\t\t.catch((error: unknown) => connection.console.error(String(error)))\n\t\tconnection.workspace.onDidChangeWorkspaceFolders((event) => {\n\t\t\tconst removed = new Set(event.removed.map((f) => fileURLToPath(f.uri)))\n\t\t\tworkspace.roots = [\n\t\t\t\t...workspace.roots.filter((r) => !removed.has(r)),\n\t\t\t\t...event.added.map((f) => fileURLToPath(f.uri)),\n\t\t\t]\n\t\t\trefresh()\n\t\t})\n\t})\n\tfunction schedule(document: TextDocument) {\n\t\tif (!document.uri.startsWith(\"file:\")) return\n\t\tconst previous = jobs.get(document.uri)\n\t\tif (previous) {\n\t\t\tclearTimeout(previous.timer)\n\t\t\tprevious.controller.abort()\n\t\t}\n\t\tconst controller = new AbortController()\n\t\tconst generation = workspace.generation\n\t\tconst timer = setTimeout(() => {\n\t\t\tvoid workspace\n\t\t\t\t.validate(document)\n\t\t\t\t.then((result) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcontroller.signal.aborted ||\n\t\t\t\t\t\tworkspace.generation !== generation ||\n\t\t\t\t\t\tdocuments.get(document.uri)?.version !== document.version\n\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\treturn connection.sendDiagnostics({\n\t\t\t\t\t\turi: document.uri,\n\t\t\t\t\t\tversion: document.version,\n\t\t\t\t\t\tdiagnostics: lspDiagnostics(result, document.getText()),\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t.catch((error: unknown) => connection.console.error(String(error)))\n\t\t}, 40)\n\t\tjobs.set(document.uri, { controller, timer })\n\t}\n\tfunction scheduleAll() {\n\t\tfor (const document of documents.all()) schedule(document)\n\t}\n\tfunction refresh() {\n\t\tworkspace.invalidate()\n\t\tscheduleAll()\n\t}\n\tdocuments.onDidChangeContent(({ document }) => {\n\t\tif (!document.uri.startsWith(\"file:\")) return\n\t\tconst resourceChanged = workspace.open(document)\n\t\tif (resourceChanged) scheduleAll()\n\t\telse schedule(document)\n\t})\n\tdocuments.onDidClose(async ({ document }) => {\n\t\tconst job = jobs.get(document.uri)\n\t\tif (job) {\n\t\t\tclearTimeout(job.timer)\n\t\t\tjob.controller.abort()\n\t\t\tjobs.delete(document.uri)\n\t\t}\n\t\tawait connection.sendDiagnostics({ uri: document.uri, diagnostics: [] })\n\t\tif (document.uri.startsWith(\"file:\") && workspace.close(document.uri))\n\t\t\tscheduleAll()\n\t})\n\tdocuments.onDidSave(({ document }) => {\n\t\tif (workspace.resourceChanged(document.uri)) refresh()\n\t})\n\tconnection.onDidChangeWatchedFiles((params) => {\n\t\t// Cache writes and unrelated JSON files must not trigger schema reloads.\n\t\tif (params.changes.some((change) => workspace.resourceChanged(change.uri)))\n\t\t\trefresh()\n\t})\n\tconnection.onDidChangeConfiguration(refresh)\n\tasync function request<T>(\n\t\turi: string,\n\t\ttoken: CancellationToken,\n\t\taction: (\n\t\t\tengine: Awaited<ReturnType<Workspace[\"engineFor\"]>>,\n\t\t\tdocument: TextDocument,\n\t\t) => Promise<T>,\n\t): Promise<T | null> {\n\t\tconst document = documents.get(uri)\n\t\tif (!document || !uri.startsWith(\"file:\")) return null\n\t\tconst generation = workspace.generation\n\t\tconst cancelled = () =>\n\t\t\ttoken.isCancellationRequested ||\n\t\t\tworkspace.generation !== generation ||\n\t\t\tdocuments.get(uri)?.version !== document.version\n\t\tif (cancelled())\n\t\t\tthrow new ResponseError(\n\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\"Request cancelled\",\n\t\t\t)\n\t\tconst operation = (async () => {\n\t\t\ttry {\n\t\t\t\tconst engine = await workspace.engineFor(uri)\n\t\t\t\tif (cancelled())\n\t\t\t\t\tthrow new ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t)\n\t\t\t\tconst result = await action(engine, document)\n\t\t\t\tif (cancelled())\n\t\t\t\t\tthrow new ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t)\n\t\t\t\treturn result\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ResponseError) throw error\n\t\t\t\tif (cancelled())\n\t\t\t\t\tthrow new ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t)\n\t\t\t\tconnection.console.error(String(error))\n\t\t\t\treturn null\n\t\t\t}\n\t\t})()\n\t\tlet listener: { dispose(): void } | undefined\n\t\tconst cancellation = new Promise<never>((_resolve, reject) => {\n\t\t\tlistener = token.onCancellationRequested(() =>\n\t\t\t\treject(\n\t\t\t\t\tnew ResponseError(\n\t\t\t\t\t\tLSPErrorCodes.RequestCancelled,\n\t\t\t\t\t\t\"Request cancelled\",\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t})\n\t\ttry {\n\t\t\treturn await Promise.race([operation, cancellation])\n\t\t} finally {\n\t\t\tlistener?.dispose()\n\t\t}\n\t}\n\tconnection.onCompletion((params, token) =>\n\t\trequest(params.textDocument.uri, token, (engine, document) =>\n\t\t\thints.complete(engine, document, params.position),\n\t\t),\n\t)\n\tconnection.onHover((params, token) =>\n\t\trequest(params.textDocument.uri, token, (engine, document) =>\n\t\t\thints.hover(engine, document, params.position),\n\t\t),\n\t)\n\tconnection.onShutdown(() => {\n\t\tworkspace.invalidate()\n\t\tfor (const job of jobs.values()) {\n\t\t\tclearTimeout(job.timer)\n\t\t\tjob.controller.abort()\n\t\t}\n\t\tjobs.clear()\n\t})\n\tdocuments.listen(connection)\n\tconnection.listen()\n}\n\nstartServer()\n"],"mappings":";;;;;;;;;;AAQA,IAAa,YAAb,MAAuB;CACtB;CACA;CACA,4BAAqB,IAAI,IAA0B;CACnD,0BAA2B,IAAI,IAA4B;CAC3D,aAAa;CACb,YAAY,OAAiB;EAC5B,KAAK,QAAQ;CACd;CAEA,aAAa;EACZ,KAAK;EACL,KAAK,MAAM,WAAW,KAAK,QAAQ,OAAO,GAAG,QAAa,QAAQ;EAClE,KAAK,QAAQ,MAAM;CACpB;CAEA,KAAK,UAAiC;EACrC,KAAK,UAAU,IAAI,SAAS,KAAK,QAAQ;EACzC,MAAM,WACL,KAAK,SAAS,SAAS,GAAG,KAAK,CAAC,KAAK,WAAW,IAAI,SAAS,GAAG;EACjE,IAAI,UAAU,KAAK,WAAW;EAC9B,OAAO;CACR;CACA,MAAM,KAAsB;EAC3B,MAAM,WAAW,KAAK,SAAS,GAAG,KAAK,CAAC,KAAK,WAAW,IAAI,GAAG;EAC/D,KAAK,UAAU,OAAO,GAAG;EACzB,IAAI,UAAU,KAAK,WAAW;EAC9B,OAAO;CACR;CACA,6BAA8B,IAAI,IAAY;CAC9C,SAAiB,KAAsB;EACtC,OAAO,KAAK,WAAW,IAAI,GAAG;CAC/B;CACA,6BAA8B,IAAI,IAAY;CAC9C,gBAAgB,KAAsB;EACrC,OACC,IAAI,SAAS,sBAAiB,KAC9B,KAAK,SAAS,GAAG,KACjB,KAAK,WAAW,IAAI,GAAG,KACvB,qEAAqE,KACpE,GACD;CAEF;CAEA,MAAM,UAAU,KAAsC;EACrD,MAAM,OAAO,cAAc,GAAG;EAC9B,MAAM,OAAO,KAAK,MAChB,QAAQ,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAChC,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;EACtC,IAAI,KAAK,MAAM,UAAU,CAAC,MACzB,MAAM,IAAI,eACT,UACA,6CAA6C,MAC9C;EACD,MAAM,aAAa,MAAM,eAAe,KAAK,QAAQ,IAAI,GAAG,IAAI;EAChE,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU;EACzC,IAAI,CAAC,SAAS;GACb,UAAU,IAAI,eAAe,YAAY;IACxC,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,KAAK,CAAC,KAAK,cAAc,CACrD,KACA,SAAS,QAAQ,CAClB,CAAC;IACD,eAAe,YAAY,SAAS;KAClC,CAAC,SAAS,WAAW,KAAK,aAAa,KAAK,WAAA,CAAY,IACxD,UACD;KACA,KAAK,WAAW,UAAU;IAC3B;GACD,CAAC;GACD,KAAK,QAAQ,IAAI,YAAY,OAAO;EACrC;EACA,MAAM,QAAQ;EACd,OAAO;CACR;CAEA,MAAM,SAAS,UAA6C;EAC3D,IAAI;GAEH,OAAO,OAAM,MADQ,KAAK,UAAU,SAAS,GAAG,EAAA,CAC5B,SACnB,cAAc,SAAS,GAAG,GAC1B,SAAS,QAAQ,CAClB;EACD,SAAS,OAAO;GACf,MAAM,UAAU,QAAQ,OAAO,cAAc,SAAS,GAAG,CAAC;GAC1D,IACC,QAAQ,SAAS,YACjB,QAAQ,QAAQ,WAAW,8BAAyB,KACpD,CAAC,CAAC,QAAQ,OAAO,CAAC,CAAC,SAAS,SAAS,UAAU,GAE/C,OAAO;IACN,MAAM,cAAc,SAAS,GAAG;IAChC,MAAM,SAAS;IACf,aAAa;IACb,UAAU;IACV,aAAa,CAAC;IACd,UAAU,CAAC;GACZ;GAED,OAAO;IACN,MAAM,cAAc,SAAS,GAAG;IAChC,MAAM,SAAS,eAAe,UAAU,UAAU;IAClD,aAAa;IACb,UAAU;IACV,aAAa,CACZ,WAAW,SAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,OAAO,CAC7D;IACA,UAAU,CAAC,OAAO;GACnB;EACD;CACD;CAEA,aAAa,KAAqB;EACjC,OAAO,cAAc,GAAG;CACzB;CACA,WAAqB;EACpB,OAAO,KAAK,MAAM,KAAK,SAAS,cAAc,IAAI,CAAC,CAAC,IAAI;CACzD;AACD;;;AC3GA,SAAgB,eAAe,QAAoB,MAAc;CAChE,OAAO,gBAAgB,CACtB,GAAG,OAAO,aACV,GAAG,OAAO,SACR,QACC,MACA,CAAC,OAAO,YAAY,MAClB,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,OAC7C,CACF,CAAC,CACA,KAAK,MAAM,WAAW,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CACjD,CAAC,CAAC,CAAC,KAAK,OAAO;EACd,OAAO,EAAE;EACT,SAAS,EAAE;EACX,MAAM,EAAE;EACR,UAAU;EACV,QAAQ;EACR,MAAM,EAAE,SAAS,EAAE,QAAQ;EAC3B,GAAI,EAAE,SAAS,SACZ,EACA,oBAAoB,kBAAkB,CAAC,CAAC,CAAC,KACvC,EAAE,OAAO,kBAAkB;GAC3B,UAAU;IACT,KAAK,cAAc,OAAO,IAAI,CAAC,CAAC;IAChC,OAAO,WAAW;GACnB;GACA,SAAS,GAAG,MAAM,IAAI,WAAW;EAClC,EACD,EACD,IACC,CAAC;CACL,EAAE;AACH;AAEA,SAAgB,cAAc;CAC7B,MAAM,aAAa,iBAAiB;CACpC,MAAM,YAAY,IAAI,cAAc,YAAY;CAChD,MAAM,YAAY,IAAI,UAAU,CAAC,CAAC;CAClC,MAAM,QAAQ,IAAI,MAAM;CACxB,MAAM,uBAAO,IAAI,IAGf;CACF,IAAI,eAAe;CACnB,MAAM,iCAAiB,IAAI,IAAY;CACvC,UAAU,YAAY,QAAQ;EAC7B,IAAI,CAAC,gBAAgB,CAAC,IAAI,WAAW,OAAO,KAAK,eAAe,IAAI,GAAG,GACtE;EACD,eAAe,IAAI,GAAG;EACtB,MAAM,OAAO,cAAc,GAAG;EAC9B,WAAgB,OACd,SAAS,kCAAkC,MAAM,EACjD,UAAU,CACT,EACC,aAAa;GACZ,SAAS,cAAc,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC;GAC3C,SAAS,KAAK,SAAS,IAAI;EAC5B,EACD,CACD,EACD,CAAC,CAAC,CACD,OAAO,UAAmB,WAAW,QAAQ,MAAM,OAAO,KAAK,CAAC,CAAC;CACpE;CACA,WAAW,cAAc,WAAW;EACnC,UAAU,SACT,OAAO,kBAAkB,KAAK,MAAM,EAAE,GAAG,MACxC,OAAO,UAAU,CAAC,OAAO,OAAO,IAAI,CAAC,GAAA,CAErC,QAAQ,QAAQ,IAAI,WAAW,OAAO,CAAC,CAAC,CACxC,KAAK,QAAQ,cAAc,GAAG,CAAC;EACjC,eACC,OAAO,aAAa,WAAW,uBAC5B,uBAAuB;EAC3B,OAAO;GACN,cAAc;IACb,kBAAkB;KACjB,WAAW;KACX,QAAQ,qBAAqB;KAC7B,MAAM,EAAE,aAAa,MAAM;IAC5B;IACA,oBAAoB,EAAE,mBAAmB,CAAC,MAAK,GAAG,EAAE;IACpD,eAAe;IACf,WAAW,EACV,kBAAkB;KAAE,WAAW;KAAM,qBAAqB;IAAK,EAChE;GACD;GACA,YAAY;IAAE,MAAM;IAAa,SAAS;GAAQ;EACnD;CACD,CAAC;CACD,WAAW,oBAAoB;EAC9B,IAAI,cACH,WAAgB,OACd,SAAS,kCAAkC,MAAM,EACjD,UAAU,CAAC,EAAE,aAAa,OAAO,CAAC,EACnC,CAAC,CAAC,CACD,OAAO,UAAmB,WAAW,QAAQ,MAAM,OAAO,KAAK,CAAC,CAAC;EACpE,WAAW,UAAU,6BAA6B,UAAU;GAC3D,MAAM,UAAU,IAAI,IAAI,MAAM,QAAQ,KAAK,MAAM,cAAc,EAAE,GAAG,CAAC,CAAC;GACtE,UAAU,QAAQ,CACjB,GAAG,UAAU,MAAM,QAAQ,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,GAChD,GAAG,MAAM,MAAM,KAAK,MAAM,cAAc,EAAE,GAAG,CAAC,CAC/C;GACA,QAAQ;EACT,CAAC;CACF,CAAC;CACD,SAAS,SAAS,UAAwB;EACzC,IAAI,CAAC,SAAS,IAAI,WAAW,OAAO,GAAG;EACvC,MAAM,WAAW,KAAK,IAAI,SAAS,GAAG;EACtC,IAAI,UAAU;GACb,aAAa,SAAS,KAAK;GAC3B,SAAS,WAAW,MAAM;EAC3B;EACA,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,aAAa,UAAU;EAC7B,MAAM,QAAQ,iBAAiB;GAC9B,UACE,SAAS,QAAQ,CAAC,CAClB,MAAM,WAAW;IACjB,IACC,WAAW,OAAO,WAClB,UAAU,eAAe,cACzB,UAAU,IAAI,SAAS,GAAG,CAAC,EAAE,YAAY,SAAS,SAElD;IACD,OAAO,WAAW,gBAAgB;KACjC,KAAK,SAAS;KACd,SAAS,SAAS;KAClB,aAAa,eAAe,QAAQ,SAAS,QAAQ,CAAC;IACvD,CAAC;GACF,CAAC,CAAC,CACD,OAAO,UAAmB,WAAW,QAAQ,MAAM,OAAO,KAAK,CAAC,CAAC;EACpE,GAAG,EAAE;EACL,KAAK,IAAI,SAAS,KAAK;GAAE;GAAY;EAAM,CAAC;CAC7C;CACA,SAAS,cAAc;EACtB,KAAK,MAAM,YAAY,UAAU,IAAI,GAAG,SAAS,QAAQ;CAC1D;CACA,SAAS,UAAU;EAClB,UAAU,WAAW;EACrB,YAAY;CACb;CACA,UAAU,oBAAoB,EAAE,eAAe;EAC9C,IAAI,CAAC,SAAS,IAAI,WAAW,OAAO,GAAG;EAEvC,IADwB,UAAU,KAAK,QACrB,GAAG,YAAY;OAC5B,SAAS,QAAQ;CACvB,CAAC;CACD,UAAU,WAAW,OAAO,EAAE,eAAe;EAC5C,MAAM,MAAM,KAAK,IAAI,SAAS,GAAG;EACjC,IAAI,KAAK;GACR,aAAa,IAAI,KAAK;GACtB,IAAI,WAAW,MAAM;GACrB,KAAK,OAAO,SAAS,GAAG;EACzB;EACA,MAAM,WAAW,gBAAgB;GAAE,KAAK,SAAS;GAAK,aAAa,CAAC;EAAE,CAAC;EACvE,IAAI,SAAS,IAAI,WAAW,OAAO,KAAK,UAAU,MAAM,SAAS,GAAG,GACnE,YAAY;CACd,CAAC;CACD,UAAU,WAAW,EAAE,eAAe;EACrC,IAAI,UAAU,gBAAgB,SAAS,GAAG,GAAG,QAAQ;CACtD,CAAC;CACD,WAAW,yBAAyB,WAAW;EAE9C,IAAI,OAAO,QAAQ,MAAM,WAAW,UAAU,gBAAgB,OAAO,GAAG,CAAC,GACxE,QAAQ;CACV,CAAC;CACD,WAAW,yBAAyB,OAAO;CAC3C,eAAe,QACd,KACA,OACA,QAIoB;EACpB,MAAM,WAAW,UAAU,IAAI,GAAG;EAClC,IAAI,CAAC,YAAY,CAAC,IAAI,WAAW,OAAO,GAAG,OAAO;EAClD,MAAM,aAAa,UAAU;EAC7B,MAAM,kBACL,MAAM,2BACN,UAAU,eAAe,cACzB,UAAU,IAAI,GAAG,CAAC,EAAE,YAAY,SAAS;EAC1C,IAAI,UAAU,GACb,MAAM,IAAI,cACT,cAAc,kBACd,mBACD;EACD,MAAM,aAAa,YAAY;GAC9B,IAAI;IACH,MAAM,SAAS,MAAM,UAAU,UAAU,GAAG;IAC5C,IAAI,UAAU,GACb,MAAM,IAAI,cACT,cAAc,kBACd,mBACD;IACD,MAAM,SAAS,MAAM,OAAO,QAAQ,QAAQ;IAC5C,IAAI,UAAU,GACb,MAAM,IAAI,cACT,cAAc,kBACd,mBACD;IACD,OAAO;GACR,SAAS,OAAO;IACf,IAAI,iBAAiB,eAAe,MAAM;IAC1C,IAAI,UAAU,GACb,MAAM,IAAI,cACT,cAAc,kBACd,mBACD;IACD,WAAW,QAAQ,MAAM,OAAO,KAAK,CAAC;IACtC,OAAO;GACR;EACD,EAAA,CAAG;EACH,IAAI;EACJ,MAAM,eAAe,IAAI,SAAgB,UAAU,WAAW;GAC7D,WAAW,MAAM,8BAChB,OACC,IAAI,cACH,cAAc,kBACd,mBACD,CACD,CACD;EACD,CAAC;EACD,IAAI;GACH,OAAO,MAAM,QAAQ,KAAK,CAAC,WAAW,YAAY,CAAC;EACpD,UAAU;GACT,UAAU,QAAQ;EACnB;CACD;CACA,WAAW,cAAc,QAAQ,UAChC,QAAQ,OAAO,aAAa,KAAK,QAAQ,QAAQ,aAChD,MAAM,SAAS,QAAQ,UAAU,OAAO,QAAQ,CACjD,CACD;CACA,WAAW,SAAS,QAAQ,UAC3B,QAAQ,OAAO,aAAa,KAAK,QAAQ,QAAQ,aAChD,MAAM,MAAM,QAAQ,UAAU,OAAO,QAAQ,CAC9C,CACD;CACA,WAAW,iBAAiB;EAC3B,UAAU,WAAW;EACrB,KAAK,MAAM,OAAO,KAAK,OAAO,GAAG;GAChC,aAAa,IAAI,KAAK;GACtB,IAAI,WAAW,MAAM;EACtB;EACA,KAAK,MAAM;CACZ,CAAC;CACD,UAAU,OAAO,UAAU;CAC3B,WAAW,OAAO;AACnB;AAEA,YAAY"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renovate.d.mts","names":[],"sources":["../src/extensions/renovate.ts"],"mappings":";;wBACgB,YAAY"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renovate.mjs","names":[],"sources":["../src/extensions/renovate.ts"],"sourcesContent":["import type { AjvExtension } from \"../core/extensions.ts\"\nexport function renovate(): AjvExtension {\n\treturn { id: \"renovate\", annotations: [\"x-renovate-version\"] }\n}\n"],"mappings":";AACA,SAAgB,WAAyB;CACxC,OAAO;EAAE,IAAI;EAAY,aAAa,CAAC,oBAAoB;CAAE;AAC9D"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { C as pointer, S as parseDocument, d as readText, m as CorrectlyError } from "./config-Cb6o4YnJ.mjs";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
//#region src/core/schema-walk.ts
|
|
7
|
+
/** Only schema positions: defaults, enums, examples and other data stay opaque. */
|
|
8
|
+
function* schemaChildren(schema) {
|
|
9
|
+
for (const key of [
|
|
10
|
+
"additionalItems",
|
|
11
|
+
"additionalProperties",
|
|
12
|
+
"contains",
|
|
13
|
+
"propertyNames",
|
|
14
|
+
"not",
|
|
15
|
+
"if",
|
|
16
|
+
"then",
|
|
17
|
+
"else",
|
|
18
|
+
"unevaluatedProperties",
|
|
19
|
+
"unevaluatedItems",
|
|
20
|
+
"contentSchema"
|
|
21
|
+
]) if (schema[key] !== void 0) yield [pointer([key]), schema[key]];
|
|
22
|
+
for (const key of [
|
|
23
|
+
"properties",
|
|
24
|
+
"patternProperties",
|
|
25
|
+
"definitions",
|
|
26
|
+
"$defs",
|
|
27
|
+
"dependentSchemas",
|
|
28
|
+
"dependencies"
|
|
29
|
+
]) {
|
|
30
|
+
const map = schema[key];
|
|
31
|
+
if (typeof map === "object" && map !== null && !Array.isArray(map)) for (const [name, child] of Object.entries(map)) yield [pointer([key, name]), child];
|
|
32
|
+
}
|
|
33
|
+
for (const key of [
|
|
34
|
+
"allOf",
|
|
35
|
+
"anyOf",
|
|
36
|
+
"oneOf",
|
|
37
|
+
"prefixItems",
|
|
38
|
+
"items"
|
|
39
|
+
]) {
|
|
40
|
+
const children = schema[key];
|
|
41
|
+
if (Array.isArray(children)) for (const [index, child] of children.entries()) yield [pointer([key, index]), child];
|
|
42
|
+
else if (key === "items" && children !== void 0) yield [pointer([key]), children];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/core/schemas.ts
|
|
47
|
+
const DIALECTS = {
|
|
48
|
+
draft7: "http://json-schema.org/draft-07/schema#",
|
|
49
|
+
"2020-12": "https://json-schema.org/draft/2020-12/schema"
|
|
50
|
+
};
|
|
51
|
+
const VOCABULARIES = new Set([
|
|
52
|
+
"core",
|
|
53
|
+
"applicator",
|
|
54
|
+
"unevaluated",
|
|
55
|
+
"validation",
|
|
56
|
+
"meta-data",
|
|
57
|
+
"format-annotation",
|
|
58
|
+
"content"
|
|
59
|
+
].map((name) => `https://json-schema.org/draft/2020-12/vocab/${name}`));
|
|
60
|
+
function isObject(value) {
|
|
61
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
62
|
+
}
|
|
63
|
+
function dialect(value, inherited) {
|
|
64
|
+
if (value === void 0) return inherited;
|
|
65
|
+
if (typeof value !== "string") throw new CorrectlyError("schema", "Schema $schema must be a string");
|
|
66
|
+
const id = value.replace(/#$/, "");
|
|
67
|
+
if (id === "http://json-schema.org/draft-07/schema" || id === "https://json-schema.org/draft-07/schema") return "draft7";
|
|
68
|
+
if (id === DIALECTS["2020-12"]) return "2020-12";
|
|
69
|
+
throw new CorrectlyError("unsupported-dialect", `Unsupported schema dialect: ${value}; supported: draft 7, draft 2020-12`);
|
|
70
|
+
}
|
|
71
|
+
var SchemaStore = class {
|
|
72
|
+
options;
|
|
73
|
+
resources = /* @__PURE__ */ new Map();
|
|
74
|
+
pending = /* @__PURE__ */ new Map();
|
|
75
|
+
cacheDir;
|
|
76
|
+
offline;
|
|
77
|
+
timeoutMs;
|
|
78
|
+
maxBytes;
|
|
79
|
+
maxRequests;
|
|
80
|
+
read;
|
|
81
|
+
request;
|
|
82
|
+
requests = 0;
|
|
83
|
+
constructor(project, options = {}) {
|
|
84
|
+
this.options = options;
|
|
85
|
+
const remote = project.config.remote;
|
|
86
|
+
this.cacheDir = path.resolve(project.root, remote?.cacheDir ?? ".correctly-cache");
|
|
87
|
+
this.offline = options.offline ?? remote?.offline ?? false;
|
|
88
|
+
this.timeoutMs = remote?.timeoutMs ?? 5e3;
|
|
89
|
+
this.maxBytes = remote?.maxBytes ?? 2097152;
|
|
90
|
+
this.maxRequests = remote?.maxRequests ?? 64;
|
|
91
|
+
this.read = options.read ?? readText;
|
|
92
|
+
this.request = options.fetch ?? fetch;
|
|
93
|
+
}
|
|
94
|
+
async load(value, inherited = "draft7") {
|
|
95
|
+
this.options.signal?.throwIfAborted();
|
|
96
|
+
const url = new URL(value);
|
|
97
|
+
url.hash = "";
|
|
98
|
+
const uri = url.href;
|
|
99
|
+
const known = this.resources.get(uri);
|
|
100
|
+
if (known) return known;
|
|
101
|
+
let promise = this.pending.get(uri);
|
|
102
|
+
if (!promise) {
|
|
103
|
+
promise = this.loadResource(uri, inherited);
|
|
104
|
+
this.pending.set(uri, promise);
|
|
105
|
+
}
|
|
106
|
+
return promise;
|
|
107
|
+
}
|
|
108
|
+
async loadResource(uri, inherited) {
|
|
109
|
+
if (++this.requests > this.maxRequests) throw new CorrectlyError("schema-limit", `Schema resource limit (${this.maxRequests}) exceeded`);
|
|
110
|
+
let text;
|
|
111
|
+
const url = new URL(uri);
|
|
112
|
+
try {
|
|
113
|
+
if (url.protocol === "file:") text = await this.read(uri);
|
|
114
|
+
else if (url.protocol === "https:" || url.protocol === "http:") text = await this.remote(uri);
|
|
115
|
+
else throw new CorrectlyError("schema", `Unsupported schema protocol: ${url.protocol}`);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof CorrectlyError) throw error;
|
|
118
|
+
throw new CorrectlyError("schema-load", `Cannot load schema ${uri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
|
+
}
|
|
120
|
+
this.options.signal?.throwIfAborted();
|
|
121
|
+
if (Buffer.byteLength(text) > this.maxBytes) throw new CorrectlyError("schema-limit", `Schema ${uri} exceeds ${this.maxBytes} bytes`);
|
|
122
|
+
const parsed = parseDocument(text, "json");
|
|
123
|
+
if (parsed.diagnostics.length) {
|
|
124
|
+
const d = parsed.diagnostics[0];
|
|
125
|
+
throw new CorrectlyError("schema", `Invalid schema JSON at ${uri}:${d.range.start.line + 1}:${d.range.start.character + 1}: ${d.message}`);
|
|
126
|
+
}
|
|
127
|
+
if (typeof parsed.value !== "boolean" && !isObject(parsed.value)) throw new CorrectlyError("schema", `Schema must be an object or boolean: ${uri}`);
|
|
128
|
+
const rootDialect = dialect(isObject(parsed.value) ? parsed.value.$schema : void 0, inherited);
|
|
129
|
+
const schema = parsed.value;
|
|
130
|
+
const resource = {
|
|
131
|
+
uri,
|
|
132
|
+
schema,
|
|
133
|
+
dialect: rootDialect
|
|
134
|
+
};
|
|
135
|
+
this.normalize(schema, uri, rootDialect, resource);
|
|
136
|
+
if (isObject(schema) && schema.$id === void 0) schema.$id = uri;
|
|
137
|
+
this.register(uri, resource);
|
|
138
|
+
return resource;
|
|
139
|
+
}
|
|
140
|
+
register(uri, resource) {
|
|
141
|
+
const previous = this.resources.get(uri);
|
|
142
|
+
if (previous && previous.schema !== resource.schema) throw new CorrectlyError("schema", `Duplicate schema identifier: ${uri}`);
|
|
143
|
+
this.resources.set(uri, resource);
|
|
144
|
+
}
|
|
145
|
+
normalize(schema, base, expected, root, location = "") {
|
|
146
|
+
if (!isObject(schema)) return;
|
|
147
|
+
const ownDialect = dialect(schema.$schema, expected);
|
|
148
|
+
if (ownDialect !== expected) throw new CorrectlyError("unsupported-dialect", `Mixed schema dialects at ${base} are not supported`);
|
|
149
|
+
if (schema.$schema !== void 0) schema.$schema = DIALECTS[ownDialect];
|
|
150
|
+
if (schema.$async !== void 0) throw new CorrectlyError("schema", `Asynchronous data validation is not supported: ${base}`);
|
|
151
|
+
if (schema.$vocabulary !== void 0) {
|
|
152
|
+
if (!isObject(schema.$vocabulary)) throw new CorrectlyError("schema", `$vocabulary must be an object: ${base}`);
|
|
153
|
+
for (const [vocabulary, required] of Object.entries(schema.$vocabulary)) {
|
|
154
|
+
if (typeof required !== "boolean") throw new CorrectlyError("schema", `$vocabulary values must be boolean: ${base}`);
|
|
155
|
+
if (required && !VOCABULARIES.has(vocabulary)) throw new CorrectlyError("unsupported-vocabulary", `Unsupported required vocabulary: ${vocabulary}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
let scope = base;
|
|
159
|
+
if (schema.$id !== void 0) {
|
|
160
|
+
if (typeof schema.$id !== "string") throw new CorrectlyError("schema", `$id must be a string: ${base}`);
|
|
161
|
+
scope = new URL(schema.$id, base).href;
|
|
162
|
+
schema.$id = scope;
|
|
163
|
+
this.register(scope.replace(/#$/, ""), {
|
|
164
|
+
...root,
|
|
165
|
+
schema,
|
|
166
|
+
pointer: location
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
for (const anchor of [schema.$anchor, schema.$dynamicAnchor]) if (typeof anchor === "string") this.register(new URL(`#${anchor}`, scope).href, {
|
|
170
|
+
...root,
|
|
171
|
+
schema,
|
|
172
|
+
pointer: location
|
|
173
|
+
});
|
|
174
|
+
for (const [path, child] of schemaChildren(schema)) this.normalize(child, scope, expected, root, location + path);
|
|
175
|
+
}
|
|
176
|
+
async remote(uri) {
|
|
177
|
+
const cacheFile = path.join(this.cacheDir, `${createHash("sha256").update(uri).digest("hex")}.json`);
|
|
178
|
+
let cached;
|
|
179
|
+
try {
|
|
180
|
+
const raw = await readFile(cacheFile, "utf8");
|
|
181
|
+
const entry = JSON.parse(raw);
|
|
182
|
+
if (!isObject(entry) || entry.uri !== uri || typeof entry.text !== "string") throw new Error("invalid cache entry");
|
|
183
|
+
cached = entry;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error.code !== "ENOENT") throw new CorrectlyError("schema-cache", `Cannot read schema cache ${cacheFile}: ${String(error)}`);
|
|
186
|
+
}
|
|
187
|
+
if (this.offline) {
|
|
188
|
+
if (!cached) throw new CorrectlyError("offline-miss", `Schema is not cached in offline mode: ${uri}`);
|
|
189
|
+
return cached.text;
|
|
190
|
+
}
|
|
191
|
+
const timeout = AbortSignal.timeout(this.timeoutMs);
|
|
192
|
+
const signal = this.options.signal ? AbortSignal.any([this.options.signal, timeout]) : timeout;
|
|
193
|
+
const headers = {};
|
|
194
|
+
if (cached?.etag) headers["If-None-Match"] = cached.etag;
|
|
195
|
+
if (cached?.lastModified) headers["If-Modified-Since"] = cached.lastModified;
|
|
196
|
+
let current = uri;
|
|
197
|
+
for (let redirects = 0; redirects <= 5; redirects++) {
|
|
198
|
+
const response = await this.request(current, {
|
|
199
|
+
signal,
|
|
200
|
+
headers,
|
|
201
|
+
redirect: "manual"
|
|
202
|
+
});
|
|
203
|
+
if ([
|
|
204
|
+
301,
|
|
205
|
+
302,
|
|
206
|
+
303,
|
|
207
|
+
307,
|
|
208
|
+
308
|
|
209
|
+
].includes(response.status)) {
|
|
210
|
+
await response.body?.cancel();
|
|
211
|
+
const location = response.headers.get("location");
|
|
212
|
+
if (!location) throw new CorrectlyError("schema-load", `Redirect without location: ${current}`);
|
|
213
|
+
current = new URL(location, current).href;
|
|
214
|
+
if (!["http:", "https:"].includes(new URL(current).protocol)) throw new CorrectlyError("schema-load", `Unsupported redirect: ${current}`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (response.status === 304 && cached) {
|
|
218
|
+
await response.body?.cancel();
|
|
219
|
+
return cached.text;
|
|
220
|
+
}
|
|
221
|
+
if (!response.ok || !response.body) {
|
|
222
|
+
await response.body?.cancel();
|
|
223
|
+
throw new CorrectlyError("schema-load", `HTTP ${response.status} loading schema ${uri}`);
|
|
224
|
+
}
|
|
225
|
+
const reader = response.body.getReader();
|
|
226
|
+
const chunks = [];
|
|
227
|
+
let size = 0;
|
|
228
|
+
try {
|
|
229
|
+
while (true) {
|
|
230
|
+
const { value, done } = await reader.read();
|
|
231
|
+
if (done) break;
|
|
232
|
+
size += value.byteLength;
|
|
233
|
+
if (size > this.maxBytes) throw new CorrectlyError("schema-limit", `Schema ${uri} exceeds ${this.maxBytes} bytes`);
|
|
234
|
+
chunks.push(value);
|
|
235
|
+
}
|
|
236
|
+
} finally {
|
|
237
|
+
await reader.cancel();
|
|
238
|
+
}
|
|
239
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
240
|
+
const entry = {
|
|
241
|
+
uri,
|
|
242
|
+
text
|
|
243
|
+
};
|
|
244
|
+
const etag = response.headers.get("etag");
|
|
245
|
+
const lastModified = response.headers.get("last-modified");
|
|
246
|
+
if (etag) entry.etag = etag;
|
|
247
|
+
if (lastModified) entry.lastModified = lastModified;
|
|
248
|
+
signal.throwIfAborted();
|
|
249
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
250
|
+
const temporary = `${cacheFile}.${randomUUID()}.tmp`;
|
|
251
|
+
await writeFile(temporary, JSON.stringify(entry));
|
|
252
|
+
await rename(temporary, cacheFile);
|
|
253
|
+
return text;
|
|
254
|
+
}
|
|
255
|
+
throw new CorrectlyError("schema-limit", `Too many redirects loading schema ${uri}`);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
function localSchemaPath(uri) {
|
|
259
|
+
const url = new URL(uri);
|
|
260
|
+
return url.protocol === "file:" ? fileURLToPath(url) : void 0;
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
export { schemaChildren as i, SchemaStore as n, localSchemaPath as r, DIALECTS as t };
|
|
264
|
+
|
|
265
|
+
//# sourceMappingURL=schemas-UxCtIdo2.mjs.map
|