correctly 0.0.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 +8 -0
- package/LICENSE +373 -0
- package/README.md +24 -0
- package/dist/cli.mjs +294 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/core.d.mts +183 -0
- package/dist/core.d.mts.map +1 -0
- package/dist/core.mjs +2 -0
- package/dist/engine-BgWBnb1A.mjs +746 -0
- package/dist/engine-BgWBnb1A.mjs.map +1 -0
- package/dist/lsp.mjs +315 -0
- package/dist/lsp.mjs.map +1 -0
- package/docs/guide.md +114 -0
- package/examples/basic/correctly.config.json +10 -0
- package/examples/basic/project.json +1 -0
- package/examples/basic/project.jsonc +5 -0
- package/examples/basic/project.schema.json +10 -0
- package/package.json +61 -0
- package/src/cli/check.ts +83 -0
- package/src/cli/main.ts +86 -0
- package/src/cli/readable.ts +295 -0
- package/src/cli/vsix.ts +86 -0
- package/src/core/config.schema.json +47 -0
- package/src/core/config.ts +200 -0
- package/src/core/diagnostics.ts +114 -0
- package/src/core/engine.ts +247 -0
- package/src/core/index.ts +10 -0
- package/src/core/parse.ts +168 -0
- package/src/core/schemas.ts +379 -0
- package/src/core/types.ts +56 -0
- package/src/lsp/hints.ts +82 -0
- package/src/lsp/server.ts +265 -0
- package/src/lsp/workspace.ts +132 -0
- package/src/vscode/README.md +7 -0
- package/src/vscode/extension.ts +33 -0
- package/src/vscode/package.json +43 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-BgWBnb1A.mjs","names":["configSchema"],"sources":["../src/core/config.schema.json","../src/core/parse.ts","../src/core/types.ts","../src/core/config.ts","../src/core/schemas.ts","../src/core/diagnostics.ts","../src/core/engine.ts"],"sourcesContent":["","import {\n\tfindNodeAtLocation,\n\tgetNodePath,\n\tgetNodeValue,\n\tparseTree,\n\tprintParseErrorCode,\n\ttype Node,\n\ttype ParseError,\n} from \"jsonc-parser\"\nimport type { Diagnostic, Mode, Position } from \"./types.ts\"\n\nexport type SourceSpan = { offset: number; length: number }\nexport type ParsedDocument = {\n\tvalue: unknown\n\tdiagnostics: Diagnostic[]\n\tlocate: (pointer: string, key?: boolean) => SourceSpan | undefined\n}\nexport type ParsedJsonDocument = ParsedDocument & { root: Node | undefined }\n\nexport function pointer(parts: readonly (string | number)[]): string {\n\treturn parts\n\t\t.map((p) => `/${String(p).replace(/~/g, \"~0\").replace(/\\//g, \"~1\")}`)\n\t\t.join(\"\")\n}\n\nexport function positionAt(text: string, offset: number): Position {\n\tconst end = Math.min(Math.max(0, offset), text.length)\n\tlet line = 0\n\tlet start = 0\n\tfor (let i = 0; i < end; i++) {\n\t\tif (text[i] === \"\\r\") {\n\t\t\tif (text[i + 1] === \"\\n\" && i + 1 < end) i++\n\t\t\tline++\n\t\t\tstart = i + 1\n\t\t} else if (text[i] === \"\\n\") {\n\t\t\tline++\n\t\t\tstart = i + 1\n\t\t}\n\t}\n\treturn { line, character: end - start }\n}\n\nexport function diagnostic(\n\ttext: string,\n\tcode: string,\n\tmessage: string,\n\tjsonPointer = \"\",\n\toffset = 0,\n\tlength = 1,\n): Diagnostic {\n\tconst start = Math.min(offset, text.length)\n\tconst size = Math.min(Math.max(1, length), text.length - start)\n\treturn {\n\t\tcode,\n\t\tmessage,\n\t\tpointer: jsonPointer,\n\t\toffset: start,\n\t\tlength: size,\n\t\trange: {\n\t\t\tstart: positionAt(text, start),\n\t\t\tend: positionAt(text, start + size),\n\t\t},\n\t}\n}\n\nexport function parseDocument(text: string, mode: Mode): ParsedJsonDocument {\n\tconst errors: ParseError[] = []\n\tconst root = parseTree(text, errors, {\n\t\tdisallowComments: mode === \"json\",\n\t\tallowTrailingComma: mode === \"jsonc\",\n\t\tallowEmptyContent: false,\n\t})\n\tconst diagnostics = errors.map((e) =>\n\t\tdiagnostic(\n\t\t\ttext,\n\t\t\t\"syntax\",\n\t\t\tprintParseErrorCode(e.error),\n\t\t\t\"\",\n\t\t\te.offset,\n\t\t\te.length,\n\t\t),\n\t)\n\tfunction visit(node: Node) {\n\t\tif (node.type === \"object\") {\n\t\t\tconst keys = new Set<string>()\n\t\t\tfor (const property of node.children ?? []) {\n\t\t\t\tconst key = property.children?.[0]\n\t\t\t\tif (!key || typeof key.value !== \"string\") continue\n\t\t\t\tif (keys.has(key.value)) {\n\t\t\t\t\tdiagnostics.push(\n\t\t\t\t\t\tdiagnostic(\n\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\"duplicate-key\",\n\t\t\t\t\t\t\t`Duplicate key ${JSON.stringify(key.value)}`,\n\t\t\t\t\t\t\tpointer([...getNodePath(node), key.value]),\n\t\t\t\t\t\t\tkey.offset,\n\t\t\t\t\t\t\tkey.length,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tkeys.add(key.value)\n\t\t\t}\n\t\t}\n\t\tif (node.type === \"number\" && !Number.isFinite(node.value)) {\n\t\t\tdiagnostics.push(\n\t\t\t\tdiagnostic(\n\t\t\t\t\ttext,\n\t\t\t\t\t\"syntax\",\n\t\t\t\t\t\"Number exceeds the supported finite range\",\n\t\t\t\t\tpointer(getNodePath(node)),\n\t\t\t\t\tnode.offset,\n\t\t\t\t\tnode.length,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tfor (const child of node.children ?? []) visit(child)\n\t}\n\tif (root) visit(root)\n\t// jsonc-parser's object values have null prototypes. Ajv's deep equality\n\t// expects ordinary JSON objects; Object.fromEntries safely preserves __proto__.\n\tfunction value(node: Node): unknown {\n\t\tif (node.type === \"object\")\n\t\t\treturn Object.fromEntries(\n\t\t\t\t(node.children ?? []).flatMap((property) => {\n\t\t\t\t\tconst key = property.children?.[0]\n\t\t\t\t\tconst child = property.children?.[1]\n\t\t\t\t\treturn key && child ? [[String(key.value), value(child)]] : []\n\t\t\t\t}),\n\t\t\t)\n\t\tif (node.type === \"array\") return (node.children ?? []).map(value)\n\t\treturn getNodeValue(node) as unknown\n\t}\n\treturn {\n\t\troot,\n\t\tvalue: root ? value(root) : undefined,\n\t\tdiagnostics,\n\t\tlocate: (jsonPointer, key = false) => {\n\t\t\tconst node = locate(root, jsonPointer, key)\n\t\t\treturn node ? { offset: node.offset, length: node.length } : undefined\n\t\t},\n\t}\n}\n\nexport function locate(\n\troot: Node | undefined,\n\tjsonPointer: string,\n\tkey = false,\n): Node | undefined {\n\tif (!root) return undefined\n\tconst parts =\n\t\tjsonPointer === \"\"\n\t\t\t? []\n\t\t\t: jsonPointer\n\t\t\t\t\t.slice(1)\n\t\t\t\t\t.split(\"/\")\n\t\t\t\t\t.map((p) => p.replace(/~1/g, \"/\").replace(/~0/g, \"~\"))\n\tlet current = root\n\tfor (const part of parts) {\n\t\tconst next = findNodeAtLocation(current, [\n\t\t\tcurrent.type === \"array\" ? Number(part) : part,\n\t\t])\n\t\tif (!next) break\n\t\tcurrent = next\n\t}\n\treturn key && current.parent?.type === \"property\"\n\t\t? (current.parent.children?.[0] ?? current)\n\t\t: current\n}\n","export type Mode = \"json\" | \"jsonc\"\nexport type Position = { line: number; character: number }\nexport type Range = { start: Position; end: Position }\nexport type Diagnostic = {\n\tcode: string\n\tmessage: string\n\tpointer: string\n\toffset: number\n\tlength: number\n\trange: Range\n\tcontext?: { parent: number; label: string }\n}\nexport type Failure = { code: string; message: string; file?: string }\nexport class CorrectlyError extends Error {\n\treadonly code: string\n\tconstructor(code: string, message: string) {\n\t\tsuper(message)\n\t\tthis.name = \"CorrectlyError\"\n\t\tthis.code = code\n\t}\n}\nexport function failure(error: unknown, file?: string): Failure {\n\treturn {\n\t\tcode: error instanceof CorrectlyError ? error.code : \"execution\",\n\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t...(file === undefined ? {} : { file }),\n\t}\n}\nexport type Association = {\n\tindex: number\n\tname: string\n\tschema: string | null\n\tmode: Mode\n}\nexport type FileResult = {\n\tfile: string\n\tmode: Mode\n\tcoverage: \"schema\" | \"syntax-only\" | \"excluded\"\n\tassociation: Association | null\n\tdiagnostics: Diagnostic[]\n\tfailures: Failure[]\n}\nexport type Report = {\n\treportVersion: 1\n\tconfig: string | null\n\tfiles: FileResult[]\n\tfailures: Failure[]\n\tsummary: {\n\t\tchecked: number\n\t\tschemaCovered: number\n\t\tsyntaxOnly: number\n\t\tinvalid: number\n\t\tfailures: number\n\t}\n\texitCode: 0 | 1 | 2\n}\n","import { readFile, stat } from \"node:fs/promises\"\nimport path from \"node:path\"\nimport { fileURLToPath, pathToFileURL } from \"node:url\"\nimport { Ajv } from \"ajv\"\nimport picomatch from \"picomatch\"\nimport configSchema from \"./config.schema.json\" with { type: \"json\" }\nimport { parseDocument } from \"./parse.ts\"\nimport { CorrectlyError, type Association, type Mode } from \"./types.ts\"\n\nexport const CONFIG_NAME = \"correctly.config.json\"\nexport const DEFAULT_EXCLUDES = [\n\t\"**/node_modules/**\",\n\t\"**/.git/**\",\n\t\"**/dist/**\",\n\t\"**/artifacts/**\",\n\t\"**/.correctly-cache/**\",\n]\nexport type ProjectConfig = {\n\tfiles?: string[]\n\texclude?: string[]\n\tassociations: {\n\t\tname?: string\n\t\tfiles: string[]\n\t\tschema: string | null\n\t\tmode?: Mode\n\t}[]\n\tremote?: {\n\t\toffline?: boolean\n\t\tcacheDir?: string\n\t\ttimeoutMs?: number\n\t\tmaxBytes?: number\n\t\tmaxRequests?: number\n\t}\n}\nexport type Project = {\n\tconfigPath: string\n\troot: string\n\tconfig: ProjectConfig\n}\nexport type ReadText = (uri: string) => Promise<string>\nexport const readText: ReadText = (uri) => readFile(fileURLToPath(uri), \"utf8\")\nconst validateConfig = new Ajv({ allErrors: true, strict: true }).compile(\n\tconfigSchema,\n)\n\nexport function contains(root: string, file: string): boolean {\n\tconst relative = path.relative(root, file)\n\treturn (\n\t\trelative !== \"..\" &&\n\t\t!relative.startsWith(`..${path.sep}`) &&\n\t\t!path.isAbsolute(relative)\n\t)\n}\n\nexport async function discoverConfig(\n\tstart: string,\n\tboundary?: string,\n\tvirtualFiles: ReadonlySet<string> = new Set(),\n): Promise<string> {\n\tlet directory = path.resolve(start)\n\tif (boundary && !contains(boundary, directory))\n\t\tthrow new CorrectlyError(\"config\", `Path is outside workspace ${boundary}`)\n\twhile (true) {\n\t\tconst candidate = path.join(directory, CONFIG_NAME)\n\t\tif (virtualFiles.has(candidate)) return candidate\n\t\ttry {\n\t\t\tconst info = await stat(candidate)\n\t\t\tif (info.isFile()) return candidate\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error\n\t\t}\n\t\tconst parent = path.dirname(directory)\n\t\tif (directory === boundary || parent === directory) break\n\t\tdirectory = parent\n\t}\n\tthrow new CorrectlyError(\n\t\t\"config\",\n\t\t`No ${CONFIG_NAME} found from ${start}${boundary ? ` within ${boundary}` : \"\"}`,\n\t)\n}\n\nexport async function loadProject(\n\tconfigPath: string,\n\tread: ReadText = readText,\n): Promise<Project> {\n\tconst resolved = path.resolve(configPath)\n\tlet text: string\n\ttry {\n\t\ttext = await read(pathToFileURL(resolved).href)\n\t} catch (error) {\n\t\tthrow new CorrectlyError(\n\t\t\t\"config\",\n\t\t\t`Cannot read configuration ${resolved}: ${String(error)}`,\n\t\t)\n\t}\n\tconst parsed = parseDocument(text, \"json\")\n\tif (parsed.diagnostics.length) {\n\t\tconst d = parsed.diagnostics[0]!\n\t\tthrow new CorrectlyError(\n\t\t\t\"config\",\n\t\t\t`${resolved}:${d.range.start.line + 1}:${d.range.start.character + 1}: ${d.message}`,\n\t\t)\n\t}\n\tif (!validateConfig(parsed.value)) {\n\t\tthrow new CorrectlyError(\n\t\t\t\"config\",\n\t\t\t`${resolved}: ${validateConfig.errors?.map((e) => `${e.instancePath || \"/\"} ${e.message}`).join(\"; \")}`,\n\t\t)\n\t}\n\tconst config = parsed.value as ProjectConfig\n\tfor (const pattern of [\n\t\t...(config.files ?? []),\n\t\t...(config.exclude ?? []),\n\t\t...config.associations.flatMap((a) => a.files),\n\t]) {\n\t\tif (pattern.includes(\"\\\\\"))\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"config\",\n\t\t\t\t`Use forward slashes in patterns: ${pattern}`,\n\t\t\t)\n\t}\n\treturn { configPath: resolved, root: path.dirname(resolved), config }\n}\n\nexport function schemaUri(value: string, root: string): string {\n\tif (\n\t\t/^[A-Za-z][A-Za-z\\d+.-]*:/.test(value) &&\n\t\t!/^[A-Za-z]:[\\\\/]/.test(value)\n\t) {\n\t\tconst uri = new URL(value)\n\t\tif (![\"file:\", \"http:\", \"https:\"].includes(uri.protocol))\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema\",\n\t\t\t\t`Unsupported schema protocol: ${uri.protocol}`,\n\t\t\t)\n\t\treturn uri.href\n\t}\n\tconst hash = value.indexOf(\"#\")\n\tconst filename = hash === -1 ? value : value.slice(0, hash)\n\tconst uri = pathToFileURL(path.resolve(root, filename))\n\tif (hash !== -1) uri.hash = value.slice(hash + 1)\n\treturn uri.href\n}\n\nexport function exclusions(project: Project): string[] {\n\tconst cachePath = path.resolve(\n\t\tproject.root,\n\t\tproject.config.remote?.cacheDir ?? \".correctly-cache\",\n\t)\n\tconst relative = path\n\t\t.relative(project.root, cachePath)\n\t\t.split(path.sep)\n\t\t.join(\"/\")\n\treturn [\n\t\t...DEFAULT_EXCLUDES,\n\t\t...(project.config.exclude ?? []),\n\t\t...(relative && contains(project.root, cachePath)\n\t\t\t? [`${relative}/**`]\n\t\t\t: []),\n\t]\n}\n\nexport function isIncluded(project: Project, file: string): boolean {\n\tif (!contains(project.root, file)) return false\n\tconst relative = path.relative(project.root, file).split(path.sep).join(\"/\")\n\tconst options = { dot: true }\n\treturn (\n\t\tpicomatch(\n\t\t\tproject.config.files ?? [\"**/*.json\", \"**/*.jsonc\"],\n\t\t\toptions,\n\t\t)(relative) && !picomatch(exclusions(project), options)(relative)\n\t)\n}\n\nexport function associationFor(\n\tproject: Project,\n\tfile: string,\n): Association | null {\n\tconst relative = path.relative(project.root, file).split(path.sep).join(\"/\")\n\tfor (\n\t\tlet index = project.config.associations.length - 1;\n\t\tindex >= 0;\n\t\tindex--\n\t) {\n\t\tconst rule = project.config.associations[index]!\n\t\tif (picomatch(rule.files, { dot: true })(relative)) {\n\t\t\treturn {\n\t\t\t\tindex,\n\t\t\t\tname: rule.name ?? `association ${index + 1}`,\n\t\t\t\tschema:\n\t\t\t\t\trule.schema === null ? null : schemaUri(rule.schema, project.root),\n\t\t\t\tmode: rule.mode ?? modeFor(file),\n\t\t\t}\n\t\t}\n\t}\n\treturn null\n}\nexport function modeFor(file: string): Mode {\n\treturn path.extname(file).toLowerCase() === \".jsonc\" ? \"jsonc\" : \"json\"\n}\n","import { createHash, randomUUID } from \"node:crypto\"\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\"\nimport path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport type { AnySchema, SchemaObject } from \"ajv\"\nimport { parseDocument } from \"./parse.ts\"\nimport { readText, type Project, type ReadText } from \"./config.ts\"\nimport { CorrectlyError } from \"./types.ts\"\n\nexport type Dialect = \"draft7\" | \"2020-12\"\nexport const DIALECTS = {\n\tdraft7: \"http://json-schema.org/draft-07/schema#\",\n\t\"2020-12\": \"https://json-schema.org/draft/2020-12/schema\",\n} as const\nconst VOCABULARIES = new Set(\n\t[\n\t\t\"core\",\n\t\t\"applicator\",\n\t\t\"unevaluated\",\n\t\t\"validation\",\n\t\t\"meta-data\",\n\t\t\"format-annotation\",\n\t\t\"content\",\n\t].map((name) => `https://json-schema.org/draft/2020-12/vocab/${name}`),\n)\nconst SINGLE_SCHEMAS = [\n\t\"additionalItems\",\n\t\"additionalProperties\",\n\t\"contains\",\n\t\"propertyNames\",\n\t\"not\",\n\t\"if\",\n\t\"then\",\n\t\"else\",\n\t\"unevaluatedProperties\",\n\t\"unevaluatedItems\",\n\t\"contentSchema\",\n]\nconst MAP_SCHEMAS = [\n\t\"properties\",\n\t\"patternProperties\",\n\t\"definitions\",\n\t\"$defs\",\n\t\"dependentSchemas\",\n\t\"dependencies\",\n]\nconst ARRAY_SCHEMAS = [\"allOf\", \"anyOf\", \"oneOf\", \"prefixItems\"]\n\nfunction isObject(value: unknown): value is SchemaObject {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\nfunction dialect(value: unknown, inherited: Dialect): Dialect {\n\tif (value === undefined) return inherited\n\tif (typeof value !== \"string\")\n\t\tthrow new CorrectlyError(\"schema\", \"Schema $schema must be a string\")\n\tconst id = value.replace(/#$/, \"\")\n\tif (\n\t\tid === \"http://json-schema.org/draft-07/schema\" ||\n\t\tid === \"https://json-schema.org/draft-07/schema\"\n\t)\n\t\treturn \"draft7\"\n\tif (id === DIALECTS[\"2020-12\"]) return \"2020-12\"\n\tthrow new CorrectlyError(\n\t\t\"unsupported-dialect\",\n\t\t`Unsupported schema dialect: ${value}; supported: draft 7, draft 2020-12`,\n\t)\n}\n\nexport type SchemaResource = {\n\turi: string\n\tschema: AnySchema\n\tdialect: Dialect\n}\ntype CacheEntry = {\n\turi: string\n\ttext: string\n\tetag?: string\n\tlastModified?: string\n}\nexport type StoreOptions = {\n\toffline?: boolean\n\tread?: ReadText\n\tfetch?: typeof fetch\n\tsignal?: AbortSignal\n}\n\nexport class SchemaStore {\n\tprivate readonly options: StoreOptions\n\treadonly resources = new Map<string, SchemaResource>()\n\tprivate readonly pending = new Map<string, Promise<SchemaResource>>()\n\tprivate readonly cacheDir: string\n\tprivate readonly offline: boolean\n\tprivate readonly timeoutMs: number\n\tprivate readonly maxBytes: number\n\tprivate readonly maxRequests: number\n\tprivate readonly read: ReadText\n\tprivate readonly request: typeof fetch\n\tprivate requests = 0\n\tconstructor(project: Project, options: StoreOptions = {}) {\n\t\tthis.options = options\n\t\tconst remote = project.config.remote\n\t\tthis.cacheDir = path.resolve(\n\t\t\tproject.root,\n\t\t\tremote?.cacheDir ?? \".correctly-cache\",\n\t\t)\n\t\tthis.offline = options.offline ?? remote?.offline ?? false\n\t\tthis.timeoutMs = remote?.timeoutMs ?? 5000\n\t\tthis.maxBytes = remote?.maxBytes ?? 2 * 1024 * 1024\n\t\tthis.maxRequests = remote?.maxRequests ?? 64\n\t\tthis.read = options.read ?? readText\n\t\tthis.request = options.fetch ?? fetch\n\t}\n\n\tasync load(\n\t\tvalue: string,\n\t\tinherited: Dialect = \"draft7\",\n\t): Promise<SchemaResource> {\n\t\tthis.options.signal?.throwIfAborted()\n\t\tconst url = new URL(value)\n\t\turl.hash = \"\"\n\t\tconst uri = url.href\n\t\tconst known = this.resources.get(uri)\n\t\tif (known) return known\n\t\tlet promise = this.pending.get(uri)\n\t\tif (!promise) {\n\t\t\tpromise = this.loadResource(uri, inherited)\n\t\t\tthis.pending.set(uri, promise)\n\t\t}\n\t\treturn promise\n\t}\n\n\tprivate async loadResource(\n\t\turi: string,\n\t\tinherited: Dialect,\n\t): Promise<SchemaResource> {\n\t\tif (++this.requests > this.maxRequests)\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema-limit\",\n\t\t\t\t`Schema resource limit (${this.maxRequests}) exceeded`,\n\t\t\t)\n\t\tlet text: string\n\t\tconst url = new URL(uri)\n\t\ttry {\n\t\t\tif (url.protocol === \"file:\") text = await this.read(uri)\n\t\t\telse if (url.protocol === \"https:\" || url.protocol === \"http:\")\n\t\t\t\ttext = await this.remote(uri)\n\t\t\telse\n\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\"schema\",\n\t\t\t\t\t`Unsupported schema protocol: ${url.protocol}`,\n\t\t\t\t)\n\t\t} catch (error) {\n\t\t\tif (error instanceof CorrectlyError) throw error\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema-load\",\n\t\t\t\t`Cannot load schema ${uri}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t)\n\t\t}\n\t\tthis.options.signal?.throwIfAborted()\n\t\tif (Buffer.byteLength(text) > this.maxBytes)\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema-limit\",\n\t\t\t\t`Schema ${uri} exceeds ${this.maxBytes} bytes`,\n\t\t\t)\n\t\tconst parsed = parseDocument(text, \"json\")\n\t\tif (parsed.diagnostics.length) {\n\t\t\tconst d = parsed.diagnostics[0]!\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema\",\n\t\t\t\t`Invalid schema JSON at ${uri}:${d.range.start.line + 1}:${d.range.start.character + 1}: ${d.message}`,\n\t\t\t)\n\t\t}\n\t\tif (typeof parsed.value !== \"boolean\" && !isObject(parsed.value))\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema\",\n\t\t\t\t`Schema must be an object or boolean: ${uri}`,\n\t\t\t)\n\t\tconst rootDialect = dialect(\n\t\t\tisObject(parsed.value) ? parsed.value.$schema : undefined,\n\t\t\tinherited,\n\t\t)\n\t\tconst schema = parsed.value as AnySchema\n\t\tconst resource: SchemaResource = { uri, schema, dialect: rootDialect }\n\t\t// Normalize schema identifiers only, never document data or annotation values.\n\t\tthis.normalize(schema, uri, rootDialect, resource)\n\t\tif (isObject(schema) && schema.$id === undefined) schema.$id = uri\n\t\tthis.register(uri, resource)\n\t\treturn resource\n\t}\n\n\tprivate register(uri: string, resource: SchemaResource) {\n\t\tconst previous = this.resources.get(uri)\n\t\tif (previous && previous.schema !== resource.schema)\n\t\t\tthrow new CorrectlyError(\"schema\", `Duplicate schema identifier: ${uri}`)\n\t\tthis.resources.set(uri, resource)\n\t}\n\n\tprivate normalize(\n\t\tschema: unknown,\n\t\tbase: string,\n\t\texpected: Dialect,\n\t\troot: SchemaResource,\n\t) {\n\t\tif (!isObject(schema)) return\n\t\tconst ownDialect = dialect(schema.$schema, expected)\n\t\tif (ownDialect !== expected)\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"unsupported-dialect\",\n\t\t\t\t`Mixed schema dialects at ${base} are not supported`,\n\t\t\t)\n\t\tif (schema.$schema !== undefined) schema.$schema = DIALECTS[ownDialect]\n\t\tif (schema.$async !== undefined)\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema\",\n\t\t\t\t`Asynchronous data validation is not supported: ${base}`,\n\t\t\t)\n\t\tif (schema.$vocabulary !== undefined) {\n\t\t\tif (!isObject(schema.$vocabulary))\n\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\"schema\",\n\t\t\t\t\t`$vocabulary must be an object: ${base}`,\n\t\t\t\t)\n\t\t\tfor (const [vocabulary, required] of Object.entries(schema.$vocabulary)) {\n\t\t\t\tif (typeof required !== \"boolean\")\n\t\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\t\"schema\",\n\t\t\t\t\t\t`$vocabulary values must be boolean: ${base}`,\n\t\t\t\t\t)\n\t\t\t\tif (required && !VOCABULARIES.has(vocabulary))\n\t\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\t\"unsupported-vocabulary\",\n\t\t\t\t\t\t`Unsupported required vocabulary: ${vocabulary}`,\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tlet scope = base\n\t\tif (schema.$id !== undefined) {\n\t\t\tif (typeof schema.$id !== \"string\")\n\t\t\t\tthrow new CorrectlyError(\"schema\", `$id must be a string: ${base}`)\n\t\t\tscope = new URL(schema.$id, base).href\n\t\t\tschema.$id = scope\n\t\t\tthis.register(scope.replace(/#$/, \"\"), { ...root, schema })\n\t\t}\n\t\tfor (const anchor of [schema.$anchor, schema.$dynamicAnchor]) {\n\t\t\tif (typeof anchor === \"string\")\n\t\t\t\tthis.register(new URL(`#${anchor}`, scope).href, { ...root, schema })\n\t\t}\n\t\tfor (const key of SINGLE_SCHEMAS)\n\t\t\tthis.normalize(schema[key], scope, expected, root)\n\t\tfor (const key of MAP_SCHEMAS) {\n\t\t\tif (isObject(schema[key]))\n\t\t\t\tfor (const child of Object.values(\n\t\t\t\t\tschema[key] as Record<string, unknown>,\n\t\t\t\t))\n\t\t\t\t\tthis.normalize(child, scope, expected, root)\n\t\t}\n\t\tfor (const key of [...ARRAY_SCHEMAS, \"items\"]) {\n\t\t\tconst children: unknown = schema[key]\n\t\t\tif (Array.isArray(children))\n\t\t\t\tfor (const child of children)\n\t\t\t\t\tthis.normalize(child, scope, expected, root)\n\t\t\telse if (key === \"items\") this.normalize(children, scope, expected, root)\n\t\t}\n\t}\n\n\tprivate async remote(uri: string): Promise<string> {\n\t\tconst cacheFile = path.join(\n\t\t\tthis.cacheDir,\n\t\t\t`${createHash(\"sha256\").update(uri).digest(\"hex\")}.json`,\n\t\t)\n\t\tlet cached: CacheEntry | undefined\n\t\ttry {\n\t\t\tconst raw = await readFile(cacheFile, \"utf8\")\n\t\t\tconst entry: unknown = JSON.parse(raw)\n\t\t\tif (\n\t\t\t\t!isObject(entry) ||\n\t\t\t\tentry.uri !== uri ||\n\t\t\t\ttypeof entry.text !== \"string\"\n\t\t\t)\n\t\t\t\tthrow new Error(\"invalid cache entry\")\n\t\t\tcached = entry as CacheEntry\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\")\n\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\"schema-cache\",\n\t\t\t\t\t`Cannot read schema cache ${cacheFile}: ${String(error)}`,\n\t\t\t\t)\n\t\t}\n\t\tif (this.offline) {\n\t\t\tif (!cached)\n\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\"offline-miss\",\n\t\t\t\t\t`Schema is not cached in offline mode: ${uri}`,\n\t\t\t\t)\n\t\t\treturn cached.text\n\t\t}\n\t\tconst timeout = AbortSignal.timeout(this.timeoutMs)\n\t\tconst signal = this.options.signal\n\t\t\t? AbortSignal.any([this.options.signal, timeout])\n\t\t\t: timeout\n\t\tconst headers: Record<string, string> = {}\n\t\tif (cached?.etag) headers[\"If-None-Match\"] = cached.etag\n\t\tif (cached?.lastModified) headers[\"If-Modified-Since\"] = cached.lastModified\n\t\tlet current = uri\n\t\tfor (let redirects = 0; redirects <= 5; redirects++) {\n\t\t\tconst response = await this.request(current, {\n\t\t\t\tsignal,\n\t\t\t\theaders,\n\t\t\t\tredirect: \"manual\",\n\t\t\t})\n\t\t\tif ([301, 302, 303, 307, 308].includes(response.status)) {\n\t\t\t\tawait response.body?.cancel()\n\t\t\t\tconst location = response.headers.get(\"location\")\n\t\t\t\tif (!location)\n\t\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\t\"schema-load\",\n\t\t\t\t\t\t`Redirect without location: ${current}`,\n\t\t\t\t\t)\n\t\t\t\tcurrent = new URL(location, current).href\n\t\t\t\tif (![\"http:\", \"https:\"].includes(new URL(current).protocol))\n\t\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\t\"schema-load\",\n\t\t\t\t\t\t`Unsupported redirect: ${current}`,\n\t\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (response.status === 304 && cached) {\n\t\t\t\tawait response.body?.cancel()\n\t\t\t\treturn cached.text\n\t\t\t}\n\t\t\tif (!response.ok || !response.body) {\n\t\t\t\tawait response.body?.cancel()\n\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\"schema-load\",\n\t\t\t\t\t`HTTP ${response.status} loading schema ${uri}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst reader = response.body.getReader()\n\t\t\tconst chunks: Uint8Array[] = []\n\t\t\tlet size = 0\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { value, done } = await reader.read()\n\t\t\t\t\tif (done) break\n\t\t\t\t\tsize += value.byteLength\n\t\t\t\t\tif (size > this.maxBytes)\n\t\t\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\t\t\"schema-limit\",\n\t\t\t\t\t\t\t`Schema ${uri} exceeds ${this.maxBytes} bytes`,\n\t\t\t\t\t\t)\n\t\t\t\t\tchunks.push(value)\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait reader.cancel()\n\t\t\t}\n\t\t\tconst text = Buffer.concat(chunks).toString(\"utf8\")\n\t\t\tconst entry: CacheEntry = { uri, text }\n\t\t\tconst etag = response.headers.get(\"etag\")\n\t\t\tconst lastModified = response.headers.get(\"last-modified\")\n\t\t\tif (etag) entry.etag = etag\n\t\t\tif (lastModified) entry.lastModified = lastModified\n\t\t\tsignal.throwIfAborted()\n\t\t\tawait mkdir(this.cacheDir, { recursive: true })\n\t\t\tconst temporary = `${cacheFile}.${randomUUID()}.tmp`\n\t\t\tawait writeFile(temporary, JSON.stringify(entry))\n\t\t\tawait rename(temporary, cacheFile)\n\t\t\treturn text\n\t\t}\n\t\tthrow new CorrectlyError(\n\t\t\t\"schema-limit\",\n\t\t\t`Too many redirects loading schema ${uri}`,\n\t\t)\n\t}\n}\n\nexport function localSchemaPath(uri: string): string | undefined {\n\tconst url = new URL(uri)\n\treturn url.protocol === \"file:\" ? fileURLToPath(url) : undefined\n}\n","import type { ErrorObject } from \"ajv\"\nimport type { Diagnostic } from \"./types.ts\"\n\nexport type DiagnosticView = Diagnostic & {\n\tbranches: { label: string; diagnostics: DiagnosticView[] }[]\n}\n\n// Ajv emits branch failures before the applicator's summary. Keep the complete\n// flat report and annotate only relationships established by schema paths.\n// Reference paths can lose their caller; leave those errors standalone.\nexport function validationContexts(\n\terrors: readonly ErrorObject[],\n\tdiagnostics: Diagnostic[],\n): Diagnostic[] {\n\tconst summaries = errors.flatMap((error, index) => {\n\t\tif (error.keyword === \"anyOf\" || error.keyword === \"oneOf\")\n\t\t\treturn [\n\t\t\t\t{ index, error, prefix: `${error.schemaPath}/`, alternative: true },\n\t\t\t]\n\t\tif (\n\t\t\terror.keyword === \"if\" &&\n\t\t\t[\"then\", \"else\"].includes(error.params.failingKeyword as string)\n\t\t)\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tindex,\n\t\t\t\t\terror,\n\t\t\t\t\tprefix: `${error.schemaPath.slice(0, -3)}/${error.params.failingKeyword}`,\n\t\t\t\t\talternative: false,\n\t\t\t\t},\n\t\t\t]\n\t\treturn []\n\t})\n\treturn diagnostics.map((diagnostic, index) => {\n\t\tconst error = errors[index]!\n\t\tconst candidates = summaries.filter(\n\t\t\t(parent) =>\n\t\t\t\tparent.index > index &&\n\t\t\t\t(error.instancePath === parent.error.instancePath ||\n\t\t\t\t\terror.instancePath.startsWith(`${parent.error.instancePath}/`)) &&\n\t\t\t\t(parent.error.propertyName === undefined ||\n\t\t\t\t\terror.propertyName === parent.error.propertyName) &&\n\t\t\t\t(parent.alternative\n\t\t\t\t\t? error.schemaPath.startsWith(parent.prefix)\n\t\t\t\t\t: error.schemaPath === parent.prefix ||\n\t\t\t\t\t\terror.schemaPath.startsWith(`${parent.prefix}/`)),\n\t\t)\n\t\tconst parent = candidates.toSorted(\n\t\t\t(a, b) => b.prefix.length - a.prefix.length || a.index - b.index,\n\t\t)[0]\n\t\tif (!parent) return diagnostic\n\t\tconst branch = parent.alternative\n\t\t\t? error.schemaPath.slice(parent.prefix.length).split(\"/\")[0]!\n\t\t\t: (parent.error.params.failingKeyword as string)\n\t\tif (parent.alternative && !/^\\d+$/.test(branch)) return diagnostic\n\t\treturn {\n\t\t\t...diagnostic,\n\t\t\tcontext: {\n\t\t\t\tparent: parent.index,\n\t\t\t\tlabel: parent.alternative\n\t\t\t\t\t? `Alternative ${Number(branch) + 1}`\n\t\t\t\t\t: branch === \"then\"\n\t\t\t\t\t\t? \"Then branch\"\n\t\t\t\t\t\t: \"Else branch\",\n\t\t\t},\n\t\t}\n\t})\n}\n\nexport function diagnosticViews(\n\tdiagnostics: readonly Diagnostic[],\n): DiagnosticView[] {\n\tconst views = diagnostics.map((diagnostic): DiagnosticView => ({\n\t\t...diagnostic,\n\t\tbranches: [],\n\t}))\n\tconst roots: DiagnosticView[] = []\n\tfor (const [index, view] of views.entries()) {\n\t\tconst context = view.context\n\t\t// Parents must follow their children, so malformed metadata cannot create\n\t\t// cycles or make a diagnostic disappear from the displayed report.\n\t\tconst parent =\n\t\t\tcontext && Number.isInteger(context.parent) && context.parent > index\n\t\t\t\t? views[context.parent]\n\t\t\t\t: undefined\n\t\tif (!parent) {\n\t\t\troots.push(view)\n\t\t\tcontinue\n\t\t}\n\t\tlet branch = parent.branches.find(\n\t\t\t(branch) => branch.label === context!.label,\n\t\t)\n\t\tif (!branch) {\n\t\t\tbranch = { label: context!.label, diagnostics: [] }\n\t\t\tparent.branches.push(branch)\n\t\t}\n\t\tbranch.diagnostics.push(view)\n\t}\n\treturn roots\n}\n\nexport function diagnosticDetails(\n\tview: DiagnosticView,\n): { label: string; diagnostic: DiagnosticView }[] {\n\treturn view.branches.flatMap((branch) =>\n\t\tbranch.diagnostics.flatMap((child) => [\n\t\t\t{ label: branch.label, diagnostic: child },\n\t\t\t...diagnosticDetails(child).map((detail) => ({\n\t\t\t\t...detail,\n\t\t\t\tlabel: `${branch.label} / ${detail.label}`,\n\t\t\t})),\n\t\t]),\n\t)\n}\n","import {\n\tAjv,\n\ttype ErrorObject,\n\ttype ValidateFunction,\n\ttype SchemaObject,\n} from \"ajv\"\nimport { Ajv2020 } from \"ajv/dist/2020.js\"\nimport addFormats from \"ajv-formats\"\nimport {\n\tassociationFor,\n\tisIncluded,\n\tmodeFor,\n\tschemaUri,\n\ttype Project,\n} from \"./config.ts\"\nimport {\n\tdiagnostic,\n\tparseDocument,\n\tpointer,\n\ttype ParsedDocument,\n} from \"./parse.ts\"\nimport { DIALECTS, SchemaStore, type StoreOptions } from \"./schemas.ts\"\nimport { validationContexts } from \"./diagnostics.ts\"\nimport {\n\tCorrectlyError,\n\tfailure,\n\ttype Diagnostic,\n\ttype FileResult,\n} from \"./types.ts\"\n\nconst ANNOTATIONS = [\n\t\"markdownDescription\",\n\t\"enumDescriptions\",\n\t\"markdownEnumDescriptions\",\n\t\"defaultSnippets\",\n\t\"errorMessage\",\n\t\"patternErrorMessage\",\n\t\"deprecationMessage\",\n\t\"suggestSortText\",\n\t\"doNotSuggest\",\n\t\"allowComments\",\n\t\"allowTrailingCommas\",\n\t\"deprecated\",\n\t\"$vocabulary\",\n]\n\nfunction registerRootAnchors(ajv: Ajv, schema: unknown, uri: string) {\n\tif (typeof schema !== \"object\" || schema === null) return\n\tconst object = schema as SchemaObject\n\tfor (const anchor of [object.$anchor, object.$dynamicAnchor]) {\n\t\tif (typeof anchor === \"string\")\n\t\t\tajv.refs[new URL(`#${anchor}`, object.$id ?? uri).href] =\n\t\t\t\tobject.$id ?? uri\n\t}\n}\n\nfunction validationMessage(error: ErrorObject): string {\n\tif (error.keyword === \"anyOf\")\n\t\treturn \"must match at least one alternative; none matched\"\n\tif (\n\t\terror.keyword === \"if\" &&\n\t\t[\"then\", \"else\"].includes(error.params.failingKeyword as string)\n\t)\n\t\treturn `must satisfy the ${error.params.failingKeyword} branch of the conditional schema`\n\tif (error.keyword === \"uniqueItems\") {\n\t\tconst { i, j } = error.params\n\t\tif (Number.isInteger(i) && Number.isInteger(j))\n\t\t\treturn `duplicates item at ${error.instancePath}${pointer([Math.min(i, j)])}; array items must be unique`\n\t}\n\tif (error.keyword === \"enum\" && Array.isArray(error.params.allowedValues))\n\t\treturn `must be one of: ${error.params.allowedValues.map((value: unknown) => JSON.stringify(value)).join(\", \")}`\n\tif (error.keyword === \"const\" && Object.hasOwn(error.params, \"allowedValue\"))\n\t\treturn `must equal ${JSON.stringify(error.params.allowedValue)}`\n\tif (error.keyword === \"oneOf\") {\n\t\tif (error.params.passingSchemas === null)\n\t\t\treturn \"must match exactly one alternative; none matched\"\n\t\tif (Array.isArray(error.params.passingSchemas))\n\t\t\treturn `must match exactly one alternative; multiple matched (alternatives ${error.params.passingSchemas.map((index: number) => index + 1).join(\", \")})`\n\t}\n\treturn error.message ?? \"Schema violation\"\n}\n\nfunction validationDiagnostic(\n\ttext: string,\n\tparsed: ParsedDocument,\n\terror: ErrorObject,\n): Diagnostic {\n\tlet jsonPointer = error.instancePath\n\tif (\n\t\terror.keyword === \"uniqueItems\" &&\n\t\tNumber.isInteger(error.params.i) &&\n\t\tNumber.isInteger(error.params.j)\n\t)\n\t\tjsonPointer += pointer([Math.max(error.params.i, error.params.j)])\n\tconst property: unknown =\n\t\terror.params.additionalProperty ??\n\t\terror.params.unevaluatedProperty ??\n\t\terror.params.missingProperty ??\n\t\terror.params.propertyName ??\n\t\terror.propertyName\n\tif (typeof property === \"string\") jsonPointer += pointer([property])\n\tconst node = parsed.locate(\n\t\tjsonPointer,\n\t\terror.keyword === \"additionalProperties\" ||\n\t\t\terror.keyword === \"unevaluatedProperties\" ||\n\t\t\terror.keyword === \"propertyNames\" ||\n\t\t\ttypeof error.propertyName === \"string\",\n\t)\n\tconst rangeNode =\n\t\terror.keyword === \"required\" && error.propertyName === undefined\n\t\t\t? parsed.locate(error.instancePath)\n\t\t\t: node\n\tconst length =\n\t\terror.keyword === \"required\" && error.propertyName === undefined\n\t\t\t? 1\n\t\t\t: rangeNode?.length\n\treturn diagnostic(\n\t\ttext,\n\t\t`schema/${error.keyword}`,\n\t\t`${jsonPointer || \"/\"}: ${validationMessage(error)}`,\n\t\tjsonPointer,\n\t\trangeNode?.offset ?? 0,\n\t\tlength ?? 1,\n\t)\n}\n\nexport class Engine {\n\treadonly project: Project\n\treadonly store: SchemaStore\n\tprivate readonly compiled = new Map<string, Promise<ValidateFunction>>()\n\tconstructor(project: Project, options: StoreOptions = {}) {\n\t\tthis.project = project\n\t\tthis.store = new SchemaStore(project, options)\n\t}\n\n\tvalidator(uri: string): Promise<ValidateFunction> {\n\t\tlet compiled = this.compiled.get(uri)\n\t\tif (!compiled) {\n\t\t\tcompiled = this.compile(uri)\n\t\t\tthis.compiled.set(uri, compiled)\n\t\t}\n\t\treturn compiled\n\t}\n\n\tprivate async compile(uri: string): Promise<ValidateFunction> {\n\t\ttry {\n\t\t\tconst resource = await this.store.load(uri)\n\t\t\tconst options = {\n\t\t\t\tallErrors: true,\n\t\t\t\tstrictSchema: true,\n\t\t\t\tstrictTypes: false,\n\t\t\t\tstrictTuples: false,\n\t\t\t\tstrictRequired: false,\n\t\t\t\tcoerceTypes: false,\n\t\t\t\tuseDefaults: false,\n\t\t\t\tremoveAdditional: false,\n\t\t\t\tvalidateSchema: true,\n\t\t\t\tloadSchema: async (ref: string) => {\n\t\t\t\t\tconst loaded = await this.store.load(ref, resource.dialect)\n\t\t\t\t\tif (loaded.dialect !== resource.dialect)\n\t\t\t\t\t\tthrow new CorrectlyError(\n\t\t\t\t\t\t\t\"unsupported-dialect\",\n\t\t\t\t\t\t\t`Mixed schema dialects: ${uri} references ${ref}`,\n\t\t\t\t\t\t)\n\t\t\t\t\tregisterRootAnchors(ajv, loaded.schema, loaded.uri)\n\t\t\t\t\treturn loaded.schema as SchemaObject\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst ajv =\n\t\t\t\tresource.dialect === \"2020-12\" ? new Ajv2020(options) : new Ajv(options)\n\t\t\t// CommonJS package exports retain a callable default at runtime.\n\t\t\tconst formats = addFormats as unknown as (instance: Ajv) => void\n\t\t\tformats(ajv)\n\t\t\tfor (const keyword of ANNOTATIONS)\n\t\t\t\tif (!ajv.RULES.keywords[keyword])\n\t\t\t\t\tajv.addKeyword({ keyword, valid: true })\n\t\t\t// Ajv indexes plain anchors during reference resolution, but does not\n\t\t\t// register the annotation keyword in its strict vocabulary.\n\t\t\tif (resource.dialect === \"2020-12\")\n\t\t\t\tajv.addKeyword({\n\t\t\t\t\tkeyword: \"$anchor\",\n\t\t\t\t\tschemaType: \"string\",\n\t\t\t\t\tvalid: true,\n\t\t\t\t})\n\t\t\tajv.addSchema(resource.schema, resource.uri)\n\t\t\tregisterRootAnchors(ajv, resource.schema, resource.uri)\n\t\t\tconst validate = await ajv.compileAsync({\n\t\t\t\t$schema: DIALECTS[resource.dialect],\n\t\t\t\t$ref: uri,\n\t\t\t})\n\t\t\treturn validate as ValidateFunction\n\t\t} catch (error) {\n\t\t\tif (error instanceof CorrectlyError) throw error\n\t\t\tthrow new CorrectlyError(\n\t\t\t\t\"schema\",\n\t\t\t\t`Cannot compile schema ${uri}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync prepare(): Promise<void> {\n\t\t// Register configured resources before compiling references to their IDs.\n\t\tfor (const association of this.project.config.associations) {\n\t\t\tif (association.schema !== null)\n\t\t\t\tawait this.store.load(schemaUri(association.schema, this.project.root))\n\t\t}\n\t\tfor (const association of this.project.config.associations) {\n\t\t\tif (association.schema !== null)\n\t\t\t\tawait this.validator(schemaUri(association.schema, this.project.root))\n\t\t}\n\t}\n\n\tasync validate(file: string, text: string): Promise<FileResult> {\n\t\tconst association = associationFor(this.project, file)\n\t\tconst mode = association?.mode ?? modeFor(file)\n\t\tconst result: FileResult = {\n\t\t\tfile,\n\t\t\tmode,\n\t\t\tassociation,\n\t\t\tcoverage: \"excluded\",\n\t\t\tdiagnostics: [],\n\t\t\tfailures: [],\n\t\t}\n\t\tif (!isIncluded(this.project, file)) return result\n\t\tresult.coverage = association?.schema ? \"schema\" : \"syntax-only\"\n\t\tconst parsed = parseDocument(text, mode)\n\t\tresult.diagnostics.push(...parsed.diagnostics)\n\t\tif (association?.schema) {\n\t\t\ttry {\n\t\t\t\tconst validate = await this.validator(association.schema)\n\t\t\t\tif (parsed.diagnostics.length === 0 && !validate(parsed.value)) {\n\t\t\t\t\tresult.diagnostics.push(\n\t\t\t\t\t\t...validationContexts(\n\t\t\t\t\t\t\tvalidate.errors ?? [],\n\t\t\t\t\t\t\t(validate.errors ?? []).map((e) =>\n\t\t\t\t\t\t\t\tvalidationDiagnostic(text, parsed, e),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tresult.failures.push(failure(error, file))\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n}\n"],"mappings":"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"}
|
package/dist/lsp.mjs
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { b as diagnostic, d as discoverConfig, g as readText, l as associationFor, m as loadProject, n as diagnosticDetails, p as isIncluded, r as diagnosticViews, t as Engine, u as contains, v as CorrectlyError, y as failure } from "./engine-BgWBnb1A.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { DidChangeWatchedFilesNotification, LSPErrorCodes, ResponseError, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node";
|
|
6
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
7
|
+
import { getLanguageService } from "vscode-json-languageservice";
|
|
8
|
+
//#region src/lsp/workspace.ts
|
|
9
|
+
var Workspace = class {
|
|
10
|
+
onSchema;
|
|
11
|
+
roots;
|
|
12
|
+
read;
|
|
13
|
+
documents = /* @__PURE__ */ new Map();
|
|
14
|
+
engines = /* @__PURE__ */ new Map();
|
|
15
|
+
controller = new AbortController();
|
|
16
|
+
generation = 0;
|
|
17
|
+
constructor(roots, read = readText) {
|
|
18
|
+
this.roots = roots;
|
|
19
|
+
this.read = read;
|
|
20
|
+
}
|
|
21
|
+
invalidate() {
|
|
22
|
+
this.generation++;
|
|
23
|
+
this.controller.abort();
|
|
24
|
+
this.controller = new AbortController();
|
|
25
|
+
this.engines.clear();
|
|
26
|
+
}
|
|
27
|
+
open(document) {
|
|
28
|
+
this.documents.set(document.uri, document);
|
|
29
|
+
const resource = path.basename(fileURLToPath(document.uri)) === "correctly.config.json" || this.isSchema(document.uri);
|
|
30
|
+
if (resource) this.invalidate();
|
|
31
|
+
return resource;
|
|
32
|
+
}
|
|
33
|
+
close(uri) {
|
|
34
|
+
const resource = uri.endsWith("/correctly.config.json") || this.isSchema(uri);
|
|
35
|
+
this.documents.delete(uri);
|
|
36
|
+
if (resource) this.invalidate();
|
|
37
|
+
return resource;
|
|
38
|
+
}
|
|
39
|
+
schemaUris = /* @__PURE__ */ new Set();
|
|
40
|
+
isSchema(uri) {
|
|
41
|
+
return this.schemaUris.has(uri);
|
|
42
|
+
}
|
|
43
|
+
resourceChanged(uri) {
|
|
44
|
+
return uri.endsWith("/correctly.config.json") || this.isSchema(uri);
|
|
45
|
+
}
|
|
46
|
+
readBuffer = (uri) => {
|
|
47
|
+
const document = this.documents.get(uri);
|
|
48
|
+
return document ? Promise.resolve(document.getText()) : this.read(uri);
|
|
49
|
+
};
|
|
50
|
+
async engineFor(uri) {
|
|
51
|
+
const file = fileURLToPath(uri);
|
|
52
|
+
const root = this.roots.filter((r) => contains(r, file)).sort((a, b) => b.length - a.length)[0];
|
|
53
|
+
if (this.roots.length && !root) throw new CorrectlyError("config", `Document is outside the editor workspace: ${file}`);
|
|
54
|
+
const virtualConfigs = new Set([...this.documents.keys()].filter((key) => key.endsWith("/correctly.config.json")).map((key) => fileURLToPath(key)));
|
|
55
|
+
const configPath = await discoverConfig(path.dirname(file), root, virtualConfigs);
|
|
56
|
+
let promise = this.engines.get(configPath);
|
|
57
|
+
if (!promise) {
|
|
58
|
+
const signal = this.controller.signal;
|
|
59
|
+
promise = (async () => {
|
|
60
|
+
const project = await loadProject(configPath, this.readBuffer);
|
|
61
|
+
const readSchema = async (schema) => {
|
|
62
|
+
this.schemaUris.add(schema);
|
|
63
|
+
this.onSchema?.(schema);
|
|
64
|
+
return this.readBuffer(schema);
|
|
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);
|
|
76
|
+
}
|
|
77
|
+
return promise;
|
|
78
|
+
}
|
|
79
|
+
async validate(document) {
|
|
80
|
+
try {
|
|
81
|
+
return await (await this.engineFor(document.uri)).validate(fileURLToPath(document.uri), document.getText());
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const problem = failure(error, fileURLToPath(document.uri));
|
|
84
|
+
return {
|
|
85
|
+
file: fileURLToPath(document.uri),
|
|
86
|
+
mode: document.languageId === "jsonc" ? "jsonc" : "json",
|
|
87
|
+
association: null,
|
|
88
|
+
coverage: "syntax-only",
|
|
89
|
+
diagnostics: [diagnostic(document.getText(), problem.code, problem.message)],
|
|
90
|
+
failures: [problem]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
documentPath(uri) {
|
|
95
|
+
return fileURLToPath(uri);
|
|
96
|
+
}
|
|
97
|
+
rootUris() {
|
|
98
|
+
return this.roots.map((root) => pathToFileURL(root).href);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
//#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
|
+
//#region src/lsp/server.ts
|
|
154
|
+
function lspDiagnostics(result, text) {
|
|
155
|
+
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) => ({
|
|
156
|
+
range: d.range,
|
|
157
|
+
message: d.message,
|
|
158
|
+
code: d.code,
|
|
159
|
+
severity: 1,
|
|
160
|
+
source: "correctly",
|
|
161
|
+
data: { pointer: d.pointer },
|
|
162
|
+
...d.branches.length ? { relatedInformation: diagnosticDetails(d).map(({ label, diagnostic }) => ({
|
|
163
|
+
location: {
|
|
164
|
+
uri: pathToFileURL(result.file).href,
|
|
165
|
+
range: diagnostic.range
|
|
166
|
+
},
|
|
167
|
+
message: `${label}: ${diagnostic.message}`
|
|
168
|
+
})) } : {}
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
function startServer() {
|
|
172
|
+
const connection = createConnection();
|
|
173
|
+
const documents = new TextDocuments(TextDocument);
|
|
174
|
+
const workspace = new Workspace([]);
|
|
175
|
+
const hints = new Hints();
|
|
176
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
177
|
+
let dynamicWatch = false;
|
|
178
|
+
const watchedSchemas = /* @__PURE__ */ new Set();
|
|
179
|
+
workspace.onSchema = (uri) => {
|
|
180
|
+
if (!dynamicWatch || !uri.startsWith("file:") || watchedSchemas.has(uri)) return;
|
|
181
|
+
watchedSchemas.add(uri);
|
|
182
|
+
const file = fileURLToPath(uri);
|
|
183
|
+
connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: {
|
|
184
|
+
baseUri: pathToFileURL(path.dirname(file)).href,
|
|
185
|
+
pattern: path.basename(file)
|
|
186
|
+
} }] }).catch((error) => connection.console.error(String(error)));
|
|
187
|
+
};
|
|
188
|
+
connection.onInitialize((params) => {
|
|
189
|
+
workspace.roots = (params.workspaceFolders?.map((f) => f.uri) ?? (params.rootUri ? [params.rootUri] : [])).filter((uri) => uri.startsWith("file:")).map((uri) => fileURLToPath(uri));
|
|
190
|
+
dynamicWatch = params.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration ?? false;
|
|
191
|
+
return {
|
|
192
|
+
capabilities: {
|
|
193
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
194
|
+
completionProvider: { triggerCharacters: ["\"", ":"] },
|
|
195
|
+
hoverProvider: true,
|
|
196
|
+
workspace: { workspaceFolders: {
|
|
197
|
+
supported: true,
|
|
198
|
+
changeNotifications: true
|
|
199
|
+
} }
|
|
200
|
+
},
|
|
201
|
+
serverInfo: {
|
|
202
|
+
name: "correctly",
|
|
203
|
+
version: "0.0.0"
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
connection.onInitialized(() => {
|
|
208
|
+
if (dynamicWatch) connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: "**/*.{json,jsonc}" }] }).catch((error) => connection.console.error(String(error)));
|
|
209
|
+
connection.workspace.onDidChangeWorkspaceFolders((event) => {
|
|
210
|
+
const removed = new Set(event.removed.map((f) => fileURLToPath(f.uri)));
|
|
211
|
+
workspace.roots = [...workspace.roots.filter((r) => !removed.has(r)), ...event.added.map((f) => fileURLToPath(f.uri))];
|
|
212
|
+
refresh();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
function schedule(document) {
|
|
216
|
+
if (!document.uri.startsWith("file:")) return;
|
|
217
|
+
const previous = jobs.get(document.uri);
|
|
218
|
+
if (previous) {
|
|
219
|
+
clearTimeout(previous.timer);
|
|
220
|
+
previous.controller.abort();
|
|
221
|
+
}
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
const generation = workspace.generation;
|
|
224
|
+
const timer = setTimeout(() => {
|
|
225
|
+
workspace.validate(document).then((result) => {
|
|
226
|
+
if (controller.signal.aborted || workspace.generation !== generation || documents.get(document.uri)?.version !== document.version) return;
|
|
227
|
+
return connection.sendDiagnostics({
|
|
228
|
+
uri: document.uri,
|
|
229
|
+
version: document.version,
|
|
230
|
+
diagnostics: lspDiagnostics(result, document.getText())
|
|
231
|
+
});
|
|
232
|
+
}).catch((error) => connection.console.error(String(error)));
|
|
233
|
+
}, 40);
|
|
234
|
+
jobs.set(document.uri, {
|
|
235
|
+
controller,
|
|
236
|
+
timer
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function scheduleAll() {
|
|
240
|
+
for (const document of documents.all()) schedule(document);
|
|
241
|
+
}
|
|
242
|
+
function refresh() {
|
|
243
|
+
workspace.invalidate();
|
|
244
|
+
scheduleAll();
|
|
245
|
+
}
|
|
246
|
+
documents.onDidChangeContent(({ document }) => {
|
|
247
|
+
if (!document.uri.startsWith("file:")) return;
|
|
248
|
+
if (workspace.open(document)) scheduleAll();
|
|
249
|
+
else schedule(document);
|
|
250
|
+
});
|
|
251
|
+
documents.onDidClose(async ({ document }) => {
|
|
252
|
+
const job = jobs.get(document.uri);
|
|
253
|
+
if (job) {
|
|
254
|
+
clearTimeout(job.timer);
|
|
255
|
+
job.controller.abort();
|
|
256
|
+
jobs.delete(document.uri);
|
|
257
|
+
}
|
|
258
|
+
await connection.sendDiagnostics({
|
|
259
|
+
uri: document.uri,
|
|
260
|
+
diagnostics: []
|
|
261
|
+
});
|
|
262
|
+
if (document.uri.startsWith("file:") && workspace.close(document.uri)) scheduleAll();
|
|
263
|
+
});
|
|
264
|
+
connection.onDidChangeWatchedFiles((params) => {
|
|
265
|
+
if (params.changes.some((change) => workspace.resourceChanged(change.uri))) refresh();
|
|
266
|
+
});
|
|
267
|
+
connection.onDidChangeConfiguration(refresh);
|
|
268
|
+
async function request(uri, token, action) {
|
|
269
|
+
const document = documents.get(uri);
|
|
270
|
+
if (!document || !uri.startsWith("file:")) return null;
|
|
271
|
+
const generation = workspace.generation;
|
|
272
|
+
const cancelled = () => token.isCancellationRequested || workspace.generation !== generation || documents.get(uri)?.version !== document.version;
|
|
273
|
+
if (cancelled()) throw new ResponseError(LSPErrorCodes.RequestCancelled, "Request cancelled");
|
|
274
|
+
const operation = (async () => {
|
|
275
|
+
try {
|
|
276
|
+
const engine = await workspace.engineFor(uri);
|
|
277
|
+
if (cancelled()) throw new ResponseError(LSPErrorCodes.RequestCancelled, "Request cancelled");
|
|
278
|
+
const result = await action(engine, document);
|
|
279
|
+
if (cancelled()) throw new ResponseError(LSPErrorCodes.RequestCancelled, "Request cancelled");
|
|
280
|
+
return result;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error instanceof ResponseError) throw error;
|
|
283
|
+
if (cancelled()) throw new ResponseError(LSPErrorCodes.RequestCancelled, "Request cancelled");
|
|
284
|
+
connection.console.error(String(error));
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
})();
|
|
288
|
+
let listener;
|
|
289
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
290
|
+
listener = token.onCancellationRequested(() => reject(new ResponseError(LSPErrorCodes.RequestCancelled, "Request cancelled")));
|
|
291
|
+
});
|
|
292
|
+
try {
|
|
293
|
+
return await Promise.race([operation, cancellation]);
|
|
294
|
+
} finally {
|
|
295
|
+
listener?.dispose();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
connection.onCompletion((params, token) => request(params.textDocument.uri, token, (engine, document) => hints.complete(engine, document, params.position)));
|
|
299
|
+
connection.onHover((params, token) => request(params.textDocument.uri, token, (engine, document) => hints.hover(engine, document, params.position)));
|
|
300
|
+
connection.onShutdown(() => {
|
|
301
|
+
workspace.invalidate();
|
|
302
|
+
for (const job of jobs.values()) {
|
|
303
|
+
clearTimeout(job.timer);
|
|
304
|
+
job.controller.abort();
|
|
305
|
+
}
|
|
306
|
+
jobs.clear();
|
|
307
|
+
});
|
|
308
|
+
documents.listen(connection);
|
|
309
|
+
connection.listen();
|
|
310
|
+
}
|
|
311
|
+
startServer();
|
|
312
|
+
//#endregion
|
|
313
|
+
export { lspDiagnostics, startServer };
|
|
314
|
+
|
|
315
|
+
//# sourceMappingURL=lsp.mjs.map
|
package/dist/lsp.mjs.map
ADDED
|
@@ -0,0 +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":";;;;;;;;AAcA,IAAa,YAAb,MAAuB;CACtB;CACA;CACA;CACA,4BAAqB,IAAI,IAA0B;CACnD,0BAA2B,IAAI,IAA6B;CAC5D,aAAqB,IAAI,gBAAgB;CACzC,aAAa;CACb,YAAY,OAAiB,OAAiB,UAAU;EACvD,KAAK,QAAQ;EACb,KAAK,OAAO;CACb;CAEA,aAAa;EACZ,KAAK;EACL,KAAK,WAAW,MAAM;EACtB,KAAK,aAAa,IAAI,gBAAgB;EACtC,KAAK,QAAQ,MAAM;CACpB;CAEA,KAAK,UAAiC;EACrC,KAAK,UAAU,IAAI,SAAS,KAAK,QAAQ;EACzC,MAAM,WACL,KAAK,SAAS,cAAc,SAAS,GAAG,CAAC,MAAM,2BAC/C,KAAK,SAAS,SAAS,GAAG;EAC3B,IAAI,UAAU,KAAK,WAAW;EAC9B,OAAO;CACR;CACA,MAAM,KAAsB;EAC3B,MAAM,WACL,IAAI,SAAS,wBAAwB,KAAK,KAAK,SAAS,GAAG;EAC5D,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,gBAAgB,KAAsB;EACrC,OAAO,IAAI,SAAS,wBAAwB,KAAK,KAAK,SAAS,GAAG;CACnE;CACA,cAAyC,QAAQ;EAChD,MAAM,WAAW,KAAK,UAAU,IAAI,GAAG;EACvC,OAAO,WAAW,QAAQ,QAAQ,SAAS,QAAQ,CAAC,IAAI,KAAK,KAAK,GAAG;CACtE;CAEA,MAAM,UAAU,KAA8B;EAC7C,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,iBAAiB,IAAI,IAC1B,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,CAAC,CACxB,QAAQ,QAAQ,IAAI,SAAS,wBAAwB,CAAC,CAAC,CACvD,KAAK,QAAQ,cAAc,GAAG,CAAC,CAClC;EACA,MAAM,aAAa,MAAM,eACxB,KAAK,QAAQ,IAAI,GACjB,MACA,cACD;EACA,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU;EACzC,IAAI,CAAC,SAAS;GACb,MAAM,SAAS,KAAK,WAAW;GAC/B,WAAW,YAAY;IACtB,MAAM,UAAU,MAAM,YAAY,YAAY,KAAK,UAAU;IAC7D,MAAM,aAAuB,OAAO,WAAW;KAC9C,KAAK,WAAW,IAAI,MAAM;KAC1B,KAAK,WAAW,MAAM;KACtB,OAAO,KAAK,WAAW,MAAM;IAC9B;IACA,MAAM,SAAS,IAAI,OAAO,SAAS;KAAE,MAAM;KAAY;IAAO,CAAC;IAC/D,MAAM,OAAO,QAAQ;IACrB,KAAK,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO,GACpD,KAAK,WAAW,IAAI,SAAS,GAAG;IACjC,OAAO,eAAe;IACtB,OAAO;GACR,EAAA,CAAG;GACH,KAAK,QAAQ,IAAI,YAAY,OAAO;EACrC;EACA,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,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;;;ACtHA,SAAgB,kBAAkB,UAAsC;CACvE,OAAO,IAAI,MAAM,UAAU,EAC1B,IAAI,QAAQ,KAAK;EAChB,IAAI,QAAQ,QAAQ,OAAO,KAAA;EAC3B,MAAM,QAAiB,QAAQ,IAAI,QAAQ,GAAG;EAC9C,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,MAAM,IAAI;CAC3D,EACD,CAAC;AACF;AAEA,IAAa,QAAb,MAAmB;CAClB,2BAA4B,IAAI,QAG9B;CACF,MAAc,QACb,QACA,UACuC;EACvC,MAAM,OAAO,cAAc,SAAS,GAAG;EACvC,IAAI,CAAC,WAAW,OAAO,SAAS,IAAI,GAAG,OAAO,KAAA;EAC9C,MAAM,cAAc,eAAe,OAAO,SAAS,IAAI;EACvD,IAAI,CAAC,aAAa,QAAQ,OAAO,KAAA;EACjC,MAAM,OAAO,UAAU,YAAY,MAAM;EACzC,IAAI,WAAW,KAAK,SAAS,IAAI,MAAM;EACvC,IAAI,CAAC,UAAU;GACd,2BAAW,IAAI,IAAI;GACnB,KAAK,SAAS,IAAI,QAAQ,QAAQ;EACnC;EACA,IAAI,UAAU,SAAS,IAAI,YAAY,MAAM;EAC7C,IAAI,CAAC,SAAS;GACb,UAAU,mBAAmB;IAC5B,sBAAsB,OAAO,QAC5B,KAAK,WAAW,MAAM,OAAO,MAAM,KAAK,GAAG,EAAA,CAAG,MAAM;IACrD,kBAAkB,EACjB,sBAAsB,UAAU,aAC/B,IAAI,IAAI,UAAU,QAAQ,CAAC,CAAC,KAC9B;GACD,CAAC;GAED,QAAQ,UAAU;IACjB,UAAU;IACV,SAAS,CAAC;KAAE,KAAK,YAAY;KAAQ,WAAW,CAAC,GAAG;IAAE,CAAC;GACxD,CAAC;GACD,SAAS,IAAI,YAAY,QAAQ,OAAO;EACzC;EACA,OAAO;CACR;CACA,MAAM,SAAS,QAAgB,UAAwB,UAAoB;EAC1E,MAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ,QAAQ;EACnD,OAAO,UACJ,MAAM,QAAQ,WACd,UACA,UACA,kBAAkB,QAAQ,kBAAkB,QAAQ,CAAC,CACtD,IACC;GAAE,cAAc;GAAO,OAAO,CAAC;EAAE;CACrC;CACA,MAAM,MAAM,QAAgB,UAAwB,UAAoB;EACvE,MAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ,QAAQ;EACnD,OAAO,UACJ,MAAM,QAAQ,QACd,UACA,UACA,kBAAkB,QAAQ,kBAAkB,QAAQ,CAAC,CACtD,IACC;CACJ;AACD;;;AC9DA,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,qBAAqB;IACvC,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,oBAAoB,CAAC,EAChD,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,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"}
|
package/docs/guide.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Correctly guide
|
|
2
|
+
|
|
3
|
+
## One configuration for CI and editors
|
|
4
|
+
|
|
5
|
+
Create `correctly.config.json`:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"files": ["config/**/*.json", "config/**/*.jsonc"],
|
|
10
|
+
"exclude": ["config/generated/**"],
|
|
11
|
+
"associations": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Application",
|
|
14
|
+
"files": ["config/**"],
|
|
15
|
+
"schema": "schemas/application.json"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Local settings",
|
|
19
|
+
"files": ["config/local.json"],
|
|
20
|
+
"mode": "jsonc",
|
|
21
|
+
"schema": null
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"remote": {
|
|
25
|
+
"offline": false,
|
|
26
|
+
"cacheDir": ".correctly-cache",
|
|
27
|
+
"timeoutMs": 5000,
|
|
28
|
+
"maxBytes": 2097152,
|
|
29
|
+
"maxRequests": 64
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Configuration is strict JSON, validated by the exported `correctly/config.schema.json`. Unknown configuration properties, invalid modes, and duplicate keys fail. There is no executable configuration, implicit merging, or second editor map.
|
|
35
|
+
|
|
36
|
+
## Discovery, paths and precedence
|
|
37
|
+
|
|
38
|
+
`correctly check` discovers the nearest configuration by searching upward from the invocation directory. `--config path` overrides discovery and resolves from that directory. CLI discovery selects one project; invoke once per independent workspace root. Explicit document paths also resolve from the invocation directory and must be included by that project's patterns.
|
|
39
|
+
|
|
40
|
+
Editors choose the longest workspace root containing a file, then search upward from the file's directory, stopping at that root. Nested configurations override parent configurations completely. Sibling roots remain independent. Missing configurations become editor diagnostics. Unsaved configuration contents, including newly opened configs, participate in discovery.
|
|
41
|
+
|
|
42
|
+
File patterns, exclusions, local schema paths, and cache paths resolve from the configuration directory. Patterns use forward slashes, can match dotfiles, and must be relative without `..` or leading `!`. Use `exclude` instead of negated patterns. `files` defaults to `**/*.json` and `**/*.jsonc`. Built-in exclusions cover `node_modules`, `.git`, `dist`, `artifacts`, and `.correctly-cache`; configured cache directories inside the project are also excluded. User exclusions add to these defaults. Excluded files are skipped.
|
|
43
|
+
|
|
44
|
+
Associations are ordered: the **last matching rule wins**. A matching association's `mode` overrides the extension; otherwise `.jsonc` selects JSONC and other files use strict JSON. A null schema explicitly requests syntax-only coverage. Unassociated included documents are also syntax-checked and reported as `syntax-only`; missing coverage does not fail the command. Reports include the matched index, name, schema URI, mode, and coverage. Association patterns do not expand the project's `files` list.
|
|
45
|
+
|
|
46
|
+
## Parsing and validation
|
|
47
|
+
|
|
48
|
+
Strict JSON rejects comments and trailing commas; JSONC allows both. Both modes reject duplicate object keys, including escaped duplicates, and preserve useful source ranges and JSON pointers. Incomplete editor documents receive syntax diagnostics and tolerant completion/hover; Ajv validates values only once syntax is valid. UTF-16 ranges are zero-based and compatible with LSP; readable CLI locations are one-based. Duplicate-key diagnostics point to the second key, value errors point to the value, disallowed-property errors point to the key, and missing-property errors point to the containing object's start and retain the missing property's pointer.
|
|
49
|
+
|
|
50
|
+
Document `$schema` is always ordinary data. It is neither removed nor used to select a schema, and a schema can reject that property with `additionalProperties: false`. This also holds for editor hints and unassociated documents. Schema documents' own `$schema` fields retain dialect meaning.
|
|
51
|
+
|
|
52
|
+
Ajv provides authoritative validation in both CLI and LSP. Separate instances support draft 7 and draft 2020-12, including prefixItems, unevaluatedProperties, and dynamic references. Root schemas without `$schema` default to draft 7; referenced resources without one inherit the referencing dialect. Unknown dialects, mixed-dialect graphs, unsupported required vocabularies, unresolved references, invalid schemas, and unknown validation keywords fail visibly. Standard 2020-12 core/applicator/unevaluated/validation/metadata/format-annotation/content vocabularies are recognized. Required format-assertion vocabulary and custom required vocabularies are not supported. ajv-formats applies its supported format checks in both drafts; unknown formats fail compilation. Content decoding/validation is not performed. `$async` is unsupported.
|
|
53
|
+
|
|
54
|
+
Descriptive Microsoft schema extensions, such as `markdownDescription`, `enumDescriptions`, and `defaultSnippets`, are treated as annotations. No values are coerced, no defaults are inserted, and no properties are removed. Formatting is outside validation.
|
|
55
|
+
|
|
56
|
+
## References and cache
|
|
57
|
+
|
|
58
|
+
Schema associations accept local paths, `file:` URIs, and HTTP(S) URLs, optionally with pointer or anchor fragments. Relative references resolve from their schema resource's `$id`, or its retrieval URI when there is no identifier. Relative `$id` values are resolved from their parent resource. Configured schema resources are registered before compilation, so identifiers can refer to other configured local schemas without needing to host them. References to unloaded identifiers use the loader. Cycles and nested resources are resolved by Ajv. Only supported dialect graphs are accepted.
|
|
59
|
+
|
|
60
|
+
Remote requests have a total timeout per resource, a byte limit, at most five redirects, and a resource-count limit per engine. Defaults are shown above; maximum configuration values are 60 seconds, 16 MiB, and 256 resources. Cache entries are keyed by the full retrieval URL without its fragment. Online loads refresh cache entries and send ETag/Last-Modified validators when available. Requests without validators fetch again. Network or HTTP failures never silently fall back to stale cache. Local and loaded remote resources and compiled validators are reused for the life of an engine.
|
|
61
|
+
|
|
62
|
+
`correctly check --offline` prevents all HTTP requests; a missing or corrupt cached schema fails with exit 2. Warm the cache with an online check, retain the cache directory in CI, then validate offline. Local references still work offline. Cache location is explicit and portable when kept with the project's CI cache. Schema resource size limits also apply to local schemas.
|
|
63
|
+
|
|
64
|
+
Editor watchers refresh configurations and local schemas, including schemas outside workspace roots through explicit dependency watchers; unsaved schema edits also invalidate validators and hints. Relevant schema/configuration changes and server restart refresh remote schemas. Cache writes and unrelated JSON changes do not trigger reloads. Remote servers do not send change notifications, so remote changes require one of those refresh triggers. Cancellation and document version/generation guards prevent older work from publishing diagnostics or hints after newer edits. Closing a buffer returns it to disk contents; closing a document clears its diagnostics.
|
|
65
|
+
|
|
66
|
+
## CLI and report contract
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
correctly check
|
|
70
|
+
correctly check --config config/correctly.config.json
|
|
71
|
+
correctly check config/application.json --format json
|
|
72
|
+
correctly check --offline
|
|
73
|
+
correctly-lsp --stdio
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Exit 0 means no validation or infrastructure failures; exit 1 means at least one invalid document; exit 2 means a configuration/schema/loading/execution failure, which takes priority over document errors. All configured associations are prepared even when their patterns match no documents, preventing unused broken schema maps from going unnoticed. Readable output identifies every file's coverage and selected association. JSON output is a single object without progress text or a filtering workaround.
|
|
77
|
+
|
|
78
|
+
Readable output follows Lasertag's file sections: a heading with the file name, error/failure counts, parsing mode, and selected schema; diagnostics sorted by source position; and numbered source excerpts with carets marking each affected range. Files without a schema explicitly say `syntax only`. Excerpts use the exact text validated, expand tabs to four spaces, and include one context line on each side unless that line has another diagnostic. Long ranges show their first and last lines with an ellipsis between them. Paths inside the current directory are relative; other paths remain absolute.
|
|
79
|
+
|
|
80
|
+
Missing required properties point to the containing object, while the diagnostic's JSON pointer names the missing property. Enum violations list the allowed values as JSON, including values from referenced schemas. `const` violations include the required value, including `null`, `false`, arrays, and objects. Property-name failures highlight the offending key, including the underlying pattern, enum, or constant reason. `uniqueItems` highlights the later duplicate and names the earlier item by its JSON pointer; array indices remain zero-based.
|
|
81
|
+
|
|
82
|
+
`oneOf` distinguishes no matching alternatives from overlapping alternatives; matching witnesses are numbered from 1 in schema order, without claiming an exhaustive match count. Failures from `anyOf`, `oneOf`, and conditional schemas are grouped under their summary, with reasons labeled by alternative or by the selected `then`/`else` branch. Nested groups retain their hierarchy. Readable error counts include the top-level groups; repeated identical source ranges appear once within a group. The editor publishes those same primary diagnostics with the nested reasons and their locations in LSP related information. Grouping uses Ajv's schema paths and instance paths. References that lose their caller provenance retain standalone errors rather than receiving a guessed branch label. Every validation failure remains visible.
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
data/project.json 1 error · JSON against Project (schemas/project.schema.json)
|
|
86
|
+
└─ 2:11 schema/type
|
|
87
|
+
1 │ {
|
|
88
|
+
2 │ "name": 42
|
|
89
|
+
│ ^^
|
|
90
|
+
3 │ }
|
|
91
|
+
╰─ /name: must be string
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Readable reports use terminal colors automatically: bold file names, red error counts, cyan diagnostic codes, yellow carets, and dim context/gutters. Success summaries are green. Set `NO_COLOR=1` or `FORCE_COLOR=0` to disable colors, or `FORCE_COLOR=1` to force them when piping output. JSON reports never include ANSI styling.
|
|
95
|
+
|
|
96
|
+
`reportVersion: 1` contains `config`, sorted `files`, top-level `failures`, `summary`, and `exitCode`. Each file has `file`, `mode`, `coverage`, `association`, `diagnostics`, and `failures`. Diagnostics contain `code`, `message`, `pointer`, `offset`, `length`, and `range`; failures contain `code`, `message`, and optionally `file`. Paths are absolute. The JSON report retains the complete flat diagnostic list in validator order, including every grouped reason. A grouped reason may additionally have `context: { parent, label }`: `parent` is the zero-based index of its summary in that same file's `diagnostics` array; `label` names its branch. Do not reorder that array before resolving context indices. The exported `diagnosticViews` helper projects it into the same hierarchy used by CLI and LSP, and `diagnosticDetails` lists the nested reasons. Messages reflect the pinned validator/parser versions and Correctly's message enrichment; consumers should use codes and pointers rather than parsing message prose.
|
|
97
|
+
|
|
98
|
+
## Editor installation and shared APIs
|
|
99
|
+
|
|
100
|
+
From the repository, run `pnpm build:vsix`, then `code --install-extension artifacts/Correctly-0.0.0.vsix`. The universal VSIX bundles the client and server and needs no npm dependencies on the editor machine. Open a workspace with a configuration to activate JSON/JSONC support. The extension's **Correctly: Restart Server** command refreshes its lifecycle. Other LSP clients launch `correctly-lsp --stdio` and must send workspace folders and file-change notifications for automatic disk refresh. No formatter or automatic fixes are registered.
|
|
101
|
+
|
|
102
|
+
Microsoft's JSON language service supplies completions and hover. Its AST is retained with traversal methods bound to the original document; a selection view disables automatic embedded `$schema` selection. Ajv remains the validation authority. Built-in VS Code JSON features may independently provide suggestions/diagnostics; set `json.validate.enable` to `false` to use only Correctly validation, if desired.
|
|
103
|
+
|
|
104
|
+
The package root exports `loadProject`, `Engine`, `SchemaStore`, parser/location utilities, configuration discovery/matching, and report/diagnostic types:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { Engine, loadProject } from "correctly"
|
|
108
|
+
|
|
109
|
+
const engine = new Engine(await loadProject("/project/correctly.config.json"))
|
|
110
|
+
await engine.prepare()
|
|
111
|
+
const result = await engine.validate("/project/config/app.json", sourceText)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The core owns association, loading, and validation; parsing is isolated in its own module. The validation layer consumes a value, diagnostics, and a pointer-to-source-span locator without depending on the JSON AST. A future YAML/TOML adapter must supply equivalent pointer/source mapping and explicit format semantics before becoming a supported mode. Those formats are not accepted by the initial configuration schema.
|