correctly 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -2
- package/README.md +21 -15
- package/dist/ajv.d.mts +11 -0
- package/dist/ajv.d.mts.map +1 -0
- package/dist/ajv.mjs +221 -0
- package/dist/ajv.mjs.map +1 -0
- package/dist/check-BI7U3ZLc.mjs +88 -0
- package/dist/check-BI7U3ZLc.mjs.map +1 -0
- package/dist/cli.mjs +14 -62
- package/dist/cli.mjs.map +1 -1
- package/dist/config-Bx1-XXeo.d.mts +189 -0
- package/dist/config-Bx1-XXeo.d.mts.map +1 -0
- package/dist/config-Cb6o4YnJ.mjs +348 -0
- package/dist/config-Cb6o4YnJ.mjs.map +1 -0
- package/dist/core.d.mts +27 -125
- package/dist/core.d.mts.map +1 -1
- package/dist/core.mjs +5 -2
- package/dist/diagnostics-BqD1WOy4.mjs +70 -0
- package/dist/diagnostics-BqD1WOy4.mjs.map +1 -0
- package/dist/engine-VST0hoSk.mjs +111 -0
- package/dist/engine-VST0hoSk.mjs.map +1 -0
- package/dist/extensions-ClclWslY.d.mts +11 -0
- package/dist/extensions-ClclWslY.d.mts.map +1 -0
- package/dist/hints-HvSIhuMM.mjs +57 -0
- package/dist/hints-HvSIhuMM.mjs.map +1 -0
- package/dist/lsp.mjs +40 -91
- package/dist/lsp.mjs.map +1 -1
- package/dist/renovate.d.mts +5 -0
- package/dist/renovate.d.mts.map +1 -0
- package/dist/renovate.mjs +11 -0
- package/dist/renovate.mjs.map +1 -0
- package/dist/schemas-UxCtIdo2.mjs +265 -0
- package/dist/schemas-UxCtIdo2.mjs.map +1 -0
- package/dist/session-DCFav56x.mjs +113 -0
- package/dist/session-DCFav56x.mjs.map +1 -0
- package/dist/worker.mjs +80 -0
- package/dist/worker.mjs.map +1 -0
- package/docs/guide.md +166 -34
- package/examples/basic/correctly.config.ts +13 -0
- package/package.json +69 -60
- package/src/cli/check.ts +41 -11
- package/src/cli/main.ts +3 -2
- package/src/cli/readable.ts +24 -5
- package/src/cli/vsix.ts +1 -0
- package/src/core/adapters.ts +54 -0
- package/src/core/config.ts +201 -64
- package/src/core/engine.ts +118 -218
- package/src/core/extensions.ts +144 -0
- package/src/core/index.ts +3 -0
- package/src/core/parse.ts +26 -2
- package/src/core/parsers.ts +22 -0
- package/src/core/schema-walk.ts +43 -0
- package/src/core/schemas.ts +21 -41
- package/src/core/types.ts +33 -6
- package/src/extensions/renovate.ts +4 -0
- package/src/lsp/hints.ts +22 -12
- package/src/lsp/server.ts +9 -2
- package/src/lsp/workspace.ts +49 -54
- package/src/runtime/session.ts +173 -0
- package/src/runtime/worker.ts +99 -0
- package/src/validators/ajv.ts +257 -0
- package/src/vscode/README.md +1 -1
- package/src/vscode/extension.ts +2 -4
- package/src/vscode/package.json +9 -2
- package/dist/engine-BgWBnb1A.mjs +0 -746
- package/dist/engine-BgWBnb1A.mjs.map +0 -1
- package/examples/basic/correctly.config.json +0 -10
- package/src/core/config.schema.json +0 -47
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli/readable.ts","../src/cli/check.ts","../src/cli/main.ts"],"sourcesContent":["import path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { styleText } from \"node:util\"\nimport type { Diagnostic, Failure, FileResult, Report } from \"../core/types.ts\"\nimport { diagnosticViews, type DiagnosticView } from \"../core/diagnostics.ts\"\n\nexport type ReadableOptions = {\n\tcwd?: string\n\tsources?: ReadonlyMap<string, string>\n\tcolor?: boolean\n}\n\n// Node's automatic color detection respects the output stream and NO_COLOR,\n// NODE_DISABLE_COLORS, and FORCE_COLOR. Use the same palette as Lasertag.\nfunction createStyler(color?: boolean) {\n\tconst apply = (format: Parameters<typeof styleText>[0], text: string) =>\n\t\tcolor === false\n\t\t\t? text\n\t\t\t: styleText(format, text, {\n\t\t\t\t\tstream: process.stdout,\n\t\t\t\t\t...(color === true ? { validateStream: false } : {}),\n\t\t\t\t})\n\treturn {\n\t\tbold: (text: string) => apply(\"bold\", text),\n\t\tbad: (text: string) => apply([\"bold\", \"red\"], text),\n\t\tcaret: (text: string) => apply([\"bold\", \"yellow\"], text),\n\t\tcode: (text: string) => apply(\"cyan\", text),\n\t\tdim: (text: string) => apply(\"dim\", text),\n\t\tsuccess: (text: string) => apply([\"bold\", \"green\"], text),\n\t\twarning: (text: string) => apply([\"bold\", \"yellow\"], text),\n\t}\n}\ntype Styler = ReturnType<typeof createStyler>\n\nfunction displayPath(cwd: string, file: string): string {\n\tconst relative = path.relative(cwd, file)\n\treturn relative &&\n\t\trelative !== \"..\" &&\n\t\t!relative.startsWith(`..${path.sep}`) &&\n\t\t!path.isAbsolute(relative)\n\t\t? relative\n\t\t: file\n}\n\nfunction displaySchema(cwd: string, uri: string): string {\n\tconst url = new URL(uri)\n\tif (url.protocol !== \"file:\") return uri\n\treturn `${displayPath(cwd, fileURLToPath(url))}${url.hash}`\n}\n\nfunction countLabel(count: number, label: string): string {\n\treturn `${count} ${label}${count === 1 ? \"\" : \"s\"}`\n}\n\nfunction lastSelectedLine(diagnostic: Diagnostic): number {\n\tconst { start, end } = diagnostic.range\n\treturn Math.max(\n\t\tstart.line,\n\t\tend.line - (end.line > start.line && end.character === 0 ? 1 : 0),\n\t)\n}\n\n// Like Lasertag, keep one context line on either side and collapse long ranges\n// to their first/last lines. Tabs are expanded equally in source and carets.\nfunction excerpt(\n\tsource: string[],\n\tdiagnostic: Diagnostic,\n\tdiagnosedLines: ReadonlySet<number>,\n\tstyler: Styler,\n) {\n\tconst start = diagnostic.range.start.line\n\tconst end = Math.min(lastSelectedLine(diagnostic), source.length - 1)\n\tif (start < 0 || start >= source.length) return undefined\n\tconst visible: (number | undefined)[] = []\n\tif (start > 0 && !diagnosedLines.has(start - 1)) visible.push(start - 1)\n\tvisible.push(start)\n\tif (end > start + 1) visible.push(undefined)\n\tif (end > start) visible.push(end)\n\tif (\n\t\tend + 1 < source.length &&\n\t\t!diagnosedLines.has(end + 1) &&\n\t\t(end + 1 < source.length - 1 || source[end + 1] !== \"\")\n\t)\n\t\tvisible.push(end + 1)\n\tconst width = Math.max(\n\t\t...visible.map((line) =>\n\t\t\tline === undefined ? 1 : String(line + 1).length,\n\t\t),\n\t)\n\tconst output: string[] = []\n\tfor (const line of visible) {\n\t\tif (line === undefined) {\n\t\t\toutput.push(styler.dim(`${\" \".repeat(width)} │ …`))\n\t\t\tcontinue\n\t\t}\n\t\tconst text = source[line]!\n\t\tconst selected = line >= start && line <= end\n\t\tconst expanded = text.replaceAll(\"\\t\", \" \").trimEnd()\n\t\toutput.push(\n\t\t\t`${styler.dim(`${String(line + 1).padStart(width)} │`)} ${selected ? expanded : styler.dim(expanded)}`,\n\t\t)\n\t\tif (!selected) continue\n\t\tconst from = line === start ? diagnostic.range.start.character : 0\n\t\tconst to =\n\t\t\tline === diagnostic.range.end.line\n\t\t\t\t? diagnostic.range.end.character\n\t\t\t\t: text.length\n\t\tconst caretStart = text.slice(0, from).replaceAll(\"\\t\", \" \").length\n\t\tconst caretWidth = Math.max(\n\t\t\t1,\n\t\t\ttext.slice(from, to).replaceAll(\"\\t\", \" \").length,\n\t\t)\n\t\toutput.push(\n\t\t\t`${styler.dim(`${\" \".repeat(width)} │`)} ${\" \".repeat(caretStart)}${styler.caret(\"^\".repeat(caretWidth))}`,\n\t\t)\n\t}\n\treturn { lines: output, width }\n}\n\ntype FileGroup = { file: string; result?: FileResult; failures: Failure[] }\n\nfunction renderDiagnostic(\n\tissue: DiagnosticView,\n\tprefix: string,\n\tlast: boolean,\n\tsource: string[] | undefined,\n\tdiagnosedLines: ReadonlySet<number>,\n\tstyler: Styler,\n\tshownRegions = new Set<string>(),\n): string[] {\n\tconst branch = last ? \"└─\" : \"├─\"\n\tconst continuation = `${prefix}${last ? \" \" : \"│ \"}`\n\tconst output = [\n\t\t`${styler.dim(`${prefix}${branch}`)} ${styler.dim(`${issue.range.start.line + 1}:${issue.range.start.character + 1}`)} ${styler.code(issue.code)}`,\n\t]\n\tconst region = source\n\t\t? excerpt(source, issue, diagnosedLines, styler)\n\t\t: undefined\n\tconst regionKey = `${issue.offset}:${issue.length}`\n\tif (region && !shownRegions.has(regionKey))\n\t\toutput.push(\n\t\t\t...region.lines.map((line) => `${styler.dim(continuation)}${line}`),\n\t\t)\n\tshownRegions.add(regionKey)\n\tconst indentation = `${continuation}${\" \".repeat(region ? region.width + 1 : 0)}`\n\toutput.push(\n\t\t`${styler.dim(continuation)}${\" \".repeat(region ? region.width + 1 : 0)}${styler.dim(\"╰─\")} ${issue.message}`,\n\t)\n\tfor (const [index, branch] of issue.branches.entries()) {\n\t\tconst finalBranch = index === issue.branches.length - 1\n\t\toutput.push(\n\t\t\t`${styler.dim(`${indentation}${finalBranch ? \"└─\" : \"├─\"}`)} ${styler.bold(branch.label)}`,\n\t\t)\n\t\tconst children = branch.diagnostics.toSorted((a, b) => a.offset - b.offset)\n\t\tfor (const [childIndex, child] of children.entries())\n\t\t\toutput.push(\n\t\t\t\t...renderDiagnostic(\n\t\t\t\t\tchild,\n\t\t\t\t\t`${indentation}${finalBranch ? \" \" : \"│ \"}`,\n\t\t\t\t\tchildIndex === children.length - 1,\n\t\t\t\t\tsource,\n\t\t\t\t\tdiagnosedLines,\n\t\t\t\t\tstyler,\n\t\t\t\t\tshownRegions,\n\t\t\t\t),\n\t\t\t)\n\t}\n\treturn output\n}\n\nfunction fileSection(\n\tgroup: FileGroup,\n\toptions: ReadableOptions,\n\tstyler: Styler,\n): string {\n\tconst cwd = options.cwd ?? process.cwd()\n\tconst diagnostics = diagnosticViews(group.result?.diagnostics ?? []).toSorted(\n\t\t(a, b) => a.offset - b.offset,\n\t)\n\tconst counts = [\n\t\t...(diagnostics.length ? [countLabel(diagnostics.length, \"error\")] : []),\n\t\t...(group.failures.length\n\t\t\t? [countLabel(group.failures.length, \"failure\")]\n\t\t\t: []),\n\t]\n\tconst status =\n\t\tcounts.join(\", \") ||\n\t\t(group.result?.coverage === \"excluded\" ? \"excluded\" : \"valid\")\n\tconst statusColor = counts.length\n\t\t? styler.bad\n\t\t: group.result?.coverage === \"excluded\"\n\t\t\t? styler.dim\n\t\t\t: styler.success\n\tlet header = `${styler.bold(displayPath(cwd, group.file))} ${statusColor(status)}`\n\tif (group.result) {\n\t\tconst { mode, coverage, association } = group.result\n\t\tconst format = mode.toUpperCase()\n\t\tconst schema = association?.schema\n\t\tconst description =\n\t\t\tcoverage === \"excluded\"\n\t\t\t\t? format\n\t\t\t\t: schema\n\t\t\t\t\t? `${format} against ${association.name} (${displaySchema(cwd, schema)})`\n\t\t\t\t\t: `${format} syntax only (${association ? `association: ${association.name}` : \"no schema association\"})`\n\t\theader += ` ${styler.dim(`· ${description}`)}`\n\t}\n\tconst output = [header]\n\tconst text = options.sources?.get(group.file)\n\tconst source = text === undefined ? undefined : text.split(/\\r\\n|\\r|\\n/)\n\tconst diagnosedLines = new Set<number>()\n\tif (source)\n\t\tfor (const diagnostic of group.result?.diagnostics ?? []) {\n\t\t\tfor (\n\t\t\t\tlet line = diagnostic.range.start.line;\n\t\t\t\tline <= Math.min(lastSelectedLine(diagnostic), source.length - 1);\n\t\t\t\tline++\n\t\t\t)\n\t\t\t\tdiagnosedLines.add(line)\n\t\t}\n\tconst issues: (DiagnosticView | Failure)[] = [\n\t\t...diagnostics,\n\t\t...group.failures,\n\t]\n\tfor (const [index, issue] of issues.entries()) {\n\t\tconst last = index === issues.length - 1\n\t\tconst branch = last ? \"└─\" : \"├─\"\n\t\tif (\"range\" in issue) {\n\t\t\toutput.push(\n\t\t\t\t...renderDiagnostic(issue, \"\", last, source, diagnosedLines, styler),\n\t\t\t)\n\t\t} else\n\t\t\toutput.push(\n\t\t\t\t`${styler.dim(branch)} ${styler.bad(issue.code)}: ${issue.message}`,\n\t\t\t)\n\t\tif (!last) output.push(styler.dim(\"│\"))\n\t}\n\treturn output.join(\"\\n\")\n}\n\nexport function readableReport(\n\treport: Report,\n\toptions: ReadableOptions = {},\n): string {\n\tconst styler = createStyler(options.color)\n\tconst groups = new Map<string, FileGroup>()\n\tfor (const result of report.files)\n\t\tgroups.set(result.file, {\n\t\t\tfile: result.file,\n\t\t\tresult,\n\t\t\tfailures: [...result.failures],\n\t\t})\n\tconst globalFailures: Failure[] = []\n\tfor (const error of report.failures) {\n\t\tif (!error.file) {\n\t\t\tglobalFailures.push(error)\n\t\t\tcontinue\n\t\t}\n\t\tlet group = groups.get(error.file)\n\t\tif (!group) {\n\t\t\tgroup = { file: error.file, failures: [] }\n\t\t\tgroups.set(error.file, group)\n\t\t}\n\t\tgroup.failures.push(error)\n\t}\n\tconst output = [...groups.values()]\n\t\t.toSorted((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0))\n\t\t.map((group) => fileSection(group, options, styler))\n\tfor (const error of globalFailures)\n\t\toutput.push(`${styler.bad(error.code)}: ${error.message}`)\n\tconst errors = report.files.reduce(\n\t\t(count, file) => count + diagnosticViews(file.diagnostics).length,\n\t\t0,\n\t)\n\tconst s = report.summary\n\tconst status =\n\t\treport.exitCode === 2\n\t\t\t? `▲ Check failed with ${countLabel(s.failures, \"failure\")}`\n\t\t\t: report.exitCode === 1\n\t\t\t\t? `▲ Check found ${countLabel(errors, \"error\")} in ${countLabel(s.invalid, \"file\")}`\n\t\t\t\t: \"✓ Check passed\"\n\toutput.push(\n\t\t[\n\t\t\tstyler.dim(\"─\".repeat(56)),\n\t\t\t\"\",\n\t\t\t(report.exitCode === 2\n\t\t\t\t? styler.bad\n\t\t\t\t: report.exitCode === 1\n\t\t\t\t\t? styler.warning\n\t\t\t\t\t: styler.success)(status),\n\t\t\t\"\",\n\t\t\t`${s.checked} checked, ${s.schemaCovered} schema-covered, ${s.syntaxOnly} syntax-only, ${s.invalid} invalid, ${s.failures} failures`,\n\t\t].join(\"\\n\"),\n\t)\n\treturn output.join(\"\\n\\n\")\n}\n","import { readFile } from \"node:fs/promises\"\nimport path from \"node:path\"\nimport { glob } from \"tinyglobby\"\nimport {\n\tdiscoverConfig,\n\texclusions,\n\tisIncluded,\n\tloadProject,\n} from \"../core/config.ts\"\nimport { Engine } from \"../core/engine.ts\"\nimport { failure, type Report } from \"../core/types.ts\"\n\nexport type CheckOptions = {\n\tcwd?: string\n\tconfig?: string\n\toffline?: boolean\n\tfiles?: string[]\n\tonRead?: (file: string, text: string) => void\n}\nexport async function check(options: CheckOptions = {}): Promise<Report> {\n\tconst report: Report = {\n\t\treportVersion: 1,\n\t\tconfig: null,\n\t\tfiles: [],\n\t\tfailures: [],\n\t\tsummary: {\n\t\t\tchecked: 0,\n\t\t\tschemaCovered: 0,\n\t\t\tsyntaxOnly: 0,\n\t\t\tinvalid: 0,\n\t\t\tfailures: 0,\n\t\t},\n\t\texitCode: 0,\n\t}\n\ttry {\n\t\tconst cwd = path.resolve(options.cwd ?? process.cwd())\n\t\tconst configPath = options.config\n\t\t\t? path.resolve(cwd, options.config)\n\t\t\t: await discoverConfig(cwd)\n\t\treport.config = configPath\n\t\tconst project = await loadProject(configPath)\n\t\tconst engine = new Engine(\n\t\t\tproject,\n\t\t\toptions.offline === undefined ? {} : { offline: options.offline },\n\t\t)\n\t\tawait engine.prepare()\n\t\tconst files = options.files?.length\n\t\t\t? options.files.map((f) => path.resolve(cwd, f))\n\t\t\t: await glob(project.config.files ?? [\"**/*.json\", \"**/*.jsonc\"], {\n\t\t\t\t\tcwd: project.root,\n\t\t\t\t\tignore: exclusions(project),\n\t\t\t\t\tabsolute: true,\n\t\t\t\t\tdot: true,\n\t\t\t\t\tonlyFiles: true,\n\t\t\t\t})\n\t\tfor (const file of [...new Set(files)].sort()) {\n\t\t\ttry {\n\t\t\t\tconst included = isIncluded(project, file)\n\t\t\t\tconst text = included ? await readFile(file, \"utf8\") : \"\"\n\t\t\t\tif (included) options.onRead?.(file, text)\n\t\t\t\treport.files.push(await engine.validate(file, text))\n\t\t\t} catch (error) {\n\t\t\t\treport.failures.push(failure(error, file))\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\treport.failures.push(failure(error))\n\t}\n\tconst checked = report.files.filter((f) => f.coverage !== \"excluded\")\n\treport.summary = {\n\t\tchecked: checked.length,\n\t\tschemaCovered: checked.filter((f) => f.coverage === \"schema\").length,\n\t\tsyntaxOnly: checked.filter((f) => f.coverage === \"syntax-only\").length,\n\t\tinvalid: checked.filter((f) => f.diagnostics.length > 0).length,\n\t\tfailures:\n\t\t\treport.failures.length +\n\t\t\treport.files.reduce((n, f) => n + f.failures.length, 0),\n\t}\n\treport.exitCode = report.summary.failures ? 2 : report.summary.invalid ? 1 : 0\n\treturn report\n}\n\nexport { readableReport } from \"./readable.ts\"\n","#!/usr/bin/env node\nimport { parseArgs } from \"node:util\"\nimport { check, readableReport } from \"./check.ts\"\nimport { failure, type Report } from \"../core/types.ts\"\n\nconst HELP = `correctly check [files...] [--config path] [--offline] [--format readable|json]\ncorrectly-lsp --stdio\n\nValidate JSON/JSONC using associations in correctly.config.json.\nExit 0: valid; 1: document errors; 2: configuration, schema, or execution errors.\nFormatting does not affect validation. See the package guide for configuration.\n`\n\nexport async function run(\n\targs: string[],\n\tcwd = process.cwd(),\n): Promise<{ output: string; exitCode: number }> {\n\tlet json =\n\t\targs.includes(\"--format=json\") ||\n\t\targs.some((arg, i) => arg === \"--format\" && args[i + 1] === \"json\")\n\ttry {\n\t\tconst { values, positionals } = parseArgs({\n\t\t\targs,\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: {\n\t\t\t\tconfig: { type: \"string\", short: \"c\" },\n\t\t\t\toffline: { type: \"boolean\" },\n\t\t\t\tformat: { type: \"string\", default: \"readable\" },\n\t\t\t\thelp: { type: \"boolean\", short: \"h\" },\n\t\t\t},\n\t\t})\n\t\tjson = values.format === \"json\"\n\t\tif (values.help || positionals.length === 0)\n\t\t\treturn { output: HELP, exitCode: 0 }\n\t\tif (positionals[0] !== \"check\")\n\t\t\tthrow new Error(`Unknown command: ${positionals[0]}`)\n\t\tif (values.format !== \"json\" && values.format !== \"readable\")\n\t\t\tthrow new Error(`Unknown report format: ${values.format}`)\n\t\tconst sources = new Map<string, string>()\n\t\tconst report = await check({\n\t\t\tcwd,\n\t\t\tfiles: positionals.slice(1),\n\t\t\t...(values.config ? { config: values.config } : {}),\n\t\t\t...(values.offline === undefined ? {} : { offline: values.offline }),\n\t\t\t...(json\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tonRead: (file: string, text: string) => {\n\t\t\t\t\t\t\tsources.set(file, text)\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t})\n\t\treturn {\n\t\t\toutput: json\n\t\t\t\t? JSON.stringify(report, null, 2)\n\t\t\t\t: readableReport(report, { cwd, sources }),\n\t\t\texitCode: report.exitCode,\n\t\t}\n\t} catch (error) {\n\t\tconst report: Report = {\n\t\t\treportVersion: 1,\n\t\t\tconfig: null,\n\t\t\tfiles: [],\n\t\t\tfailures: [failure(error)],\n\t\t\tsummary: {\n\t\t\t\tchecked: 0,\n\t\t\t\tschemaCovered: 0,\n\t\t\t\tsyntaxOnly: 0,\n\t\t\t\tinvalid: 0,\n\t\t\t\tfailures: 1,\n\t\t\t},\n\t\t\texitCode: 2,\n\t\t}\n\t\treturn {\n\t\t\toutput: json\n\t\t\t\t? JSON.stringify(report, null, 2)\n\t\t\t\t: readableReport(report, { cwd }),\n\t\t\texitCode: 2,\n\t\t}\n\t}\n}\n\nconst result = await run(process.argv.slice(2))\nprocess.stdout.write(`${result.output}\\n`)\nprocess.exitCode = result.exitCode\n"],"mappings":";;;;;;;;AAcA,SAAS,aAAa,OAAiB;CACtC,MAAM,SAAS,QAAyC,SACvD,UAAU,QACP,OACA,UAAU,QAAQ,MAAM;EACxB,QAAQ,QAAQ;EAChB,GAAI,UAAU,OAAO,EAAE,gBAAgB,MAAM,IAAI,CAAC;CACnD,CAAC;CACJ,OAAO;EACN,OAAO,SAAiB,MAAM,QAAQ,IAAI;EAC1C,MAAM,SAAiB,MAAM,CAAC,QAAQ,KAAK,GAAG,IAAI;EAClD,QAAQ,SAAiB,MAAM,CAAC,QAAQ,QAAQ,GAAG,IAAI;EACvD,OAAO,SAAiB,MAAM,QAAQ,IAAI;EAC1C,MAAM,SAAiB,MAAM,OAAO,IAAI;EACxC,UAAU,SAAiB,MAAM,CAAC,QAAQ,OAAO,GAAG,IAAI;EACxD,UAAU,SAAiB,MAAM,CAAC,QAAQ,QAAQ,GAAG,IAAI;CAC1D;AACD;AAGA,SAAS,YAAY,KAAa,MAAsB;CACvD,MAAM,WAAW,KAAK,SAAS,KAAK,IAAI;CACxC,OAAO,YACN,aAAa,QACb,CAAC,SAAS,WAAW,KAAK,KAAK,KAAK,KACpC,CAAC,KAAK,WAAW,QAAQ,IACvB,WACA;AACJ;AAEA,SAAS,cAAc,KAAa,KAAqB;CACxD,MAAM,MAAM,IAAI,IAAI,GAAG;CACvB,IAAI,IAAI,aAAa,SAAS,OAAO;CACrC,OAAO,GAAG,YAAY,KAAK,cAAc,GAAG,CAAC,IAAI,IAAI;AACtD;AAEA,SAAS,WAAW,OAAe,OAAuB;CACzD,OAAO,GAAG,MAAM,GAAG,QAAQ,UAAU,IAAI,KAAK;AAC/C;AAEA,SAAS,iBAAiB,YAAgC;CACzD,MAAM,EAAE,OAAO,QAAQ,WAAW;CAClC,OAAO,KAAK,IACX,MAAM,MACN,IAAI,QAAQ,IAAI,OAAO,MAAM,QAAQ,IAAI,cAAc,IAAI,IAAI,EAChE;AACD;AAIA,SAAS,QACR,QACA,YACA,gBACA,QACC;CACD,MAAM,QAAQ,WAAW,MAAM,MAAM;CACrC,MAAM,MAAM,KAAK,IAAI,iBAAiB,UAAU,GAAG,OAAO,SAAS,CAAC;CACpE,IAAI,QAAQ,KAAK,SAAS,OAAO,QAAQ,OAAO,KAAA;CAChD,MAAM,UAAkC,CAAC;CACzC,IAAI,QAAQ,KAAK,CAAC,eAAe,IAAI,QAAQ,CAAC,GAAG,QAAQ,KAAK,QAAQ,CAAC;CACvE,QAAQ,KAAK,KAAK;CAClB,IAAI,MAAM,QAAQ,GAAG,QAAQ,KAAK,KAAA,CAAS;CAC3C,IAAI,MAAM,OAAO,QAAQ,KAAK,GAAG;CACjC,IACC,MAAM,IAAI,OAAO,UACjB,CAAC,eAAe,IAAI,MAAM,CAAC,MAC1B,MAAM,IAAI,OAAO,SAAS,KAAK,OAAO,MAAM,OAAO,KAEpD,QAAQ,KAAK,MAAM,CAAC;CACrB,MAAM,QAAQ,KAAK,IAClB,GAAG,QAAQ,KAAK,SACf,SAAS,KAAA,IAAY,IAAI,OAAO,OAAO,CAAC,CAAC,CAAC,MAC3C,CACD;CACA,MAAM,SAAmB,CAAC;CAC1B,KAAK,MAAM,QAAQ,SAAS;EAC3B,IAAI,SAAS,KAAA,GAAW;GACvB,OAAO,KAAK,OAAO,IAAI,GAAG,IAAI,OAAO,KAAK,EAAE,KAAK,CAAC;GAClD;EACD;EACA,MAAM,OAAO,OAAO;EACpB,MAAM,WAAW,QAAQ,SAAS,QAAQ;EAC1C,MAAM,WAAW,KAAK,WAAW,KAAM,MAAM,CAAC,CAAC,QAAQ;EACvD,OAAO,KACN,GAAG,OAAO,IAAI,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,GAAG,EAAE,GAAG,WAAW,WAAW,OAAO,IAAI,QAAQ,GACpG;EACA,IAAI,CAAC,UAAU;EACf,MAAM,OAAO,SAAS,QAAQ,WAAW,MAAM,MAAM,YAAY;EACjE,MAAM,KACL,SAAS,WAAW,MAAM,IAAI,OAC3B,WAAW,MAAM,IAAI,YACrB,KAAK;EACT,MAAM,aAAa,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,WAAW,KAAM,MAAM,CAAC,CAAC;EAChE,MAAM,aAAa,KAAK,IACvB,GACA,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC,WAAW,KAAM,MAAM,CAAC,CAAC,MAC/C;EACA,OAAO,KACN,GAAG,OAAO,IAAI,GAAG,IAAI,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI,OAAO,UAAU,IAAI,OAAO,MAAM,IAAI,OAAO,UAAU,CAAC,GACxG;CACD;CACA,OAAO;EAAE,OAAO;EAAQ;CAAM;AAC/B;AAIA,SAAS,iBACR,OACA,QACA,MACA,QACA,gBACA,QACA,+BAAe,IAAI,IAAY,GACpB;CACX,MAAM,SAAS,OAAO,OAAO;CAC7B,MAAM,eAAe,GAAG,SAAS,OAAO,QAAQ;CAChD,MAAM,SAAS,CACd,GAAG,OAAO,IAAI,GAAG,SAAS,QAAQ,EAAE,GAAG,OAAO,IAAI,GAAG,MAAM,MAAM,MAAM,OAAO,EAAE,GAAG,MAAM,MAAM,MAAM,YAAY,GAAG,EAAE,IAAI,OAAO,KAAK,MAAM,IAAI,GACjJ;CACA,MAAM,SAAS,SACZ,QAAQ,QAAQ,OAAO,gBAAgB,MAAM,IAC7C,KAAA;CACH,MAAM,YAAY,GAAG,MAAM,OAAO,GAAG,MAAM;CAC3C,IAAI,UAAU,CAAC,aAAa,IAAI,SAAS,GACxC,OAAO,KACN,GAAG,OAAO,MAAM,KAAK,SAAS,GAAG,OAAO,IAAI,YAAY,IAAI,MAAM,CACnE;CACD,aAAa,IAAI,SAAS;CAC1B,MAAM,cAAc,GAAG,eAAe,IAAI,OAAO,SAAS,OAAO,QAAQ,IAAI,CAAC;CAC9E,OAAO,KACN,GAAG,OAAO,IAAI,YAAY,IAAI,IAAI,OAAO,SAAS,OAAO,QAAQ,IAAI,CAAC,IAAI,OAAO,IAAI,IAAI,EAAE,GAAG,MAAM,SACrG;CACA,KAAK,MAAM,CAAC,OAAO,WAAW,MAAM,SAAS,QAAQ,GAAG;EACvD,MAAM,cAAc,UAAU,MAAM,SAAS,SAAS;EACtD,OAAO,KACN,GAAG,OAAO,IAAI,GAAG,cAAc,cAAc,OAAO,MAAM,EAAE,GAAG,OAAO,KAAK,OAAO,KAAK,GACxF;EACA,MAAM,WAAW,OAAO,YAAY,UAAU,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;EAC1E,KAAK,MAAM,CAAC,YAAY,UAAU,SAAS,QAAQ,GAClD,OAAO,KACN,GAAG,iBACF,OACA,GAAG,cAAc,cAAc,QAAQ,SACvC,eAAe,SAAS,SAAS,GACjC,QACA,gBACA,QACA,YACD,CACD;CACF;CACA,OAAO;AACR;AAEA,SAAS,YACR,OACA,SACA,QACS;CACT,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CACvC,MAAM,cAAc,gBAAgB,MAAM,QAAQ,eAAe,CAAC,CAAC,CAAC,CAAC,UACnE,GAAG,MAAM,EAAE,SAAS,EAAE,MACxB;CACA,MAAM,SAAS,CACd,GAAI,YAAY,SAAS,CAAC,WAAW,YAAY,QAAQ,OAAO,CAAC,IAAI,CAAC,GACtE,GAAI,MAAM,SAAS,SAChB,CAAC,WAAW,MAAM,SAAS,QAAQ,SAAS,CAAC,IAC7C,CAAC,CACL;CACA,MAAM,SACL,OAAO,KAAK,IAAI,MACf,MAAM,QAAQ,aAAa,aAAa,aAAa;CACvD,MAAM,cAAc,OAAO,SACxB,OAAO,MACP,MAAM,QAAQ,aAAa,aAC1B,OAAO,MACP,OAAO;CACX,IAAI,SAAS,GAAG,OAAO,KAAK,YAAY,KAAK,MAAM,IAAI,CAAC,EAAE,IAAI,YAAY,MAAM;CAChF,IAAI,MAAM,QAAQ;EACjB,MAAM,EAAE,MAAM,UAAU,gBAAgB,MAAM;EAC9C,MAAM,SAAS,KAAK,YAAY;EAChC,MAAM,SAAS,aAAa;EAC5B,MAAM,cACL,aAAa,aACV,SACA,SACC,GAAG,OAAO,WAAW,YAAY,KAAK,IAAI,cAAc,KAAK,MAAM,EAAE,KACrE,GAAG,OAAO,gBAAgB,cAAc,gBAAgB,YAAY,SAAS,wBAAwB;EAC1G,UAAU,KAAK,OAAO,IAAI,MAAM,aAAa;CAC9C;CACA,MAAM,SAAS,CAAC,MAAM;CACtB,MAAM,OAAO,QAAQ,SAAS,IAAI,MAAM,IAAI;CAC5C,MAAM,SAAS,SAAS,KAAA,IAAY,KAAA,IAAY,KAAK,MAAM,YAAY;CACvE,MAAM,iCAAiB,IAAI,IAAY;CACvC,IAAI,QACH,KAAK,MAAM,cAAc,MAAM,QAAQ,eAAe,CAAC,GACtD,KACC,IAAI,OAAO,WAAW,MAAM,MAAM,MAClC,QAAQ,KAAK,IAAI,iBAAiB,UAAU,GAAG,OAAO,SAAS,CAAC,GAChE,QAEA,eAAe,IAAI,IAAI;CAE1B,MAAM,SAAuC,CAC5C,GAAG,aACH,GAAG,MAAM,QACV;CACA,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG;EAC9C,MAAM,OAAO,UAAU,OAAO,SAAS;EACvC,MAAM,SAAS,OAAO,OAAO;EAC7B,IAAI,WAAW,OACd,OAAO,KACN,GAAG,iBAAiB,OAAO,IAAI,MAAM,QAAQ,gBAAgB,MAAM,CACpE;OAEA,OAAO,KACN,GAAG,OAAO,IAAI,MAAM,EAAE,GAAG,OAAO,IAAI,MAAM,IAAI,EAAE,IAAI,MAAM,SAC3D;EACD,IAAI,CAAC,MAAM,OAAO,KAAK,OAAO,IAAI,GAAG,CAAC;CACvC;CACA,OAAO,OAAO,KAAK,IAAI;AACxB;AAEA,SAAgB,eACf,QACA,UAA2B,CAAC,GACnB;CACT,MAAM,SAAS,aAAa,QAAQ,KAAK;CACzC,MAAM,yBAAS,IAAI,IAAuB;CAC1C,KAAK,MAAM,UAAU,OAAO,OAC3B,OAAO,IAAI,OAAO,MAAM;EACvB,MAAM,OAAO;EACb;EACA,UAAU,CAAC,GAAG,OAAO,QAAQ;CAC9B,CAAC;CACF,MAAM,iBAA4B,CAAC;CACnC,KAAK,MAAM,SAAS,OAAO,UAAU;EACpC,IAAI,CAAC,MAAM,MAAM;GAChB,eAAe,KAAK,KAAK;GACzB;EACD;EACA,IAAI,QAAQ,OAAO,IAAI,MAAM,IAAI;EACjC,IAAI,CAAC,OAAO;GACX,QAAQ;IAAE,MAAM,MAAM;IAAM,UAAU,CAAC;GAAE;GACzC,OAAO,IAAI,MAAM,MAAM,KAAK;EAC7B;EACA,MAAM,SAAS,KAAK,KAAK;CAC1B;CACA,MAAM,SAAS,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CACjC,UAAU,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,CAAE,CAAC,CACpE,KAAK,UAAU,YAAY,OAAO,SAAS,MAAM,CAAC;CACpD,KAAK,MAAM,SAAS,gBACnB,OAAO,KAAK,GAAG,OAAO,IAAI,MAAM,IAAI,EAAE,IAAI,MAAM,SAAS;CAC1D,MAAM,SAAS,OAAO,MAAM,QAC1B,OAAO,SAAS,QAAQ,gBAAgB,KAAK,WAAW,CAAC,CAAC,QAC3D,CACD;CACA,MAAM,IAAI,OAAO;CACjB,MAAM,SACL,OAAO,aAAa,IACjB,uBAAuB,WAAW,EAAE,UAAU,SAAS,MACvD,OAAO,aAAa,IACnB,iBAAiB,WAAW,QAAQ,OAAO,EAAE,MAAM,WAAW,EAAE,SAAS,MAAM,MAC/E;CACL,OAAO,KACN;EACC,OAAO,IAAI,IAAI,OAAO,EAAE,CAAC;EACzB;GACC,OAAO,aAAa,IAClB,OAAO,MACP,OAAO,aAAa,IACnB,OAAO,UACP,OAAO,QAAA,CAAS,MAAM;EAC1B;EACA,GAAG,EAAE,QAAQ,YAAY,EAAE,cAAc,mBAAmB,EAAE,WAAW,gBAAgB,EAAE,QAAQ,YAAY,EAAE,SAAS;CAC3H,CAAC,CAAC,KAAK,IAAI,CACZ;CACA,OAAO,OAAO,KAAK,MAAM;AAC1B;;;ACnRA,eAAsB,MAAM,UAAwB,CAAC,GAAoB;CACxE,MAAM,SAAiB;EACtB,eAAe;EACf,QAAQ;EACR,OAAO,CAAC;EACR,UAAU,CAAC;EACX,SAAS;GACR,SAAS;GACT,eAAe;GACf,YAAY;GACZ,SAAS;GACT,UAAU;EACX;EACA,UAAU;CACX;CACA,IAAI;EACH,MAAM,MAAM,KAAK,QAAQ,QAAQ,OAAO,QAAQ,IAAI,CAAC;EACrD,MAAM,aAAa,QAAQ,SACxB,KAAK,QAAQ,KAAK,QAAQ,MAAM,IAChC,MAAM,eAAe,GAAG;EAC3B,OAAO,SAAS;EAChB,MAAM,UAAU,MAAM,YAAY,UAAU;EAC5C,MAAM,SAAS,IAAI,OAClB,SACA,QAAQ,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,QAAQ,QAAQ,CACjE;EACA,MAAM,OAAO,QAAQ;EACrB,MAAM,QAAQ,QAAQ,OAAO,SAC1B,QAAQ,MAAM,KAAK,MAAM,KAAK,QAAQ,KAAK,CAAC,CAAC,IAC7C,MAAM,KAAK,QAAQ,OAAO,SAAS,CAAC,aAAa,YAAY,GAAG;GAChE,KAAK,QAAQ;GACb,QAAQ,WAAW,OAAO;GAC1B,UAAU;GACV,KAAK;GACL,WAAW;EACZ,CAAC;EACH,KAAK,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAC3C,IAAI;GACH,MAAM,WAAW,WAAW,SAAS,IAAI;GACzC,MAAM,OAAO,WAAW,MAAM,SAAS,MAAM,MAAM,IAAI;GACvD,IAAI,UAAU,QAAQ,SAAS,MAAM,IAAI;GACzC,OAAO,MAAM,KAAK,MAAM,OAAO,SAAS,MAAM,IAAI,CAAC;EACpD,SAAS,OAAO;GACf,OAAO,SAAS,KAAK,QAAQ,OAAO,IAAI,CAAC;EAC1C;CAEF,SAAS,OAAO;EACf,OAAO,SAAS,KAAK,QAAQ,KAAK,CAAC;CACpC;CACA,MAAM,UAAU,OAAO,MAAM,QAAQ,MAAM,EAAE,aAAa,UAAU;CACpE,OAAO,UAAU;EAChB,SAAS,QAAQ;EACjB,eAAe,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ,CAAC,CAAC;EAC9D,YAAY,QAAQ,QAAQ,MAAM,EAAE,aAAa,aAAa,CAAC,CAAC;EAChE,SAAS,QAAQ,QAAQ,MAAM,EAAE,YAAY,SAAS,CAAC,CAAC,CAAC;EACzD,UACC,OAAO,SAAS,SAChB,OAAO,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;CACxD;CACA,OAAO,WAAW,OAAO,QAAQ,WAAW,IAAI,OAAO,QAAQ,UAAU,IAAI;CAC7E,OAAO;AACR;;;AC3EA,MAAM,OAAO;;;;;;;AAQb,eAAsB,IACrB,MACA,MAAM,QAAQ,IAAI,GAC8B;CAChD,IAAI,OACH,KAAK,SAAS,eAAe,KAC7B,KAAK,MAAM,KAAK,MAAM,QAAQ,cAAc,KAAK,IAAI,OAAO,MAAM;CACnE,IAAI;EACH,MAAM,EAAE,QAAQ,gBAAgB,UAAU;GACzC;GACA,kBAAkB;GAClB,QAAQ;GACR,SAAS;IACR,QAAQ;KAAE,MAAM;KAAU,OAAO;IAAI;IACrC,SAAS,EAAE,MAAM,UAAU;IAC3B,QAAQ;KAAE,MAAM;KAAU,SAAS;IAAW;IAC9C,MAAM;KAAE,MAAM;KAAW,OAAO;IAAI;GACrC;EACD,CAAC;EACD,OAAO,OAAO,WAAW;EACzB,IAAI,OAAO,QAAQ,YAAY,WAAW,GACzC,OAAO;GAAE,QAAQ;GAAM,UAAU;EAAE;EACpC,IAAI,YAAY,OAAO,SACtB,MAAM,IAAI,MAAM,oBAAoB,YAAY,IAAI;EACrD,IAAI,OAAO,WAAW,UAAU,OAAO,WAAW,YACjD,MAAM,IAAI,MAAM,0BAA0B,OAAO,QAAQ;EAC1D,MAAM,0BAAU,IAAI,IAAoB;EACxC,MAAM,SAAS,MAAM,MAAM;GAC1B;GACA,OAAO,YAAY,MAAM,CAAC;GAC1B,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;GACjD,GAAI,OAAO,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,OAAO,QAAQ;GAClE,GAAI,OACD,CAAC,IACD,EACA,SAAS,MAAc,SAAiB;IACvC,QAAQ,IAAI,MAAM,IAAI;GACvB,EACD;EACH,CAAC;EACD,OAAO;GACN,QAAQ,OACL,KAAK,UAAU,QAAQ,MAAM,CAAC,IAC9B,eAAe,QAAQ;IAAE;IAAK;GAAQ,CAAC;GAC1C,UAAU,OAAO;EAClB;CACD,SAAS,OAAO;EACf,MAAM,SAAiB;GACtB,eAAe;GACf,QAAQ;GACR,OAAO,CAAC;GACR,UAAU,CAAC,QAAQ,KAAK,CAAC;GACzB,SAAS;IACR,SAAS;IACT,eAAe;IACf,YAAY;IACZ,SAAS;IACT,UAAU;GACX;GACA,UAAU;EACX;EACA,OAAO;GACN,QAAQ,OACL,KAAK,UAAU,QAAQ,MAAM,CAAC,IAC9B,eAAe,QAAQ,EAAE,IAAI,CAAC;GACjC,UAAU;EACX;CACD;AACD;AAEA,MAAM,SAAS,MAAM,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC9C,QAAQ,OAAO,MAAM,GAAG,OAAO,OAAO,GAAG;AACzC,QAAQ,WAAW,OAAO"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli/readable.ts","../src/cli/main.ts"],"sourcesContent":["import path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { styleText } from \"node:util\"\nimport type { Diagnostic, Failure, FileResult, Report } from \"../core/types.ts\"\nimport { diagnosticViews, type DiagnosticView } from \"../core/diagnostics.ts\"\n\nexport type ReadableOptions = {\n\tcwd?: string\n\tsources?: ReadonlyMap<string, string>\n\tcolor?: boolean\n}\n\n// Node's automatic color detection respects the output stream and NO_COLOR,\n// NODE_DISABLE_COLORS, and FORCE_COLOR. Use the same palette as Lasertag.\nfunction createStyler(color?: boolean) {\n\tconst apply = (format: Parameters<typeof styleText>[0], text: string) =>\n\t\tcolor === false\n\t\t\t? text\n\t\t\t: styleText(format, text, {\n\t\t\t\t\tstream: process.stdout,\n\t\t\t\t\t...(color === true ? { validateStream: false } : {}),\n\t\t\t\t})\n\treturn {\n\t\tbold: (text: string) => apply(\"bold\", text),\n\t\tbad: (text: string) => apply([\"bold\", \"red\"], text),\n\t\tcaret: (text: string) => apply([\"bold\", \"yellow\"], text),\n\t\tcode: (text: string) => apply(\"cyan\", text),\n\t\tdim: (text: string) => apply(\"dim\", text),\n\t\tsuccess: (text: string) => apply([\"bold\", \"green\"], text),\n\t\twarning: (text: string) => apply([\"bold\", \"yellow\"], text),\n\t}\n}\ntype Styler = ReturnType<typeof createStyler>\n\nfunction displayPath(cwd: string, file: string): string {\n\tconst relative = path.relative(cwd, file)\n\treturn relative &&\n\t\trelative !== \"..\" &&\n\t\t!relative.startsWith(`..${path.sep}`) &&\n\t\t!path.isAbsolute(relative)\n\t\t? relative\n\t\t: file\n}\n\nfunction displaySchema(cwd: string, uri: string): string {\n\tconst url = new URL(uri)\n\tif (url.protocol !== \"file:\") return uri\n\treturn `${displayPath(cwd, fileURLToPath(url))}${url.hash}`\n}\n\nfunction countLabel(count: number, label: string): string {\n\treturn `${count} ${label}${count === 1 ? \"\" : \"s\"}`\n}\n\nfunction lastSelectedLine(diagnostic: Diagnostic): number {\n\tconst { start, end } = diagnostic.range\n\treturn Math.max(\n\t\tstart.line,\n\t\tend.line - (end.line > start.line && end.character === 0 ? 1 : 0),\n\t)\n}\n\n// Like Lasertag, keep one context line on either side and collapse long ranges\n// to their first/last lines. Tabs are expanded equally in source and carets.\nfunction excerpt(\n\tsource: string[],\n\tdiagnostic: Diagnostic,\n\tdiagnosedLines: ReadonlySet<number>,\n\tstyler: Styler,\n) {\n\tconst start = diagnostic.range.start.line\n\tconst end = Math.min(lastSelectedLine(diagnostic), source.length - 1)\n\tif (start < 0 || start >= source.length) return undefined\n\tconst visible: (number | undefined)[] = []\n\tif (start > 0 && !diagnosedLines.has(start - 1)) visible.push(start - 1)\n\tvisible.push(start)\n\tif (end > start + 1) visible.push(undefined)\n\tif (end > start) visible.push(end)\n\tif (\n\t\tend + 1 < source.length &&\n\t\t!diagnosedLines.has(end + 1) &&\n\t\t(end + 1 < source.length - 1 || source[end + 1] !== \"\")\n\t)\n\t\tvisible.push(end + 1)\n\tconst width = Math.max(\n\t\t...visible.map((line) =>\n\t\t\tline === undefined ? 1 : String(line + 1).length,\n\t\t),\n\t)\n\tconst output: string[] = []\n\tfor (const line of visible) {\n\t\tif (line === undefined) {\n\t\t\toutput.push(styler.dim(`${\" \".repeat(width)} │ …`))\n\t\t\tcontinue\n\t\t}\n\t\tconst text = source[line]!\n\t\tconst selected = line >= start && line <= end\n\t\tconst expanded = text.replaceAll(\"\\t\", \" \").trimEnd()\n\t\toutput.push(\n\t\t\t`${styler.dim(`${String(line + 1).padStart(width)} │`)} ${selected ? expanded : styler.dim(expanded)}`,\n\t\t)\n\t\tif (!selected) continue\n\t\tconst from = line === start ? diagnostic.range.start.character : 0\n\t\tconst to =\n\t\t\tline === diagnostic.range.end.line\n\t\t\t\t? diagnostic.range.end.character\n\t\t\t\t: text.length\n\t\tconst caretStart = text.slice(0, from).replaceAll(\"\\t\", \" \").length\n\t\tconst caretWidth = Math.max(\n\t\t\t1,\n\t\t\ttext.slice(from, to).replaceAll(\"\\t\", \" \").length,\n\t\t)\n\t\toutput.push(\n\t\t\t`${styler.dim(`${\" \".repeat(width)} │`)} ${\" \".repeat(caretStart)}${styler.caret(\"^\".repeat(caretWidth))}`,\n\t\t)\n\t}\n\treturn { lines: output, width }\n}\n\nfunction renderFailure(\n\terror: Failure,\n\tstyler: Styler,\n\tprefix = \"\",\n\tcontinuation = \"\",\n): string {\n\tconst [title, ...details] = error.message.split(\"\\n\")\n\treturn [\n\t\t`${prefix}${styler.bad(error.code)}: ${title}`,\n\t\t...details.map((line) => `${styler.dim(continuation + \"│\")} ${line}`),\n\t].join(\"\\n\")\n}\n\ntype FileGroup = { file: string; result?: FileResult; failures: Failure[] }\n\nfunction renderDiagnostic(\n\tissue: DiagnosticView,\n\tprefix: string,\n\tlast: boolean,\n\tsource: string[] | undefined,\n\tdiagnosedLines: ReadonlySet<number>,\n\tstyler: Styler,\n\tshownRegions = new Set<string>(),\n): string[] {\n\tconst branch = last ? \"└─\" : \"├─\"\n\tconst continuation = `${prefix}${last ? \" \" : \"│ \"}`\n\tconst output = [\n\t\t`${styler.dim(`${prefix}${branch}`)} ${styler.dim(`${issue.range.start.line + 1}:${issue.range.start.character + 1}`)} ${styler.code(issue.code)}`,\n\t]\n\tconst region = source\n\t\t? excerpt(source, issue, diagnosedLines, styler)\n\t\t: undefined\n\tconst regionKey = `${issue.offset}:${issue.length}`\n\tif (region && !shownRegions.has(regionKey))\n\t\toutput.push(\n\t\t\t...region.lines.map((line) => `${styler.dim(continuation)}${line}`),\n\t\t)\n\tshownRegions.add(regionKey)\n\tconst indentation = `${continuation}${\" \".repeat(region ? region.width + 1 : 0)}`\n\toutput.push(\n\t\t`${styler.dim(continuation)}${\" \".repeat(region ? region.width + 1 : 0)}${styler.dim(\"╰─\")} ${issue.message}`,\n\t)\n\tfor (const [index, branch] of issue.branches.entries()) {\n\t\tconst finalBranch = index === issue.branches.length - 1\n\t\toutput.push(\n\t\t\t`${styler.dim(`${indentation}${finalBranch ? \"└─\" : \"├─\"}`)} ${styler.bold(branch.label)}`,\n\t\t)\n\t\tconst children = branch.diagnostics.toSorted((a, b) => a.offset - b.offset)\n\t\tfor (const [childIndex, child] of children.entries())\n\t\t\toutput.push(\n\t\t\t\t...renderDiagnostic(\n\t\t\t\t\tchild,\n\t\t\t\t\t`${indentation}${finalBranch ? \" \" : \"│ \"}`,\n\t\t\t\t\tchildIndex === children.length - 1,\n\t\t\t\t\tsource,\n\t\t\t\t\tdiagnosedLines,\n\t\t\t\t\tstyler,\n\t\t\t\t\tshownRegions,\n\t\t\t\t),\n\t\t\t)\n\t}\n\treturn output\n}\n\nfunction fileSection(\n\tgroup: FileGroup,\n\toptions: ReadableOptions,\n\tstyler: Styler,\n): string {\n\tconst cwd = options.cwd ?? process.cwd()\n\tconst diagnostics = diagnosticViews(group.result?.diagnostics ?? []).toSorted(\n\t\t(a, b) => a.offset - b.offset,\n\t)\n\tconst counts = [\n\t\t...(diagnostics.length ? [countLabel(diagnostics.length, \"error\")] : []),\n\t\t...(group.failures.length\n\t\t\t? [countLabel(group.failures.length, \"failure\")]\n\t\t\t: []),\n\t]\n\tconst status =\n\t\tcounts.join(\", \") ||\n\t\t(group.result?.coverage === \"excluded\" ? \"excluded\" : \"valid\")\n\tconst statusColor = counts.length\n\t\t? styler.bad\n\t\t: group.result?.coverage === \"excluded\"\n\t\t\t? styler.dim\n\t\t\t: styler.success\n\tlet header = `${styler.bold(displayPath(cwd, group.file))} ${statusColor(status)}`\n\tif (group.result) {\n\t\tconst { mode, coverage, association } = group.result\n\t\tconst format = mode.toUpperCase()\n\t\tconst schema = association?.schema\n\t\tconst description =\n\t\t\tcoverage === \"excluded\"\n\t\t\t\t? format\n\t\t\t\t: schema\n\t\t\t\t\t? `${format} against ${association.name} (${displaySchema(cwd, schema)})`\n\t\t\t\t\t: association?.validator\n\t\t\t\t\t\t? `${format} validated by ${association.validator}`\n\t\t\t\t\t\t: `${format} syntax only (${association ? `association: ${association.name}` : \"no schema association\"})`\n\t\theader += ` ${styler.dim(`· ${description}`)}`\n\t}\n\tconst output = [header]\n\tconst text = options.sources?.get(group.file)\n\tconst source = text === undefined ? undefined : text.split(/\\r\\n|\\r|\\n/)\n\tconst diagnosedLines = new Set<number>()\n\tif (source)\n\t\tfor (const diagnostic of group.result?.diagnostics ?? []) {\n\t\t\tfor (\n\t\t\t\tlet line = diagnostic.range.start.line;\n\t\t\t\tline <= Math.min(lastSelectedLine(diagnostic), source.length - 1);\n\t\t\t\tline++\n\t\t\t)\n\t\t\t\tdiagnosedLines.add(line)\n\t\t}\n\tconst issues: (DiagnosticView | Failure)[] = [\n\t\t...diagnostics,\n\t\t...group.failures,\n\t]\n\tfor (const [index, issue] of issues.entries()) {\n\t\tconst last = index === issues.length - 1\n\t\tconst branch = last ? \"└─\" : \"├─\"\n\t\tif (\"range\" in issue) {\n\t\t\toutput.push(\n\t\t\t\t...renderDiagnostic(issue, \"\", last, source, diagnosedLines, styler),\n\t\t\t)\n\t\t} else\n\t\t\toutput.push(\n\t\t\t\trenderFailure(\n\t\t\t\t\tissue,\n\t\t\t\t\tstyler,\n\t\t\t\t\t`${styler.dim(branch)} `,\n\t\t\t\t\tlast ? \" \" : \"│ \",\n\t\t\t\t),\n\t\t\t)\n\t\tif (!last) output.push(styler.dim(\"│\"))\n\t}\n\treturn output.join(\"\\n\")\n}\n\nexport function readableReport(\n\treport: Report,\n\toptions: ReadableOptions = {},\n): string {\n\tconst styler = createStyler(options.color)\n\tconst groups = new Map<string, FileGroup>()\n\tfor (const result of report.files)\n\t\tgroups.set(result.file, {\n\t\t\tfile: result.file,\n\t\t\tresult,\n\t\t\tfailures: [...result.failures],\n\t\t})\n\tconst globalFailures: Failure[] = []\n\tfor (const error of report.failures) {\n\t\tif (!error.file) {\n\t\t\tglobalFailures.push(error)\n\t\t\tcontinue\n\t\t}\n\t\tlet group = groups.get(error.file)\n\t\tif (!group) {\n\t\t\tgroup = { file: error.file, failures: [] }\n\t\t\tgroups.set(error.file, group)\n\t\t}\n\t\tgroup.failures.push(error)\n\t}\n\tconst output = [...groups.values()]\n\t\t.toSorted((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0))\n\t\t.map((group) => fileSection(group, options, styler))\n\tfor (const error of globalFailures) output.push(renderFailure(error, styler))\n\tconst errors = report.files.reduce(\n\t\t(count, file) => count + diagnosticViews(file.diagnostics).length,\n\t\t0,\n\t)\n\tconst s = report.summary\n\tconst status =\n\t\treport.exitCode === 2\n\t\t\t? `▲ Check failed with ${countLabel(s.failures, \"failure\")}`\n\t\t\t: report.exitCode === 1\n\t\t\t\t? `▲ Check found ${countLabel(errors, \"error\")} in ${countLabel(s.invalid, \"file\")}`\n\t\t\t\t: \"✓ Check passed\"\n\toutput.push(\n\t\t[\n\t\t\tstyler.dim(\"─\".repeat(56)),\n\t\t\t\"\",\n\t\t\t(report.exitCode === 2\n\t\t\t\t? styler.bad\n\t\t\t\t: report.exitCode === 1\n\t\t\t\t\t? styler.warning\n\t\t\t\t\t: styler.success)(status),\n\t\t\t\"\",\n\t\t\t`${s.checked} checked, ${s.validated} validated, ${s.schemaCovered} schema-covered, ${s.syntaxOnly} syntax-only, ${s.invalid} invalid, ${s.failures} failures`,\n\t\t].join(\"\\n\"),\n\t)\n\treturn output.join(\"\\n\\n\")\n}\n","#!/usr/bin/env node\nimport { parseArgs } from \"node:util\"\nimport { check, readableReport } from \"./check.ts\"\nimport { failure, type Report } from \"../core/types.ts\"\n\nconst HELP = `correctly check [files...] [--config path] [--offline] [--format readable|json]\ncorrectly-lsp --stdio\n\nValidate JSON/JSONC using associations in correctly.config.ts.\nExit 0: valid; 1: document errors; 2: configuration, schema, or execution errors.\nFormatting does not affect validation. See the package guide for configuration.\n`\n\nexport async function run(\n\targs: string[],\n\tcwd = process.cwd(),\n): Promise<{ output: string; exitCode: number }> {\n\tlet json =\n\t\targs.includes(\"--format=json\") ||\n\t\targs.some((arg, i) => arg === \"--format\" && args[i + 1] === \"json\")\n\ttry {\n\t\tconst { values, positionals } = parseArgs({\n\t\t\targs,\n\t\t\tallowPositionals: true,\n\t\t\tstrict: true,\n\t\t\toptions: {\n\t\t\t\tconfig: { type: \"string\", short: \"c\" },\n\t\t\t\toffline: { type: \"boolean\" },\n\t\t\t\tformat: { type: \"string\", default: \"readable\" },\n\t\t\t\thelp: { type: \"boolean\", short: \"h\" },\n\t\t\t},\n\t\t})\n\t\tjson = values.format === \"json\"\n\t\tif (values.help || positionals.length === 0)\n\t\t\treturn { output: HELP, exitCode: 0 }\n\t\tif (positionals[0] !== \"check\")\n\t\t\tthrow new Error(`Unknown command: ${positionals[0]}`)\n\t\tif (values.format !== \"json\" && values.format !== \"readable\")\n\t\t\tthrow new Error(`Unknown report format: ${values.format}`)\n\t\tconst sources = new Map<string, string>()\n\t\tconst report = await check({\n\t\t\tcwd,\n\t\t\tfiles: positionals.slice(1),\n\t\t\t...(values.config ? { config: values.config } : {}),\n\t\t\t...(values.offline === undefined ? {} : { offline: values.offline }),\n\t\t\t...(json\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tonRead: (file: string, text: string) => {\n\t\t\t\t\t\t\tsources.set(file, text)\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t})\n\t\treturn {\n\t\t\toutput: json\n\t\t\t\t? JSON.stringify(report, null, 2)\n\t\t\t\t: readableReport(report, { cwd, sources }),\n\t\t\texitCode: report.exitCode,\n\t\t}\n\t} catch (error) {\n\t\tconst report: Report = {\n\t\t\treportVersion: 2,\n\t\t\tconfig: null,\n\t\t\tfiles: [],\n\t\t\tfailures: [failure(error)],\n\t\t\tsummary: {\n\t\t\t\tchecked: 0,\n\t\t\t\tvalidated: 0,\n\t\t\t\tschemaCovered: 0,\n\t\t\t\tsyntaxOnly: 0,\n\t\t\t\tinvalid: 0,\n\t\t\t\tfailures: 1,\n\t\t\t},\n\t\t\texitCode: 2,\n\t\t}\n\t\treturn {\n\t\t\toutput: json\n\t\t\t\t? JSON.stringify(report, null, 2)\n\t\t\t\t: readableReport(report, { cwd }),\n\t\t\texitCode: 2,\n\t\t}\n\t}\n}\n\nconst result = await run(process.argv.slice(2))\nprocess.stdout.write(`${result.output}\\n`)\nprocess.exitCode = result.exitCode\n"],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Node } from "jsonc-parser";
|
|
2
|
+
//#region src/core/types.d.ts
|
|
3
|
+
type Mode = string;
|
|
4
|
+
type Position = {
|
|
5
|
+
line: number;
|
|
6
|
+
character: number;
|
|
7
|
+
};
|
|
8
|
+
type Range = {
|
|
9
|
+
start: Position;
|
|
10
|
+
end: Position;
|
|
11
|
+
};
|
|
12
|
+
type Diagnostic = {
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
pointer: string;
|
|
16
|
+
offset: number;
|
|
17
|
+
length: number;
|
|
18
|
+
range: Range;
|
|
19
|
+
context?: {
|
|
20
|
+
parent: number;
|
|
21
|
+
label: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type ExtensionRequirement = {
|
|
25
|
+
schemaUri: string;
|
|
26
|
+
schemaPointer: string;
|
|
27
|
+
format: string;
|
|
28
|
+
suggestedExtension?: string;
|
|
29
|
+
};
|
|
30
|
+
type Failure = {
|
|
31
|
+
code: string;
|
|
32
|
+
message: string;
|
|
33
|
+
file?: string;
|
|
34
|
+
details?: ExtensionRequirement;
|
|
35
|
+
};
|
|
36
|
+
declare class CorrectlyError extends Error {
|
|
37
|
+
readonly code: string;
|
|
38
|
+
readonly details?: ExtensionRequirement;
|
|
39
|
+
constructor(code: string, message: string, details?: ExtensionRequirement);
|
|
40
|
+
}
|
|
41
|
+
declare function isCorrectlyError(error: unknown): error is CorrectlyError;
|
|
42
|
+
declare function failure(error: unknown, file?: string): Failure;
|
|
43
|
+
type Association = {
|
|
44
|
+
index: number;
|
|
45
|
+
name: string;
|
|
46
|
+
schema: string | null;
|
|
47
|
+
validator: string | null;
|
|
48
|
+
mode: Mode;
|
|
49
|
+
extensions?: string[];
|
|
50
|
+
};
|
|
51
|
+
type FileResult = {
|
|
52
|
+
file: string;
|
|
53
|
+
mode: Mode;
|
|
54
|
+
coverage: "schema" | "validated" | "syntax-only" | "excluded";
|
|
55
|
+
association: Association | null;
|
|
56
|
+
diagnostics: Diagnostic[];
|
|
57
|
+
failures: Failure[];
|
|
58
|
+
};
|
|
59
|
+
type Report = {
|
|
60
|
+
reportVersion: 2;
|
|
61
|
+
config: string | null;
|
|
62
|
+
files: FileResult[];
|
|
63
|
+
failures: Failure[];
|
|
64
|
+
summary: {
|
|
65
|
+
checked: number;
|
|
66
|
+
validated: number;
|
|
67
|
+
schemaCovered: number;
|
|
68
|
+
syntaxOnly: number;
|
|
69
|
+
invalid: number;
|
|
70
|
+
failures: number;
|
|
71
|
+
};
|
|
72
|
+
exitCode: 0 | 1 | 2;
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/core/parse.d.ts
|
|
76
|
+
type SourceSpan = {
|
|
77
|
+
offset: number;
|
|
78
|
+
length: number;
|
|
79
|
+
};
|
|
80
|
+
type ParsedDocument = {
|
|
81
|
+
value: unknown;
|
|
82
|
+
diagnostics: Diagnostic[];
|
|
83
|
+
locate: (pointer: string, key?: boolean) => SourceSpan | undefined;
|
|
84
|
+
/** Exact numeric lexeme when the parser can preserve it. */
|
|
85
|
+
rawNumber?: (pointer: string) => string | undefined;
|
|
86
|
+
};
|
|
87
|
+
type ParsedJsonDocument = ParsedDocument & {
|
|
88
|
+
root: Node | undefined;
|
|
89
|
+
};
|
|
90
|
+
declare function pointer(parts: readonly (string | number)[]): string;
|
|
91
|
+
declare function positionAt(text: string, offset: number): Position;
|
|
92
|
+
declare function diagnostic(text: string, code: string, message: string, jsonPointer?: string, offset?: number, length?: number): Diagnostic;
|
|
93
|
+
declare function parseDocument(text: string, mode: "json" | "jsonc"): ParsedJsonDocument;
|
|
94
|
+
declare function locate(root: Node | undefined, jsonPointer: string, key?: boolean): Node | undefined;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/core/adapters.d.ts
|
|
97
|
+
type Parser = {
|
|
98
|
+
id: string;
|
|
99
|
+
/** The parsed value representation, not the source file extension. */
|
|
100
|
+
valueModel: string;
|
|
101
|
+
editorLanguage?: "json" | "jsonc";
|
|
102
|
+
parse(text: string, context: {
|
|
103
|
+
file: string;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}): ParsedDocument | Promise<ParsedDocument>;
|
|
106
|
+
};
|
|
107
|
+
type ValidationContext = {
|
|
108
|
+
root: string;
|
|
109
|
+
configPath: string;
|
|
110
|
+
remote?: RemoteOptions;
|
|
111
|
+
read: ReadText;
|
|
112
|
+
fetch?: typeof fetch;
|
|
113
|
+
signal?: AbortSignal;
|
|
114
|
+
/** Project-scoped services; use a versioned Symbol.for key for cross-package sharing. */
|
|
115
|
+
services: Map<symbol, unknown>;
|
|
116
|
+
/** Register a file or URI which affects this adapter's prepared state. */
|
|
117
|
+
watch(uri: string): void;
|
|
118
|
+
};
|
|
119
|
+
type ValidationInput = {
|
|
120
|
+
file: string;
|
|
121
|
+
text: string;
|
|
122
|
+
parsed: ParsedDocument;
|
|
123
|
+
signal?: AbortSignal;
|
|
124
|
+
};
|
|
125
|
+
type JsonSchemaEditorSupport = {
|
|
126
|
+
kind: "json-schema";
|
|
127
|
+
uri: string;
|
|
128
|
+
readSchema(uri: string): Promise<string>;
|
|
129
|
+
};
|
|
130
|
+
type PreparedValidator = {
|
|
131
|
+
validate(input: ValidationInput): Diagnostic[] | Promise<Diagnostic[]>;
|
|
132
|
+
editor?: JsonSchemaEditorSupport;
|
|
133
|
+
dispose?(): void | Promise<void>;
|
|
134
|
+
};
|
|
135
|
+
type Validator = {
|
|
136
|
+
id: string;
|
|
137
|
+
accepts: readonly string[];
|
|
138
|
+
/** Optional JSON Schema location for coverage reporting. */
|
|
139
|
+
schema?: string;
|
|
140
|
+
extensions?: readonly {
|
|
141
|
+
readonly id: string;
|
|
142
|
+
}[];
|
|
143
|
+
/** Register resources before any association is prepared (e.g. schema IDs). */
|
|
144
|
+
register?(context: ValidationContext): void | Promise<void>;
|
|
145
|
+
prepare(context: ValidationContext): PreparedValidator | Promise<PreparedValidator>;
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/core/config.d.ts
|
|
149
|
+
declare const CONFIG_NAME = "correctly.config.ts";
|
|
150
|
+
declare const DEFAULT_EXCLUDES: string[];
|
|
151
|
+
type RemoteOptions = {
|
|
152
|
+
offline?: boolean;
|
|
153
|
+
cacheDir?: string;
|
|
154
|
+
timeoutMs?: number;
|
|
155
|
+
maxBytes?: number;
|
|
156
|
+
maxRequests?: number;
|
|
157
|
+
};
|
|
158
|
+
type ProjectConfig = {
|
|
159
|
+
files?: string[];
|
|
160
|
+
exclude?: string[];
|
|
161
|
+
associations: {
|
|
162
|
+
name?: string;
|
|
163
|
+
files: string[];
|
|
164
|
+
parse?: Parser;
|
|
165
|
+
validate: Validator | null;
|
|
166
|
+
}[];
|
|
167
|
+
remote?: RemoteOptions;
|
|
168
|
+
};
|
|
169
|
+
type Project = {
|
|
170
|
+
configPath: string;
|
|
171
|
+
root: string;
|
|
172
|
+
config: ProjectConfig;
|
|
173
|
+
};
|
|
174
|
+
type ReadText = (uri: string) => Promise<string>;
|
|
175
|
+
declare const readText: ReadText;
|
|
176
|
+
declare function defineConfig<T extends ProjectConfig>(config: T): T;
|
|
177
|
+
declare function validateConfig(value: unknown): ProjectConfig;
|
|
178
|
+
declare function contains(root: string, file: string): boolean;
|
|
179
|
+
declare function discoverConfig(start: string, boundary?: string): Promise<string>;
|
|
180
|
+
/** Comline owns module loading and invokes our runtime contract validation. */
|
|
181
|
+
declare function loadProject(configPath: string): Promise<Project>;
|
|
182
|
+
declare function schemaUri(value: string, root: string): string;
|
|
183
|
+
declare function exclusions(project: Project): string[];
|
|
184
|
+
declare function isIncluded(project: Project, file: string): boolean;
|
|
185
|
+
declare function associationFor(project: Project, file: string): Association | null;
|
|
186
|
+
declare function modeFor(file: string): string;
|
|
187
|
+
//#endregion
|
|
188
|
+
export { pointer as A, Range as B, Validator as C, diagnostic as D, SourceSpan as E, ExtensionRequirement as F, failure as H, Failure as I, FileResult as L, Association as M, CorrectlyError as N, locate as O, Diagnostic as P, Mode as R, ValidationInput as S, ParsedJsonDocument as T, isCorrectlyError as U, Report as V, validateConfig as _, ReadText as a, PreparedValidator as b, contains as c, exclusions as d, isIncluded as f, schemaUri as g, readText as h, ProjectConfig as i, positionAt as j, parseDocument as k, defineConfig as l, modeFor as m, DEFAULT_EXCLUDES as n, RemoteOptions as o, loadProject as p, Project as r, associationFor as s, CONFIG_NAME as t, discoverConfig as u, JsonSchemaEditorSupport as v, ParsedDocument as w, ValidationContext as x, Parser as y, Position as z };
|
|
189
|
+
//# sourceMappingURL=config-Bx1-XXeo.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-Bx1-XXeo.d.mts","names":[],"sources":["../src/core/types.ts","../src/core/parse.ts","../src/core/adapters.ts","../src/core/config.ts"],"mappings":";;KAAY;KACA;EAAa;EAAc;;KAC3B;EAAU,OAAO;EAAU,KAAK;;KAChC;EACX;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;IAAY;IAAgB;;;KAEjB;EACX;EACA;EACA;EACA;;KAEW;EACX;EACA;EACA;EACA,UAAU;;cAEE,uBAAuB;WAC1B;WACA,UAAU;EACnB,YAAY,cAAc,iBAAiB,UAAU;;iBAOtC,iBAAiB,iBAAiB,SAAS;iBAQ3C,QAAQ,gBAAgB,gBAAgB;KAU5C;EACX;EACA;EACA;EACA;EACA,MAAM;EACN;;KAEW;EACX;EACA,MAAM;EACN;EACA,aAAa;EACb,aAAa;EACb,UAAU;;KAEC;EACX;EACA;EACA,OAAO;EACP,UAAU;EACV;IACC;IACA;IACA;IACA;IACA;IACA;;EAED;;;;KCtEW;EAAe;EAAgB;;KAC/B;EACX;EACA,aAAa;EACb,SAAS,iBAAiB,kBAAkB;;EAE5C,aAAa;;KAEF,qBAAqB;EAAmB,MAAM;;iBAE1C,QAAQ;iBAMR,WAAW,cAAc,iBAAiB;iBAiB1C,WACf,cACA,cACA,iBACA,sBACA,iBACA,kBACE;iBAgBa,cACf,cACA,yBACE;iBAiGa,OACf,MAAM,kBACN,qBACA,gBACE;;;KCvKS;EACX;;EAEA;EACA;EACA,MACC,cACA;IAAW;IAAc,SAAS;MAChC,iBAAiB,QAAQ;;KAEjB;EACX;EACA;EACA,SAAS;EACT,MAAM;EACN,eAAe;EACf,SAAS;;EAET,UAAU;;EAEV,MAAM;;KAEK;EACX;EACA;EACA,QAAQ;EACR,SAAS;;KAEE;EACX;EACA;EACA,WAAW,cAAc;;KAEd;EACX,SAAS,OAAO,kBAAkB,eAAe,QAAQ;EACzD,SAAS;EACT,mBAAmB;;KAER;EACX;EACA;;EAEA;EACA;aAAiC;;;EAEjC,UAAU,SAAS,2BAA2B;EAC9C,QACC,SAAS,oBACP,oBAAoB,QAAQ;;;;cC3CnB;cACA;KAOD;EACX;EACA;EACA;EACA;EACA;;KAEW;EACX;EACA;EACA;IACC;IACA;IACA,QAAQ;IACR,UAAU;;EAEX,SAAS;;KAEE;EACX;EACA;EACA,QAAQ;;KAEG,YAAY,gBAAgB;cAC3B,UAAU;iBAiCP,aAAa,UAAU,eAAe,QAAQ,IAAI;iBAIlD,eAAe,iBAAiB;iBAsGhC,SAAS,cAAc;iBAQjB,eACrB,eACA,oBACE;;iBAqBmB,YAAY,qBAAqB,QAAQ;iBAyC/C,UAAU,eAAe;iBAoBzB,WAAW,SAAS;iBAkBpB,WAAW,SAAS,SAAS;iBAY7B,eACf,SAAS,SACT,eACE;iBA4Ba,QAAQ"}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { cli } from "comline";
|
|
5
|
+
import picomatch from "picomatch";
|
|
6
|
+
import { findNodeAtLocation, getNodePath, getNodeValue, parseTree, printParseErrorCode } from "jsonc-parser";
|
|
7
|
+
//#region src/core/parse.ts
|
|
8
|
+
function pointer(parts) {
|
|
9
|
+
return parts.map((p) => `/${String(p).replace(/~/g, "~0").replace(/\//g, "~1")}`).join("");
|
|
10
|
+
}
|
|
11
|
+
function positionAt(text, offset) {
|
|
12
|
+
const end = Math.min(Math.max(0, offset), text.length);
|
|
13
|
+
let line = 0;
|
|
14
|
+
let start = 0;
|
|
15
|
+
for (let i = 0; i < end; i++) if (text[i] === "\r") {
|
|
16
|
+
if (text[i + 1] === "\n" && i + 1 < end) i++;
|
|
17
|
+
line++;
|
|
18
|
+
start = i + 1;
|
|
19
|
+
} else if (text[i] === "\n") {
|
|
20
|
+
line++;
|
|
21
|
+
start = i + 1;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
line,
|
|
25
|
+
character: end - start
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function diagnostic(text, code, message, jsonPointer = "", offset = 0, length = 1) {
|
|
29
|
+
const start = Math.min(offset, text.length);
|
|
30
|
+
const size = Math.min(Math.max(1, length), text.length - start);
|
|
31
|
+
return {
|
|
32
|
+
code,
|
|
33
|
+
message,
|
|
34
|
+
pointer: jsonPointer,
|
|
35
|
+
offset: start,
|
|
36
|
+
length: size,
|
|
37
|
+
range: {
|
|
38
|
+
start: positionAt(text, start),
|
|
39
|
+
end: positionAt(text, start + size)
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function parseDocument(text, mode) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
const root = parseTree(text, errors, {
|
|
46
|
+
disallowComments: mode === "json",
|
|
47
|
+
allowTrailingComma: mode === "jsonc",
|
|
48
|
+
allowEmptyContent: false
|
|
49
|
+
});
|
|
50
|
+
const diagnostics = errors.map((e) => diagnostic(text, "syntax", printParseErrorCode(e.error), "", e.offset, e.length));
|
|
51
|
+
function visit(node) {
|
|
52
|
+
if (node.type === "object") {
|
|
53
|
+
const keys = /* @__PURE__ */ new Set();
|
|
54
|
+
for (const property of node.children ?? []) {
|
|
55
|
+
const key = property.children?.[0];
|
|
56
|
+
if (!key || typeof key.value !== "string") continue;
|
|
57
|
+
if (keys.has(key.value)) diagnostics.push(diagnostic(text, "duplicate-key", `Duplicate key ${JSON.stringify(key.value)}`, pointer([...getNodePath(node), key.value]), key.offset, key.length));
|
|
58
|
+
keys.add(key.value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (node.type === "number" && !Number.isFinite(node.value)) diagnostics.push(diagnostic(text, "syntax", "Number exceeds the supported finite range", pointer(getNodePath(node)), node.offset, node.length));
|
|
62
|
+
for (const child of node.children ?? []) visit(child);
|
|
63
|
+
}
|
|
64
|
+
if (root) visit(root);
|
|
65
|
+
function value(node) {
|
|
66
|
+
if (node.type === "object") return Object.fromEntries((node.children ?? []).flatMap((property) => {
|
|
67
|
+
const key = property.children?.[0];
|
|
68
|
+
const child = property.children?.[1];
|
|
69
|
+
return key && child ? [[String(key.value), value(child)]] : [];
|
|
70
|
+
}));
|
|
71
|
+
if (node.type === "array") return (node.children ?? []).map(value);
|
|
72
|
+
return getNodeValue(node);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
root,
|
|
76
|
+
value: root ? value(root) : void 0,
|
|
77
|
+
diagnostics,
|
|
78
|
+
rawNumber: (jsonPointer) => {
|
|
79
|
+
const parts = jsonPointer === "" ? [] : jsonPointer.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
80
|
+
let node = root;
|
|
81
|
+
for (const part of parts) {
|
|
82
|
+
if (!node) break;
|
|
83
|
+
node = findNodeAtLocation(node, [node.type === "array" ? Number(part) : part]);
|
|
84
|
+
}
|
|
85
|
+
return node?.type === "number" ? text.slice(node.offset, node.offset + node.length) : void 0;
|
|
86
|
+
},
|
|
87
|
+
locate: (jsonPointer, key = false) => {
|
|
88
|
+
const node = locate(root, jsonPointer, key);
|
|
89
|
+
return node ? {
|
|
90
|
+
offset: node.offset,
|
|
91
|
+
length: node.length
|
|
92
|
+
} : void 0;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function locate(root, jsonPointer, key = false) {
|
|
97
|
+
if (!root) return void 0;
|
|
98
|
+
const parts = jsonPointer === "" ? [] : jsonPointer.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
99
|
+
let current = root;
|
|
100
|
+
for (const part of parts) {
|
|
101
|
+
const next = findNodeAtLocation(current, [current.type === "array" ? Number(part) : part]);
|
|
102
|
+
if (!next) break;
|
|
103
|
+
current = next;
|
|
104
|
+
}
|
|
105
|
+
return key && current.parent?.type === "property" ? current.parent.children?.[0] ?? current : current;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/core/parsers.ts
|
|
109
|
+
function json() {
|
|
110
|
+
return {
|
|
111
|
+
id: "json",
|
|
112
|
+
valueModel: "json",
|
|
113
|
+
editorLanguage: "json",
|
|
114
|
+
parse: (text) => parseDocument(text, "json")
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function jsonc() {
|
|
118
|
+
return {
|
|
119
|
+
id: "jsonc",
|
|
120
|
+
valueModel: "json",
|
|
121
|
+
editorLanguage: "jsonc",
|
|
122
|
+
parse: (text) => parseDocument(text, "jsonc")
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function defaultParser(file) {
|
|
126
|
+
return file.toLowerCase().endsWith(".jsonc") ? jsonc() : json();
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/core/types.ts
|
|
130
|
+
var CorrectlyError = class extends Error {
|
|
131
|
+
code;
|
|
132
|
+
details;
|
|
133
|
+
constructor(code, message, details) {
|
|
134
|
+
super(message);
|
|
135
|
+
this.name = "CorrectlyError";
|
|
136
|
+
this.code = code;
|
|
137
|
+
if (details) this.details = details;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function isCorrectlyError(error) {
|
|
141
|
+
return error instanceof Error && error.name === "CorrectlyError" && "code" in error && typeof error.code === "string";
|
|
142
|
+
}
|
|
143
|
+
function failure(error, file) {
|
|
144
|
+
return {
|
|
145
|
+
code: isCorrectlyError(error) ? error.code : "execution",
|
|
146
|
+
message: error instanceof Error ? error.message : String(error),
|
|
147
|
+
...file === void 0 ? {} : { file },
|
|
148
|
+
...isCorrectlyError(error) && error.details ? { details: error.details } : {}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/core/config.ts
|
|
153
|
+
const CONFIG_NAME = "correctly.config.ts";
|
|
154
|
+
const DEFAULT_EXCLUDES = [
|
|
155
|
+
"**/node_modules/**",
|
|
156
|
+
"**/.git/**",
|
|
157
|
+
"**/dist/**",
|
|
158
|
+
"**/artifacts/**",
|
|
159
|
+
"**/.correctly-cache/**"
|
|
160
|
+
];
|
|
161
|
+
const readText = (uri) => readFile(fileURLToPath(uri), "utf8");
|
|
162
|
+
function object(value, location, keys) {
|
|
163
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CorrectlyError("config", `${location} must be an object`);
|
|
164
|
+
if (keys) {
|
|
165
|
+
for (const key of Object.keys(value)) if (!keys.includes(key)) throw new CorrectlyError("config", `Unknown option ${location}.${key}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function string(value, location) {
|
|
169
|
+
if (typeof value !== "string" || !value.length) throw new CorrectlyError("config", `${location} must be a nonempty string`);
|
|
170
|
+
}
|
|
171
|
+
function patterns(value, location) {
|
|
172
|
+
if (!Array.isArray(value) || !value.length) throw new CorrectlyError("config", `${location} must be a nonempty array of relative patterns`);
|
|
173
|
+
for (const item of value) {
|
|
174
|
+
string(item, location);
|
|
175
|
+
if (/^(?:[!/]|[A-Za-z]:)|(?:^|\/)\.\.(?:\/|$)|\\/.test(item)) throw new CorrectlyError("config", `${location}: use relative forward-slash patterns without '..' or leading '!': ${item}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function defineConfig(config) {
|
|
179
|
+
validateConfig(config);
|
|
180
|
+
return config;
|
|
181
|
+
}
|
|
182
|
+
function validateConfig(value) {
|
|
183
|
+
object(value, "config", [
|
|
184
|
+
"files",
|
|
185
|
+
"exclude",
|
|
186
|
+
"associations",
|
|
187
|
+
"remote"
|
|
188
|
+
]);
|
|
189
|
+
for (const key of ["files", "exclude"]) if (value[key] !== void 0) patterns(value[key], key);
|
|
190
|
+
if (!Array.isArray(value.associations)) throw new CorrectlyError("config", "associations must be an array");
|
|
191
|
+
for (const [index, rule] of value.associations.entries()) {
|
|
192
|
+
const at = `associations[${index}]`;
|
|
193
|
+
object(rule, at, [
|
|
194
|
+
"name",
|
|
195
|
+
"files",
|
|
196
|
+
"parse",
|
|
197
|
+
"validate"
|
|
198
|
+
]);
|
|
199
|
+
patterns(rule.files, `${at}.files`);
|
|
200
|
+
if (rule.name !== void 0) string(rule.name, `${at}.name`);
|
|
201
|
+
if (rule.parse !== void 0) {
|
|
202
|
+
object(rule.parse, `${at}.parse`);
|
|
203
|
+
string(rule.parse.id, `${at}.parse.id`);
|
|
204
|
+
string(rule.parse.valueModel, `${at}.parse.valueModel`);
|
|
205
|
+
if (typeof rule.parse.parse !== "function") throw new CorrectlyError("config", `${at}.parse must be a parser adapter`);
|
|
206
|
+
if (rule.parse.editorLanguage !== void 0 && rule.parse.editorLanguage !== "json" && rule.parse.editorLanguage !== "jsonc") throw new CorrectlyError("config", `${at}.parse.editorLanguage must be json or jsonc`);
|
|
207
|
+
}
|
|
208
|
+
if (rule.validate !== null) {
|
|
209
|
+
object(rule.validate, `${at}.validate`);
|
|
210
|
+
string(rule.validate.id, `${at}.validate.id`);
|
|
211
|
+
if (typeof rule.validate.prepare !== "function" || !Array.isArray(rule.validate.accepts) || !rule.validate.accepts.length || rule.validate.accepts.some((v) => typeof v !== "string" || !v)) throw new CorrectlyError("config", `${at}.validate must be a validator adapter with accepted value models`);
|
|
212
|
+
if (rule.validate.register !== void 0 && typeof rule.validate.register !== "function") throw new CorrectlyError("config", `${at}.validate.register must be a function`);
|
|
213
|
+
if (rule.validate.extensions !== void 0) {
|
|
214
|
+
if (!Array.isArray(rule.validate.extensions)) throw new CorrectlyError("config", `${at}.validate.extensions must be an array`);
|
|
215
|
+
for (const extension of rule.validate.extensions) {
|
|
216
|
+
object(extension, `${at}.validate.extensions[]`);
|
|
217
|
+
string(extension.id, `${at}.validate.extensions[].id`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (rule.validate.schema !== void 0) string(rule.validate.schema, `${at}.validate.schema`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (value.remote !== void 0) {
|
|
224
|
+
object(value.remote, "remote", [
|
|
225
|
+
"offline",
|
|
226
|
+
"cacheDir",
|
|
227
|
+
"timeoutMs",
|
|
228
|
+
"maxBytes",
|
|
229
|
+
"maxRequests"
|
|
230
|
+
]);
|
|
231
|
+
if (value.remote.offline !== void 0 && typeof value.remote.offline !== "boolean") throw new CorrectlyError("config", "remote.offline must be a boolean");
|
|
232
|
+
if (value.remote.cacheDir !== void 0) string(value.remote.cacheDir, "remote.cacheDir");
|
|
233
|
+
for (const [key, max] of [
|
|
234
|
+
["timeoutMs", 6e4],
|
|
235
|
+
["maxBytes", 16777216],
|
|
236
|
+
["maxRequests", 256]
|
|
237
|
+
]) {
|
|
238
|
+
const number = value.remote[key];
|
|
239
|
+
if (number !== void 0 && (typeof number !== "number" || !Number.isInteger(number) || number < 1 || number > max)) throw new CorrectlyError("config", `remote.${key} must be an integer between 1 and ${max}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
function contains(root, file) {
|
|
245
|
+
const relative = path.relative(root, file);
|
|
246
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
247
|
+
}
|
|
248
|
+
async function discoverConfig(start, boundary) {
|
|
249
|
+
let directory = path.resolve(start);
|
|
250
|
+
if (boundary && !contains(boundary, directory)) throw new CorrectlyError("config", `Path is outside workspace ${boundary}`);
|
|
251
|
+
while (true) {
|
|
252
|
+
const candidate = path.join(directory, CONFIG_NAME);
|
|
253
|
+
try {
|
|
254
|
+
if ((await stat(candidate)).isFile()) return candidate;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
if (error.code !== "ENOENT") throw error;
|
|
257
|
+
}
|
|
258
|
+
const parent = path.dirname(directory);
|
|
259
|
+
if (directory === boundary || parent === directory) break;
|
|
260
|
+
directory = parent;
|
|
261
|
+
}
|
|
262
|
+
throw new CorrectlyError("config", `No ${CONFIG_NAME} found from ${start}${boundary ? ` within ${boundary}` : ""}`);
|
|
263
|
+
}
|
|
264
|
+
/** Comline owns module loading and invokes our runtime contract validation. */
|
|
265
|
+
async function loadProject(configPath) {
|
|
266
|
+
const resolved = path.resolve(configPath);
|
|
267
|
+
try {
|
|
268
|
+
if (path.extname(resolved) !== ".ts") throw new Error("Correctly configuration must be a TypeScript .ts module");
|
|
269
|
+
if (!(await stat(resolved)).isFile()) throw new Error("Configuration must be a file");
|
|
270
|
+
const config = validateConfig(cli({
|
|
271
|
+
cliName: "correctly",
|
|
272
|
+
discoverConfigPath: () => resolved,
|
|
273
|
+
routeOptions: { "": {
|
|
274
|
+
description: "Correctly configuration",
|
|
275
|
+
optionsSchema: { "~standard": {
|
|
276
|
+
version: 1,
|
|
277
|
+
vendor: "correctly",
|
|
278
|
+
validate: (value) => ({ value: validateConfig(value) }),
|
|
279
|
+
jsonSchema: {
|
|
280
|
+
input: () => ({ type: "object" }),
|
|
281
|
+
output: () => ({ type: "object" })
|
|
282
|
+
}
|
|
283
|
+
} },
|
|
284
|
+
optionConfigs: {}
|
|
285
|
+
} }
|
|
286
|
+
})([process.execPath, "correctly"]).inputs.opts);
|
|
287
|
+
return {
|
|
288
|
+
configPath: resolved,
|
|
289
|
+
root: path.dirname(resolved),
|
|
290
|
+
config
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
throw new CorrectlyError("config", `Cannot load ${resolved}: ${error instanceof Error ? error.message : String(error)}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function schemaUri(value, root) {
|
|
297
|
+
if (/^[A-Za-z][A-Za-z\d+.-]*:/.test(value) && !/^[A-Za-z]:[\\/]/.test(value)) {
|
|
298
|
+
const uri = new URL(value);
|
|
299
|
+
if (![
|
|
300
|
+
"file:",
|
|
301
|
+
"http:",
|
|
302
|
+
"https:"
|
|
303
|
+
].includes(uri.protocol)) throw new CorrectlyError("schema", `Unsupported schema protocol: ${uri.protocol}`);
|
|
304
|
+
return uri.href;
|
|
305
|
+
}
|
|
306
|
+
const hash = value.indexOf("#");
|
|
307
|
+
const filename = hash === -1 ? value : value.slice(0, hash);
|
|
308
|
+
const uri = pathToFileURL(path.resolve(root, filename));
|
|
309
|
+
if (hash !== -1) uri.hash = value.slice(hash + 1);
|
|
310
|
+
return uri.href;
|
|
311
|
+
}
|
|
312
|
+
function exclusions(project) {
|
|
313
|
+
const cachePath = path.resolve(project.root, project.config.remote?.cacheDir ?? ".correctly-cache");
|
|
314
|
+
const relative = path.relative(project.root, cachePath).split(path.sep).join("/");
|
|
315
|
+
return [
|
|
316
|
+
...DEFAULT_EXCLUDES,
|
|
317
|
+
...project.config.exclude ?? [],
|
|
318
|
+
...relative && contains(project.root, cachePath) ? [`${relative}/**`] : []
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
function isIncluded(project, file) {
|
|
322
|
+
if (!contains(project.root, file)) return false;
|
|
323
|
+
const relative = path.relative(project.root, file).split(path.sep).join("/");
|
|
324
|
+
const options = { dot: true };
|
|
325
|
+
return picomatch(project.config.files ?? ["**/*.json", "**/*.jsonc"], options)(relative) && !picomatch(exclusions(project), options)(relative);
|
|
326
|
+
}
|
|
327
|
+
function associationFor(project, file) {
|
|
328
|
+
const relative = path.relative(project.root, file).split(path.sep).join("/");
|
|
329
|
+
for (let index = project.config.associations.length - 1; index >= 0; index--) {
|
|
330
|
+
const rule = project.config.associations[index];
|
|
331
|
+
if (picomatch(rule.files, { dot: true })(relative)) return {
|
|
332
|
+
index,
|
|
333
|
+
name: rule.name ?? `association ${index + 1}`,
|
|
334
|
+
mode: (rule.parse ?? defaultParser(file)).id,
|
|
335
|
+
schema: rule.validate?.schema ? schemaUri(rule.validate.schema, project.root) : null,
|
|
336
|
+
validator: rule.validate?.id ?? null,
|
|
337
|
+
...rule.validate?.extensions ? { extensions: rule.validate.extensions.map((extension) => extension.id) } : {}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
function modeFor(file) {
|
|
343
|
+
return defaultParser(file).id;
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
export { pointer as C, parseDocument as S, defaultParser as _, defineConfig as a, diagnostic as b, isIncluded as c, readText as d, schemaUri as f, isCorrectlyError as g, failure as h, contains as i, loadProject as l, CorrectlyError as m, DEFAULT_EXCLUDES as n, discoverConfig as o, validateConfig as p, associationFor as r, exclusions as s, CONFIG_NAME as t, modeFor as u, json as v, positionAt as w, locate as x, jsonc as y };
|
|
347
|
+
|
|
348
|
+
//# sourceMappingURL=config-Cb6o4YnJ.mjs.map
|