aontu 0.66.0 → 0.67.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/README.md +4 -1
- package/dist/allow.js +2 -2
- package/dist/allow.js.map +1 -1
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +126 -23
- package/dist/cli.js.map +1 -1
- package/dist/hints.js +2 -1
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +6 -3
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/query.d.ts +1 -1
- package/dist/query.js +7 -9
- package/dist/query.js.map +1 -1
- package/dist/vet.d.ts +1 -0
- package/dist/vet.js +39 -9
- package/dist/vet.js.map +1 -1
- package/dist/view.js +2 -4
- package/dist/view.js.map +1 -1
- package/package.json +2 -2
- package/src/allow.ts +3 -3
- package/src/aontu.ts +1 -1
- package/src/cli.ts +140 -24
- package/src/hints.ts +2 -1
- package/src/lang.ts +7 -3
- package/src/mcp-server.ts +1 -1
- package/src/mcp.ts +1 -1
- package/src/query.ts +8 -13
- package/src/tsconfig.json +2 -1
- package/src/vet.ts +47 -9
- package/src/view.ts +3 -7
- package/dist/tsconfig.tsbuildinfo +0 -1
package/src/cli.ts
CHANGED
|
@@ -166,22 +166,30 @@ query between a document and its own earlier versions.
|
|
|
166
166
|
|
|
167
167
|
Options:
|
|
168
168
|
-c, --canon Print the canonical form instead of generated JSON
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
(the bare command's, as --jsonl is; model get has
|
|
170
|
+
its own)
|
|
171
|
+
--format <f> text (default) or json, on every verb that answers a
|
|
172
|
+
report. The json form is one object opening with an
|
|
173
|
+
aontu block; the bare command's carries findings, ok
|
|
174
|
+
and out
|
|
172
175
|
-h, --help Show this help and exit (the verbs and their flags);
|
|
173
176
|
aontu help is the LANGUAGE, and lists its own topics
|
|
174
177
|
--jsonl REPL: answer every command as one JSON line
|
|
175
178
|
-v, --version Print the version and exit
|
|
176
|
-
--trust <t> Include capability: system
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
--trust <t> Include capability: system, none, or root[:dir] to
|
|
180
|
+
confine @"..." below a directory. A bare root means
|
|
181
|
+
the entry root: the document's own directory, or the
|
|
182
|
+
project's for the package verbs. Every verb takes it
|
|
183
|
+
but help, explain, init and lsp, which read no
|
|
184
|
+
document, and mcp, which is the npm build's server
|
|
185
|
+
and confines with --root. Unset, a run behaves as
|
|
186
|
+
system, and the bare command warns once for an
|
|
187
|
+
include that leaves the entry root
|
|
180
188
|
--include-root <dir> Shorthand for --trust root:<dir>
|
|
181
|
-
--text-ext <e> Read these extensions as text too, comma-separated
|
|
182
|
-
|
|
189
|
+
--text-ext <e> Read these extensions as text too, comma-separated,
|
|
190
|
+
with or without dots (md,sql). .txt needs no flag; a
|
|
183
191
|
named format keeps its meaning, and .js stays
|
|
184
|
-
refused.
|
|
192
|
+
refused. The verbs that take --trust take it too
|
|
185
193
|
|
|
186
194
|
Package verbs (a module is imported; a package is published):
|
|
187
195
|
sync Make the project correct: resolve by minimum version
|
|
@@ -708,7 +716,7 @@ function takeTrust(argv: string[], io: Io = PROCESS_IO):
|
|
|
708
716
|
}
|
|
709
717
|
else if ('--include-root' === arg) {
|
|
710
718
|
const dir = argv[++i]
|
|
711
|
-
if (null == dir) {
|
|
719
|
+
if (null == dir || '' === dir) {
|
|
712
720
|
io.err('aontu: --include-root needs a directory\n')
|
|
713
721
|
return undefined
|
|
714
722
|
}
|
|
@@ -2704,20 +2712,42 @@ function pkgNetText(verb: string, report: any): string {
|
|
|
2704
2712
|
const MODEL_HELP = 'aontu model get|why|set ... (try --help)'
|
|
2705
2713
|
|
|
2706
2714
|
// One document, interrogated or edited (ADR-039 part 5).
|
|
2715
|
+
// Where the subcommand is, past any global flag that precedes it.
|
|
2716
|
+
// `takeTrust` strips those anywhere in a tail, so the subcommand has
|
|
2717
|
+
// to be found past a flag AND past its value: in `--text-ext get` the
|
|
2718
|
+
// `get` is the extension list, not the subcommand.
|
|
2719
|
+
const MODEL_SUBS = ['get', 'why', 'set']
|
|
2720
|
+
const GLOBAL_VALUED = ['--trust', '--include-root', '--text-ext']
|
|
2721
|
+
|
|
2722
|
+
|
|
2723
|
+
function modelSubAt(argv: string[]): number {
|
|
2724
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2725
|
+
if (GLOBAL_VALUED.includes(argv[i])) {
|
|
2726
|
+
i++
|
|
2727
|
+
continue
|
|
2728
|
+
}
|
|
2729
|
+
return MODEL_SUBS.includes(argv[i]) ? i : -1
|
|
2730
|
+
}
|
|
2731
|
+
return -1
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
|
|
2707
2735
|
function runModel(argv: string[]): number {
|
|
2708
2736
|
const sub = argv[0]
|
|
2709
2737
|
if ('-h' === sub || '--help' === sub) {
|
|
2710
2738
|
process.stdout.write(HELP)
|
|
2711
2739
|
return 0
|
|
2712
2740
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2741
|
+
const at = modelSubAt(argv)
|
|
2742
|
+
if (0 <= at) {
|
|
2743
|
+
const tail = argv.slice(0, at).concat(argv.slice(at + 1))
|
|
2744
|
+
if ('get' === argv[at]) {
|
|
2745
|
+
return runGet(tail)
|
|
2746
|
+
}
|
|
2747
|
+
if ('why' === argv[at]) {
|
|
2748
|
+
return runWhy(tail)
|
|
2749
|
+
}
|
|
2750
|
+
return runSet(tail)
|
|
2721
2751
|
}
|
|
2722
2752
|
process.stderr.write(`aontu: model needs get, why or set\n${MODEL_HELP}\n`)
|
|
2723
2753
|
return 2
|
|
@@ -2931,6 +2961,70 @@ function isDirectory(path: string): boolean {
|
|
|
2931
2961
|
}
|
|
2932
2962
|
|
|
2933
2963
|
|
|
2964
|
+
// A `File` the write path skips, RENAMED rather than removed: dropping
|
|
2965
|
+
// the node would take its children's claims with it and hide drift the
|
|
2966
|
+
// write path does make. The skip is the File's own save alone, so what
|
|
2967
|
+
// separates the runs is its output path, composed by the runtime rather
|
|
2968
|
+
// than here.
|
|
2969
|
+
const EXCLUDED_NAME = '.aontu-check-excluded-'
|
|
2970
|
+
|
|
2971
|
+
|
|
2972
|
+
// `exclude` as the runtime reads it: `true`, or a string or list member
|
|
2973
|
+
// equal to the node's COMPONENT path, the chain of `name` props above
|
|
2974
|
+
// it, which a Project's `folder` is not part of. The Go runtime honours
|
|
2975
|
+
// the boolean alone, so the ports' `--check` answers differ for the
|
|
2976
|
+
// path forms (test/spec/divergent.tsv).
|
|
2977
|
+
function excludedFile(exclude: any, at: string[]): boolean {
|
|
2978
|
+
if (true === exclude) {
|
|
2979
|
+
return true
|
|
2980
|
+
}
|
|
2981
|
+
const path = at.join('/')
|
|
2982
|
+
if ('string' === typeof exclude) {
|
|
2983
|
+
return exclude === path
|
|
2984
|
+
}
|
|
2985
|
+
return Array.isArray(exclude) && exclude.includes(path)
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
|
|
2989
|
+
function renameExcluded(node: any, at: string[], cut: number[]): any {
|
|
2990
|
+
if (Array.isArray(node)) {
|
|
2991
|
+
return node.map((child) => renameExcluded(child, at, cut))
|
|
2992
|
+
}
|
|
2993
|
+
// A hand-written tree carries nodes with no `props` and nodes with
|
|
2994
|
+
// no `children`, and neither needs an arm of its own.
|
|
2995
|
+
const props: any = node.props ?? {}
|
|
2996
|
+
const below = 'string' === typeof props.name ? at.concat(props.name) : at
|
|
2997
|
+
if ('File' === node.cmp && excludedFile(props.exclude, below)) {
|
|
2998
|
+
cut.push(1)
|
|
2999
|
+
return { ...node, props: { ...props, name: EXCLUDED_NAME + cut.length } }
|
|
3000
|
+
}
|
|
3001
|
+
if (!Array.isArray(node.children)) {
|
|
3002
|
+
return node
|
|
3003
|
+
}
|
|
3004
|
+
return { ...node, children: renameExcluded(node.children, below, cut) }
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
|
|
3008
|
+
// A file the runtime declined to touch, its copy on disk carrying the
|
|
3009
|
+
// protect marker, is in none of the lists it answers with: they hold
|
|
3010
|
+
// what was DONE to a file, and nothing was. The run's own record names
|
|
3011
|
+
// it. `since` drops what earlier runs left.
|
|
3012
|
+
function renderSkipped(folder: string, since: number): string[] {
|
|
3013
|
+
const at = join(folder, '.jostraca', 'jostraca.meta.log')
|
|
3014
|
+
let meta: any
|
|
3015
|
+
try {
|
|
3016
|
+
meta = JSON.parse(readFileSync(at, 'utf8'))
|
|
3017
|
+
}
|
|
3018
|
+
catch (err: any) {
|
|
3019
|
+
return []
|
|
3020
|
+
}
|
|
3021
|
+
return Object.entries(meta?.files ?? {})
|
|
3022
|
+
.filter(([_, f]: [string, any]) => 'skip' === f?.action && since <= f?.when)
|
|
3023
|
+
.map(([path]) => path)
|
|
3024
|
+
.sort(cmpCodePoint)
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
|
|
2934
3028
|
async function runRender(argv: string[]): Promise<number> {
|
|
2935
3029
|
const trusted = takeTrust(argv)
|
|
2936
3030
|
if (null == trusted) {
|
|
@@ -3081,7 +3175,22 @@ async function runRender(argv: string[]): Promise<number> {
|
|
|
3081
3175
|
try {
|
|
3082
3176
|
if (check) {
|
|
3083
3177
|
const res = await runtime.check({ folder }, root)
|
|
3084
|
-
|
|
3178
|
+
let drift = res.drift.map((d: any) => ({ kind: d.kind, path: d.path }))
|
|
3179
|
+
// `--check` answers "would `render` change anything", so a file
|
|
3180
|
+
// the write path leaves alone is not held to the generator's
|
|
3181
|
+
// bytes. The skip is gated on the target BEING there -- `render`
|
|
3182
|
+
// writes an absent one -- so drift at a path with nothing at it
|
|
3183
|
+
// survives, which is the `missing` a deleted file reports.
|
|
3184
|
+
const cut: number[] = []
|
|
3185
|
+
const renamed = renameExcluded(tree, [], cut)
|
|
3186
|
+
if (0 < cut.length) {
|
|
3187
|
+
const kept = new Set<string>((await Jostraca().check(
|
|
3188
|
+
{ folder }, cmpTree(renamed, { raw: true }))).checked)
|
|
3189
|
+
const skipped = new Set<string>(
|
|
3190
|
+
res.checked.filter((p: string) => !kept.has(p)))
|
|
3191
|
+
drift = drift.filter((d: any) => !skipped.has(d.path) ||
|
|
3192
|
+
!existsSync(join(folder, d.path)))
|
|
3193
|
+
}
|
|
3085
3194
|
if ('json' === format) {
|
|
3086
3195
|
process.stdout.write(exactJSON({
|
|
3087
3196
|
aontu: { version: version(), verb: 'render' },
|
|
@@ -3098,14 +3207,21 @@ async function runRender(argv: string[]): Promise<number> {
|
|
|
3098
3207
|
return 0 === drift.length ? 0 : 1
|
|
3099
3208
|
}
|
|
3100
3209
|
|
|
3210
|
+
const since = Date.now()
|
|
3101
3211
|
const res = await runtime.generate({ folder }, root)
|
|
3212
|
+
const skipped = renderSkipped(folder, since)
|
|
3102
3213
|
if ('json' === format) {
|
|
3103
3214
|
process.stdout.write(exactJSON({
|
|
3104
3215
|
aontu: { version: version(), verb: 'render' },
|
|
3105
3216
|
verdict: 'ok',
|
|
3106
|
-
files: res.files,
|
|
3217
|
+
files: { ...res.files, skipped },
|
|
3107
3218
|
}, 2) + '\n')
|
|
3108
3219
|
}
|
|
3220
|
+
else {
|
|
3221
|
+
for (const path of skipped) {
|
|
3222
|
+
process.stdout.write(`skipped: ${path}\n`)
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3109
3225
|
return 0
|
|
3110
3226
|
}
|
|
3111
3227
|
catch (err: any) {
|
|
@@ -3932,7 +4048,7 @@ function runHash(argv: string[]): number {
|
|
|
3932
4048
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
3933
4049
|
process.stderr.write(
|
|
3934
4050
|
`aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
|
|
3935
|
-
renderFinding(evalFailure(ctx)) + '\n')
|
|
4051
|
+
renderFinding(evalFailure(ctx, v)) + '\n')
|
|
3936
4052
|
return 4
|
|
3937
4053
|
}
|
|
3938
4054
|
|
|
@@ -5322,7 +5438,7 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5322
5438
|
}
|
|
5323
5439
|
else if ('--include-root' === arg) {
|
|
5324
5440
|
const dir = args[++i]
|
|
5325
|
-
if (null == dir) {
|
|
5441
|
+
if (null == dir || '' === dir) {
|
|
5326
5442
|
process.stderr.write('aontu: --include-root needs a directory\n')
|
|
5327
5443
|
return finish(2)
|
|
5328
5444
|
}
|
|
@@ -5385,7 +5501,7 @@ export {
|
|
|
5385
5501
|
runJsonSchema,
|
|
5386
5502
|
runTemplate,
|
|
5387
5503
|
runTrace,
|
|
5388
|
-
runRender,
|
|
5504
|
+
runRender, renderSkipped,
|
|
5389
5505
|
runPkg, runModel, runPackageVerb, pkgToolOptions, serveUntilInterrupted,
|
|
5390
5506
|
runHash, runGet, runHelp, runExplain, runInit, nearestVerb,
|
|
5391
5507
|
looksLikeVerb,
|
package/src/hints.ts
CHANGED
|
@@ -181,7 +181,7 @@ const hints: Record<string, string> = {
|
|
|
181
181
|
|
|
182
182
|
merge_conflict: 'A version-control conflict marker was found in the source. The\nfile still holds an unresolved merge: resolve it and remove the\n`<<<<<<<`, `=======` and `>>>>>>>` lines before unifying.\n \nExamples:\n <<<<<<< HEAD -> nil # A conflict marker, not a `<` operation;\n ======= -> nil # ... nor a chain of `=` characters;\n >>>>>>> other -> nil # ... nor a `>` operation.',
|
|
183
183
|
|
|
184
|
-
include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"./in-root.aon"
|
|
184
|
+
include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"./in-root.aon" -> {..} # Inside the confinement root: allowed;\n a:@"../secret.aon" -> nil # ... but escaping the root is denied;\n a:@"/etc/hostname" -> nil # ... and so is an absolute path outside it.',
|
|
185
185
|
include_extension: 'An @\"...\" include named a file the engine does not read. THE\nEXTENSION DECIDES which of two things a file is: `.aon` and\n`.aontu` are aontu source, with the whole language in them, and\n`.json`, `.jsonld`, `.jsonc`, `.json5`, `.jsonic`, `.jsc`, `.toml`,\n`.yaml`, `.yml` and `.ini` are configuration DATA, read by that\nformat\'s own parser. Anything else -- and a name with no extension\n-- is refused rather than guessed at: a guess produces a document\nthat looks right and is not.\n \nExamples:\n a:@\"model.aon\" -> {..} # aontu source;\n a:@\"server.toml\" -> {..} # ... config data, read as TOML;\n a:@\"notes.txt\" -> nil # ... but text is not a document;\n a:@\"data\" -> nil # ... and neither is an unnamed kind.',
|
|
186
186
|
|
|
187
187
|
func_arity: 'This function was called with the wrong number of arguments:\n{func} takes {want}, but was given {got}.\n \nExamples:\n upper(\"a\") -> \"A\" # One argument, which is what upper takes;\n upper(\"a\",\"b\") -> nil # ... so two is a mistake in the source;\n key() -> \"\" # key takes none, or one level count;\n neq(1,2,3) -> neq # ... and neq takes one or more exclusions.',
|
|
@@ -780,6 +780,7 @@ const codeClasses: Record<string, string> = {
|
|
|
780
780
|
|
|
781
781
|
// internal -- the engine reached a state it should not reach
|
|
782
782
|
internal: 'internal',
|
|
783
|
+
unify_failed: 'internal',
|
|
783
784
|
unify_no_res: 'internal',
|
|
784
785
|
unknown_op: 'internal',
|
|
785
786
|
}
|
package/src/lang.ts
CHANGED
|
@@ -1544,9 +1544,13 @@ function makeModelResolver(options: any) {
|
|
|
1544
1544
|
|
|
1545
1545
|
const memCapability =
|
|
1546
1546
|
'object' === typeof capability && null != (capability as any).mem
|
|
1547
|
+
// An EMPTY root denies: resolving it yields the process directory.
|
|
1548
|
+
const emptyRoot =
|
|
1549
|
+
'object' === typeof capability && '' === (capability as any).root
|
|
1547
1550
|
const rootDir: string | undefined =
|
|
1548
1551
|
'object' === typeof capability &&
|
|
1549
|
-
'string' === typeof (capability as any).root
|
|
1552
|
+
'string' === typeof (capability as any).root &&
|
|
1553
|
+
'' !== (capability as any).root
|
|
1550
1554
|
? pathResolve((capability as any).root) : undefined
|
|
1551
1555
|
|
|
1552
1556
|
let memResolver = makeMemResolver(memCapability
|
|
@@ -1584,7 +1588,7 @@ function makeModelResolver(options: any) {
|
|
|
1584
1588
|
const deny = (path: string): never => {
|
|
1585
1589
|
// Only 'none' and 'root' can deny: the mem capability's misses are
|
|
1586
1590
|
// not-found (its set is the whole world), so there is no third arm.
|
|
1587
|
-
const capname =
|
|
1591
|
+
const capname = null == rootDir ? 'none' : 'root:' + rootDir
|
|
1588
1592
|
const err: any = new Error(
|
|
1589
1593
|
'include denied: ' + path + ' (capability: ' + capname + ')')
|
|
1590
1594
|
err.code = 'include_denied'
|
|
@@ -1679,7 +1683,7 @@ function makeModelResolver(options: any) {
|
|
|
1679
1683
|
return { found: false, path: '' + (path ?? ''), search: [] }
|
|
1680
1684
|
}
|
|
1681
1685
|
|
|
1682
|
-
if ('none' === capability) {
|
|
1686
|
+
if ('none' === capability || emptyRoot) {
|
|
1683
1687
|
deny(path)
|
|
1684
1688
|
}
|
|
1685
1689
|
|
package/src/mcp-server.ts
CHANGED
package/src/mcp.ts
CHANGED
|
@@ -653,7 +653,7 @@ function hashOf(
|
|
|
653
653
|
const v: any = aontu.unify(
|
|
654
654
|
src, null == path ? undefined : { path }, ctx)
|
|
655
655
|
if (0 < ctx.err.length || null == v || true === v.isNil) {
|
|
656
|
-
return { ok: false, hash: '', findings: [evalFailure(ctx)] }
|
|
656
|
+
return { ok: false, hash: '', findings: [evalFailure(ctx, v)] }
|
|
657
657
|
}
|
|
658
658
|
return {
|
|
659
659
|
ok: true,
|
package/src/query.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { includeOpts } from './utility'
|
|
|
5
5
|
import { Aontu } from './aontu'
|
|
6
6
|
import type { TrustOptions } from './type'
|
|
7
7
|
import { exactJSON } from './exactjson'
|
|
8
|
-
import { anchorAt } from './vet'
|
|
8
|
+
import { anchorAt, engineFinding } from './vet'
|
|
9
9
|
import type { VetFinding } from './vet'
|
|
10
10
|
import { cmpCodePoint } from './keyorder'
|
|
11
11
|
import { Provenance } from './provenance'
|
|
@@ -87,9 +87,6 @@ export function pathParts(path: string): string[] {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
// The queried path, normalised the way anchorAt reads it — so a
|
|
91
|
-
// finding names `$.a.b` whether the caller wrote that, `a.b` or
|
|
92
|
-
// `$.a.b.` — and `$` for the root.
|
|
93
90
|
function pathText(path: string): string {
|
|
94
91
|
const parts = pathParts(path)
|
|
95
92
|
return '$' + (0 < parts.length ? '.' + parts.join('.') : '')
|
|
@@ -170,9 +167,10 @@ function finding(
|
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
// A document that does not stand up, reported at the root: a literal
|
|
171
|
+
// nil adds nothing to `ctx.err`, so `failed` carries the only code.
|
|
172
|
+
export function evalFailure(ctx: any, failed?: any): VetFinding {
|
|
173
|
+
return engineFinding(ctx.err[0] ?? failed, ctx, '$')
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
|
|
@@ -216,7 +214,7 @@ export function get(
|
|
|
216
214
|
const root: any = aontu.unify(src, parseOpts, ctx)
|
|
217
215
|
|
|
218
216
|
if (0 < ctx.err.length || null == root || true === root.isNil) {
|
|
219
|
-
return { ok: false, out: '', findings: [evalFailure(ctx)] }
|
|
217
|
+
return { ok: false, out: '', findings: [evalFailure(ctx, root)] }
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
const node: any = anchorAt(root, path)
|
|
@@ -232,10 +230,7 @@ export function get(
|
|
|
232
230
|
return {
|
|
233
231
|
ok: false,
|
|
234
232
|
out: '',
|
|
235
|
-
findings: [
|
|
236
|
-
err?.why ?? 'no_gen',
|
|
237
|
-
pathText(path),
|
|
238
|
-
err?.msg ?? 'The value at this path is not concrete.')],
|
|
233
|
+
findings: [engineFinding(err, ctx, pathText(path))],
|
|
239
234
|
}
|
|
240
235
|
}
|
|
241
236
|
return { ok: true, out: exactJSON(gen, 2), findings: [] }
|
|
@@ -274,7 +269,7 @@ export function why(
|
|
|
274
269
|
|
|
275
270
|
const root: any = aontu.unify(parsed, parseOpts, ctx)
|
|
276
271
|
if (0 < ctx.err.length || null == root || true === root.isNil) {
|
|
277
|
-
return { ok: false, findings: [evalFailure(ctx)] }
|
|
272
|
+
return { ok: false, findings: [evalFailure(ctx, root)] }
|
|
278
273
|
}
|
|
279
274
|
|
|
280
275
|
const node: any = anchorAt(root, path)
|
package/src/tsconfig.json
CHANGED
package/src/vet.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'node:path'
|
|
11
11
|
|
|
12
12
|
import { descErr, getHint } from './err'
|
|
13
|
+
import { codeClass } from './hints'
|
|
13
14
|
import { ConjunctVal } from './val/ConjunctVal'
|
|
14
15
|
import { walkVals, collectNils } from './walk'
|
|
15
16
|
import { sizingResidue } from './val/BagVal'
|
|
@@ -204,6 +205,9 @@ function materialise(nil: any, ctx: any): void {
|
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
|
|
208
|
+
const UNSHAPED = 'unify_failed'
|
|
209
|
+
|
|
210
|
+
|
|
207
211
|
// The terminal colour escapes the parser puts in its message text. A
|
|
208
212
|
// RegExp built from a string, not a literal: the escape is a control
|
|
209
213
|
// character, and spelling it `\u001b` keeps the source readable.
|
|
@@ -214,16 +218,10 @@ function stripAnsi(s: string): string {
|
|
|
214
218
|
}
|
|
215
219
|
|
|
216
220
|
|
|
217
|
-
|
|
221
|
+
// The registry's hint and a code's own `details` fields: the same set
|
|
222
|
+
// on every finding reporting an ENGINE code.
|
|
223
|
+
function fromRegistry(finding: VetFinding, nil: any): VetFinding {
|
|
218
224
|
const details = nil.details ?? {}
|
|
219
|
-
const finding: VetFinding = {
|
|
220
|
-
code: nil.why,
|
|
221
|
-
class: nil.class,
|
|
222
|
-
severity: 'error',
|
|
223
|
-
path: pathText(nil.path),
|
|
224
|
-
message: stripAnsi(nil.msg.split('\n')[0]),
|
|
225
|
-
sites: sitesOf(nil, prov),
|
|
226
|
-
}
|
|
227
225
|
|
|
228
226
|
const hint = getHint(nil.why, nil.details)
|
|
229
227
|
if (null != hint && '' !== hint) {
|
|
@@ -244,6 +242,18 @@ function findingOf(nil: any, prov: Prov): VetFinding {
|
|
|
244
242
|
}
|
|
245
243
|
|
|
246
244
|
|
|
245
|
+
function findingOf(nil: any, prov: Prov): VetFinding {
|
|
246
|
+
return fromRegistry({
|
|
247
|
+
code: nil.why,
|
|
248
|
+
class: nil.class,
|
|
249
|
+
severity: 'error',
|
|
250
|
+
path: pathText(nil.path),
|
|
251
|
+
message: stripAnsi(nil.msg.split('\n')[0]),
|
|
252
|
+
sites: sitesOf(nil, prov),
|
|
253
|
+
}, nil)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
247
257
|
const ORDER_PAD = 9
|
|
248
258
|
|
|
249
259
|
|
|
@@ -265,6 +275,34 @@ function orderKey(f: VetFinding, index: number): string {
|
|
|
265
275
|
}
|
|
266
276
|
|
|
267
277
|
|
|
278
|
+
// An ENGINE code at a path the caller names: class from the registry
|
|
279
|
+
// through NilVal.class, message materialised first (a `gen` nil has none).
|
|
280
|
+
export function engineFinding(
|
|
281
|
+
nil: any, ctx: any, path: string): VetFinding {
|
|
282
|
+
if (null == nil) {
|
|
283
|
+
return {
|
|
284
|
+
code: UNSHAPED,
|
|
285
|
+
class: codeClass(UNSHAPED),
|
|
286
|
+
severity: 'error',
|
|
287
|
+
path,
|
|
288
|
+
message: 'The document does not evaluate.',
|
|
289
|
+
sites: [],
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
materialise(nil, ctx)
|
|
294
|
+
|
|
295
|
+
return fromRegistry({
|
|
296
|
+
code: nil.why,
|
|
297
|
+
class: nil.class,
|
|
298
|
+
severity: 'error',
|
|
299
|
+
path,
|
|
300
|
+
message: stripAnsi(nil.msg.split('\n')[0]),
|
|
301
|
+
sites: [],
|
|
302
|
+
}, nil)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
268
306
|
export function failureFinding(
|
|
269
307
|
ctx: any, url?: string, failed?: any): VetFinding {
|
|
270
308
|
const nil: any = ctx.err[0] ?? failed
|
package/src/view.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path'
|
|
5
5
|
|
|
6
6
|
import { Aontu } from './aontu'
|
|
7
|
-
import { failureFinding, anchorAt, throughResidue } from './vet'
|
|
7
|
+
import { failureFinding, engineFinding, anchorAt, throughResidue } from './vet'
|
|
8
8
|
import type { VetFinding } from './vet'
|
|
9
9
|
import type { TrustOptions } from './type'
|
|
10
10
|
import { graphOf } from './graph'
|
|
@@ -2544,10 +2544,8 @@ function drawLoaded(
|
|
|
2544
2544
|
const before = ctx.err.length
|
|
2545
2545
|
value = root.gen(ctx)
|
|
2546
2546
|
if (before < ctx.err.length) {
|
|
2547
|
-
const err: any = ctx.err[before]
|
|
2548
2547
|
return {
|
|
2549
|
-
errors: [
|
|
2550
|
-
err?.msg ?? 'The document does not generate.')],
|
|
2548
|
+
errors: [engineFinding(ctx.err[before], ctx, '$')],
|
|
2551
2549
|
}
|
|
2552
2550
|
}
|
|
2553
2551
|
}
|
|
@@ -2749,11 +2747,9 @@ export function viewSet(
|
|
|
2749
2747
|
const before = ctx.err.length
|
|
2750
2748
|
const value = root.gen(ctx)
|
|
2751
2749
|
if (before < ctx.err.length) {
|
|
2752
|
-
const err: any = ctx.err[before]
|
|
2753
2750
|
return {
|
|
2754
2751
|
verdict: 'error', views: [],
|
|
2755
|
-
errors: [
|
|
2756
|
-
err?.msg ?? 'The document does not generate.')],
|
|
2752
|
+
errors: [engineFinding(ctx.err[before], ctx, '$')],
|
|
2757
2753
|
}
|
|
2758
2754
|
}
|
|
2759
2755
|
const declared = genAt(value, at)
|