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/package.json
CHANGED
|
@@ -1,61 +1,70 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
2
|
+
"name": "correctly",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared JSON Schema validation for CI and editors",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jeremybanka/correctly.git",
|
|
8
|
+
"directory": "packages/correctly"
|
|
9
|
+
},
|
|
10
|
+
"license": "MPL-2.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/core.d.mts",
|
|
15
|
+
"import": "./dist/core.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./validators/ajv": {
|
|
18
|
+
"types": "./dist/ajv.d.mts",
|
|
19
|
+
"import": "./dist/ajv.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./extensions/renovate": {
|
|
22
|
+
"types": "./dist/renovate.d.mts",
|
|
23
|
+
"import": "./dist/renovate.mjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"correctly": "./dist/cli.mjs",
|
|
28
|
+
"correctly-lsp": "./dist/lsp.mjs"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"docs",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md",
|
|
36
|
+
"AGENTS.md",
|
|
37
|
+
"examples"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"ajv": "8.20.0",
|
|
41
|
+
"ajv-formats": "3.0.1",
|
|
42
|
+
"comline": "0.9.0",
|
|
43
|
+
"jsonc-parser": "3.3.1",
|
|
44
|
+
"picomatch": "4.0.7",
|
|
45
|
+
"tinyglobby": "0.2.17",
|
|
46
|
+
"vscode-json-languageservice": "5.7.2",
|
|
47
|
+
"vscode-languageserver": "10.1.2",
|
|
48
|
+
"vscode-languageserver-textdocument": "1.0.15"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "26.6.4",
|
|
52
|
+
"@types/picomatch": "4.0.3",
|
|
53
|
+
"@types/vscode": "1.140.0",
|
|
54
|
+
"@vscode/vsce": "4.0.0",
|
|
55
|
+
"rolldown": "1.2.12",
|
|
56
|
+
"vscode-languageclient": "10.1.2"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=22.18.0"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "vp pack",
|
|
63
|
+
"build:vsix": "node src/cli/vsix.ts",
|
|
64
|
+
"test": "vp test",
|
|
65
|
+
"test:public": "vp test tests/public",
|
|
66
|
+
"test:private": "vp test tests/private",
|
|
67
|
+
"test:watch": "vp test watch",
|
|
68
|
+
"test:distribution": "vp test tests/private/distribution.test.ts"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/cli/check.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises"
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import { glob } from "tinyglobby"
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
exclusions,
|
|
7
|
-
isIncluded,
|
|
8
|
-
loadProject,
|
|
9
|
-
} from "../core/config.ts"
|
|
4
|
+
import { discoverConfig, exclusions, isIncluded } from "../core/config.ts"
|
|
5
|
+
import { ProjectSession } from "../runtime/session.ts"
|
|
10
6
|
import { Engine } from "../core/engine.ts"
|
|
11
7
|
import { failure, type Report } from "../core/types.ts"
|
|
12
8
|
|
|
@@ -17,14 +13,15 @@ export type CheckOptions = {
|
|
|
17
13
|
files?: string[]
|
|
18
14
|
onRead?: (file: string, text: string) => void
|
|
19
15
|
}
|
|
20
|
-
|
|
16
|
+
function emptyReport(): Report {
|
|
21
17
|
const report: Report = {
|
|
22
|
-
reportVersion:
|
|
18
|
+
reportVersion: 2,
|
|
23
19
|
config: null,
|
|
24
20
|
files: [],
|
|
25
21
|
failures: [],
|
|
26
22
|
summary: {
|
|
27
23
|
checked: 0,
|
|
24
|
+
validated: 0,
|
|
28
25
|
schemaCovered: 0,
|
|
29
26
|
syntaxOnly: 0,
|
|
30
27
|
invalid: 0,
|
|
@@ -32,17 +29,47 @@ export async function check(options: CheckOptions = {}): Promise<Report> {
|
|
|
32
29
|
},
|
|
33
30
|
exitCode: 0,
|
|
34
31
|
}
|
|
32
|
+
return report
|
|
33
|
+
}
|
|
34
|
+
export async function check(options: CheckOptions = {}): Promise<Report> {
|
|
35
|
+
const report = emptyReport()
|
|
36
|
+
let session: ProjectSession | undefined
|
|
35
37
|
try {
|
|
36
38
|
const cwd = path.resolve(options.cwd ?? process.cwd())
|
|
37
39
|
const configPath = options.config
|
|
38
40
|
? path.resolve(cwd, options.config)
|
|
39
41
|
: await discoverConfig(cwd)
|
|
40
42
|
report.config = configPath
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
project,
|
|
43
|
+
session = new ProjectSession(
|
|
44
|
+
configPath,
|
|
44
45
|
options.offline === undefined ? {} : { offline: options.offline },
|
|
45
46
|
)
|
|
47
|
+
const result = await session.check({
|
|
48
|
+
cwd,
|
|
49
|
+
...(options.files ? { files: options.files } : {}),
|
|
50
|
+
sources: Boolean(options.onRead),
|
|
51
|
+
})
|
|
52
|
+
for (const [file, text] of result.sources) options.onRead?.(file, text)
|
|
53
|
+
return result.report
|
|
54
|
+
} catch (error) {
|
|
55
|
+
report.failures.push(failure(error))
|
|
56
|
+
report.summary.failures = 1
|
|
57
|
+
report.exitCode = 2
|
|
58
|
+
return report
|
|
59
|
+
} finally {
|
|
60
|
+
await session?.dispose()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function checkProject(
|
|
64
|
+
engine: Engine,
|
|
65
|
+
options: CheckOptions,
|
|
66
|
+
): Promise<Report> {
|
|
67
|
+
const report = emptyReport()
|
|
68
|
+
const project = engine.project
|
|
69
|
+
report.config = project.configPath
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const cwd = path.resolve(options.cwd ?? process.cwd())
|
|
46
73
|
await engine.prepare()
|
|
47
74
|
const files = options.files?.length
|
|
48
75
|
? options.files.map((f) => path.resolve(cwd, f))
|
|
@@ -69,6 +96,9 @@ export async function check(options: CheckOptions = {}): Promise<Report> {
|
|
|
69
96
|
const checked = report.files.filter((f) => f.coverage !== "excluded")
|
|
70
97
|
report.summary = {
|
|
71
98
|
checked: checked.length,
|
|
99
|
+
validated: checked.filter(
|
|
100
|
+
(f) => f.coverage === "schema" || f.coverage === "validated",
|
|
101
|
+
).length,
|
|
72
102
|
schemaCovered: checked.filter((f) => f.coverage === "schema").length,
|
|
73
103
|
syntaxOnly: checked.filter((f) => f.coverage === "syntax-only").length,
|
|
74
104
|
invalid: checked.filter((f) => f.diagnostics.length > 0).length,
|
package/src/cli/main.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { failure, type Report } from "../core/types.ts"
|
|
|
6
6
|
const HELP = `correctly check [files...] [--config path] [--offline] [--format readable|json]
|
|
7
7
|
correctly-lsp --stdio
|
|
8
8
|
|
|
9
|
-
Validate JSON/JSONC using associations in correctly.config.
|
|
9
|
+
Validate JSON/JSONC using associations in correctly.config.ts.
|
|
10
10
|
Exit 0: valid; 1: document errors; 2: configuration, schema, or execution errors.
|
|
11
11
|
Formatting does not affect validation. See the package guide for configuration.
|
|
12
12
|
`
|
|
@@ -59,12 +59,13 @@ export async function run(
|
|
|
59
59
|
}
|
|
60
60
|
} catch (error) {
|
|
61
61
|
const report: Report = {
|
|
62
|
-
reportVersion:
|
|
62
|
+
reportVersion: 2,
|
|
63
63
|
config: null,
|
|
64
64
|
files: [],
|
|
65
65
|
failures: [failure(error)],
|
|
66
66
|
summary: {
|
|
67
67
|
checked: 0,
|
|
68
|
+
validated: 0,
|
|
68
69
|
schemaCovered: 0,
|
|
69
70
|
syntaxOnly: 0,
|
|
70
71
|
invalid: 0,
|
package/src/cli/readable.ts
CHANGED
|
@@ -117,6 +117,19 @@ function excerpt(
|
|
|
117
117
|
return { lines: output, width }
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
function renderFailure(
|
|
121
|
+
error: Failure,
|
|
122
|
+
styler: Styler,
|
|
123
|
+
prefix = "",
|
|
124
|
+
continuation = "",
|
|
125
|
+
): string {
|
|
126
|
+
const [title, ...details] = error.message.split("\n")
|
|
127
|
+
return [
|
|
128
|
+
`${prefix}${styler.bad(error.code)}: ${title}`,
|
|
129
|
+
...details.map((line) => `${styler.dim(continuation + "│")} ${line}`),
|
|
130
|
+
].join("\n")
|
|
131
|
+
}
|
|
132
|
+
|
|
120
133
|
type FileGroup = { file: string; result?: FileResult; failures: Failure[] }
|
|
121
134
|
|
|
122
135
|
function renderDiagnostic(
|
|
@@ -201,7 +214,9 @@ function fileSection(
|
|
|
201
214
|
? format
|
|
202
215
|
: schema
|
|
203
216
|
? `${format} against ${association.name} (${displaySchema(cwd, schema)})`
|
|
204
|
-
:
|
|
217
|
+
: association?.validator
|
|
218
|
+
? `${format} validated by ${association.validator}`
|
|
219
|
+
: `${format} syntax only (${association ? `association: ${association.name}` : "no schema association"})`
|
|
205
220
|
header += ` ${styler.dim(`· ${description}`)}`
|
|
206
221
|
}
|
|
207
222
|
const output = [header]
|
|
@@ -230,7 +245,12 @@ function fileSection(
|
|
|
230
245
|
)
|
|
231
246
|
} else
|
|
232
247
|
output.push(
|
|
233
|
-
|
|
248
|
+
renderFailure(
|
|
249
|
+
issue,
|
|
250
|
+
styler,
|
|
251
|
+
`${styler.dim(branch)} `,
|
|
252
|
+
last ? " " : "│ ",
|
|
253
|
+
),
|
|
234
254
|
)
|
|
235
255
|
if (!last) output.push(styler.dim("│"))
|
|
236
256
|
}
|
|
@@ -265,8 +285,7 @@ export function readableReport(
|
|
|
265
285
|
const output = [...groups.values()]
|
|
266
286
|
.toSorted((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0))
|
|
267
287
|
.map((group) => fileSection(group, options, styler))
|
|
268
|
-
for (const error of globalFailures)
|
|
269
|
-
output.push(`${styler.bad(error.code)}: ${error.message}`)
|
|
288
|
+
for (const error of globalFailures) output.push(renderFailure(error, styler))
|
|
270
289
|
const errors = report.files.reduce(
|
|
271
290
|
(count, file) => count + diagnosticViews(file.diagnostics).length,
|
|
272
291
|
0,
|
|
@@ -288,7 +307,7 @@ export function readableReport(
|
|
|
288
307
|
? styler.warning
|
|
289
308
|
: styler.success)(status),
|
|
290
309
|
"",
|
|
291
|
-
`${s.checked} checked, ${s.schemaCovered} schema-covered, ${s.syntaxOnly} syntax-only, ${s.invalid} invalid, ${s.failures} failures`,
|
|
310
|
+
`${s.checked} checked, ${s.validated} validated, ${s.schemaCovered} schema-covered, ${s.syntaxOnly} syntax-only, ${s.invalid} invalid, ${s.failures} failures`,
|
|
292
311
|
].join("\n"),
|
|
293
312
|
)
|
|
294
313
|
return output.join("\n\n")
|
package/src/cli/vsix.ts
CHANGED
|
@@ -15,6 +15,7 @@ export async function buildVsix(
|
|
|
15
15
|
for (const [entry, outfile] of [
|
|
16
16
|
["vscode/extension", "extension"],
|
|
17
17
|
["lsp/server", "server"],
|
|
18
|
+
["runtime/worker", "worker"],
|
|
18
19
|
]) {
|
|
19
20
|
const bundle = await rolldown({
|
|
20
21
|
input: path.join(packageRoot, `src/${entry}.ts`),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Diagnostic } from "./types.ts"
|
|
2
|
+
import type { ParsedDocument } from "./parse.ts"
|
|
3
|
+
import type { ReadText, RemoteOptions } from "./config.ts"
|
|
4
|
+
|
|
5
|
+
export type Parser = {
|
|
6
|
+
id: string
|
|
7
|
+
/** The parsed value representation, not the source file extension. */
|
|
8
|
+
valueModel: string
|
|
9
|
+
editorLanguage?: "json" | "jsonc"
|
|
10
|
+
parse(
|
|
11
|
+
text: string,
|
|
12
|
+
context: { file: string; signal?: AbortSignal },
|
|
13
|
+
): ParsedDocument | Promise<ParsedDocument>
|
|
14
|
+
}
|
|
15
|
+
export type ValidationContext = {
|
|
16
|
+
root: string
|
|
17
|
+
configPath: string
|
|
18
|
+
remote?: RemoteOptions
|
|
19
|
+
read: ReadText
|
|
20
|
+
fetch?: typeof fetch
|
|
21
|
+
signal?: AbortSignal
|
|
22
|
+
/** Project-scoped services; use a versioned Symbol.for key for cross-package sharing. */
|
|
23
|
+
services: Map<symbol, unknown>
|
|
24
|
+
/** Register a file or URI which affects this adapter's prepared state. */
|
|
25
|
+
watch(uri: string): void
|
|
26
|
+
}
|
|
27
|
+
export type ValidationInput = {
|
|
28
|
+
file: string
|
|
29
|
+
text: string
|
|
30
|
+
parsed: ParsedDocument
|
|
31
|
+
signal?: AbortSignal
|
|
32
|
+
}
|
|
33
|
+
export type JsonSchemaEditorSupport = {
|
|
34
|
+
kind: "json-schema"
|
|
35
|
+
uri: string
|
|
36
|
+
readSchema(uri: string): Promise<string>
|
|
37
|
+
}
|
|
38
|
+
export type PreparedValidator = {
|
|
39
|
+
validate(input: ValidationInput): Diagnostic[] | Promise<Diagnostic[]>
|
|
40
|
+
editor?: JsonSchemaEditorSupport
|
|
41
|
+
dispose?(): void | Promise<void>
|
|
42
|
+
}
|
|
43
|
+
export type Validator = {
|
|
44
|
+
id: string
|
|
45
|
+
accepts: readonly string[]
|
|
46
|
+
/** Optional JSON Schema location for coverage reporting. */
|
|
47
|
+
schema?: string
|
|
48
|
+
extensions?: readonly { readonly id: string }[]
|
|
49
|
+
/** Register resources before any association is prepared (e.g. schema IDs). */
|
|
50
|
+
register?(context: ValidationContext): void | Promise<void>
|
|
51
|
+
prepare(
|
|
52
|
+
context: ValidationContext,
|
|
53
|
+
): PreparedValidator | Promise<PreparedValidator>
|
|
54
|
+
}
|