aontu 0.53.0 → 0.55.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/dist/aontu.d.ts +3 -2
- package/dist/aontu.js +36 -7
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +500 -8
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +5 -0
- package/dist/ctx.js +1 -0
- package/dist/ctx.js.map +1 -1
- package/dist/diff.js.map +1 -1
- package/dist/err.js +7 -1
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +2 -5
- package/dist/graph.js +83 -46
- package/dist/graph.js.map +1 -1
- package/dist/hcanon.js +9 -10
- package/dist/hcanon.js.map +1 -1
- package/dist/hints.js +94 -26
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.js +34 -0
- package/dist/jsonschema.js.map +1 -1
- package/dist/lang.js +593 -191
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +86 -6
- package/dist/lsp.js.map +1 -1
- package/dist/mcp.js +153 -6
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +42 -8
- package/dist/mod-tool.js.map +1 -1
- package/dist/mod.d.ts +4 -0
- package/dist/mod.js +97 -2
- package/dist/mod.js.map +1 -1
- package/dist/patch.d.ts +5 -0
- package/dist/patch.js +25 -25
- package/dist/patch.js.map +1 -1
- package/dist/provenance.d.ts +1 -0
- package/dist/provenance.js +2 -1
- package/dist/provenance.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/reach.d.ts +1 -0
- package/dist/reach.js +49 -21
- package/dist/reach.js.map +1 -1
- package/dist/relation.d.ts +4 -0
- package/dist/relation.js +125 -200
- package/dist/relation.js.map +1 -1
- package/dist/sig.d.ts +25 -0
- package/dist/sig.js +277 -0
- package/dist/sig.js.map +1 -0
- package/dist/sigdecl.d.ts +2 -0
- package/dist/sigdecl.js +11 -0
- package/dist/sigdecl.js.map +1 -0
- package/dist/siggate.d.ts +4 -0
- package/dist/siggate.js +90 -0
- package/dist/siggate.js.map +1 -0
- package/dist/std.js +75 -16
- package/dist/std.js.map +1 -1
- package/dist/subsume.js +57 -10
- package/dist/subsume.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.d.ts +2 -2
- package/dist/unify.js +93 -114
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +1 -2
- package/dist/utility.js +7 -61
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +12 -1
- package/dist/val/AggFuncVal.js +165 -3
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/BagVal.d.ts +2 -0
- package/dist/val/BagVal.js +56 -8
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/ConstraintVal.js +33 -5
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ContainerKindVal.d.ts +35 -0
- package/dist/val/ContainerKindVal.js +99 -0
- package/dist/val/ContainerKindVal.js.map +1 -0
- package/dist/val/CopyFuncVal.js +0 -7
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/DisjunctVal.d.ts +1 -2
- package/dist/val/DisjunctVal.js +146 -39
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/ExpectVal.js +37 -2
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +19 -8
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/GraphAtomVal.d.ts +39 -0
- package/dist/val/GraphAtomVal.js +184 -0
- package/dist/val/GraphAtomVal.js.map +1 -0
- package/dist/val/JunctionVal.js +22 -5
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/ListVal.js +27 -34
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/MapVal.d.ts +1 -0
- package/dist/val/MapVal.js +82 -35
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/PathFuncVal.d.ts +2 -2
- package/dist/val/PathFuncVal.js +75 -16
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PathVal.d.ts +25 -0
- package/dist/val/PathVal.js +150 -0
- package/dist/val/PathVal.js.map +1 -0
- package/dist/val/PlusOpVal.d.ts +2 -1
- package/dist/val/PlusOpVal.js +50 -35
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +2 -0
- package/dist/val/PrefVal.js +159 -32
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RecurseVal.d.ts +19 -0
- package/dist/val/RecurseVal.js +217 -0
- package/dist/val/RecurseVal.js.map +1 -0
- package/dist/val/RefVal.d.ts +2 -1
- package/dist/val/RefVal.js +105 -72
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +30 -7
- package/dist/val/ReferFuncVal.js +395 -94
- package/dist/val/ReferFuncVal.js.map +1 -1
- package/dist/val/ScalarKindVal.d.ts +4 -2
- package/dist/val/ScalarKindVal.js +12 -1
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/SuperFuncVal.d.ts +4 -2
- package/dist/val/SuperFuncVal.js +118 -14
- package/dist/val/SuperFuncVal.js.map +1 -1
- package/dist/val/TopVal.d.ts +1 -1
- package/dist/val/Val.d.ts +2 -3
- package/dist/val/Val.js +39 -20
- package/dist/val/Val.js.map +1 -1
- package/dist/val/arith.js +4 -1
- package/dist/val/arith.js.map +1 -1
- package/dist/vet.d.ts +1 -0
- package/dist/vet.js +32 -1
- package/dist/vet.js.map +1 -1
- package/dist/view.d.ts +90 -0
- package/dist/view.js +2168 -0
- package/dist/view.js.map +1 -0
- package/grammar/aontu.gbnf +18 -10
- package/grammar/aontu.lark +15 -10
- package/grammar/aontu.tmLanguage.json +184 -0
- package/package.json +10 -3
- package/skill/grammar-card.md +1 -2
- package/src/aontu.ts +37 -7
- package/src/cli.ts +553 -8
- package/src/ctx.ts +20 -0
- package/src/diff.ts +4 -2
- package/src/err.ts +8 -1
- package/src/graph.ts +125 -75
- package/src/hcanon.ts +9 -11
- package/src/hints.ts +112 -29
- package/src/jsonschema.ts +41 -0
- package/src/lang.ts +642 -203
- package/src/lsp.ts +75 -6
- package/src/mcp.ts +164 -6
- package/src/mod-tool.ts +48 -9
- package/src/mod.ts +110 -1
- package/src/patch.ts +31 -27
- package/src/provenance.ts +8 -1
- package/src/query.ts +4 -2
- package/src/reach.ts +52 -23
- package/src/relation.ts +139 -236
- package/src/sig.ts +345 -0
- package/src/sigdecl.ts +11 -0
- package/src/siggate.ts +144 -0
- package/src/std.ts +77 -16
- package/src/subsume.ts +59 -10
- package/src/unify.ts +102 -125
- package/src/utility.ts +7 -67
- package/src/val/AggFuncVal.ts +231 -4
- package/src/val/BagVal.ts +58 -9
- package/src/val/ConstraintVal.ts +34 -5
- package/src/val/ContainerKindVal.ts +158 -0
- package/src/val/CopyFuncVal.ts +0 -7
- package/src/val/DisjunctVal.ts +152 -40
- package/src/val/ExpectVal.ts +39 -4
- package/src/val/FuncBaseVal.ts +21 -8
- package/src/val/GraphAtomVal.ts +264 -0
- package/src/val/JunctionVal.ts +23 -6
- package/src/val/ListVal.ts +30 -37
- package/src/val/MapVal.ts +87 -39
- package/src/val/PathFuncVal.ts +107 -19
- package/src/val/PathVal.ts +221 -0
- package/src/val/PlusOpVal.ts +56 -37
- package/src/val/PrefVal.ts +186 -38
- package/src/val/RecurseVal.ts +285 -0
- package/src/val/RefVal.ts +105 -83
- package/src/val/ReferFuncVal.ts +445 -100
- package/src/val/ScalarKindVal.ts +12 -0
- package/src/val/SuperFuncVal.ts +137 -13
- package/src/val/TopVal.ts +1 -1
- package/src/val/Val.ts +44 -35
- package/src/val/arith.ts +4 -1
- package/src/vet.ts +41 -4
- package/src/view.ts +2882 -0
- package/dist/val/IdFuncVal.d.ts +0 -13
- package/dist/val/IdFuncVal.js +0 -54
- package/dist/val/IdFuncVal.js.map +0 -1
- package/src/val/IdFuncVal.ts +0 -91
package/src/lsp.ts
CHANGED
|
@@ -270,6 +270,7 @@ function initializeResult() {
|
|
|
270
270
|
textDocumentSync: 1,
|
|
271
271
|
hoverProvider: true,
|
|
272
272
|
completionProvider: {},
|
|
273
|
+
signatureHelpProvider: { triggerCharacters: ['(', ','] },
|
|
273
274
|
},
|
|
274
275
|
serverInfo: {
|
|
275
276
|
name: 'aontu-lsp',
|
|
@@ -279,6 +280,59 @@ function initializeResult() {
|
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
|
|
283
|
+
// signatureHelp: the declared signature of the ENCLOSING call, served
|
|
284
|
+
// from the registry (docs/design/SIGNATURES.0.md). The enclosing call
|
|
285
|
+
// is found lexically -- scan back from the cursor for the nearest
|
|
286
|
+
// unclosed '(' and read the word before it; commas at that depth
|
|
287
|
+
// count the active parameter, capped at the last slot so a rest tail
|
|
288
|
+
// stays active for every excess argument. Strings are skipped so a
|
|
289
|
+
// paren or comma inside one does not miscount, and the scan stops at
|
|
290
|
+
// the line start, a call being one line in practice.
|
|
291
|
+
function computeSignatureHelp(text: string, pos: any): any {
|
|
292
|
+
// Position to offset, under the full-sync model: lines are exactly
|
|
293
|
+
// the text's newlines.
|
|
294
|
+
const lines = text.split('\n')
|
|
295
|
+
const line = Math.max(0, Math.min(Number(pos.line) || 0, lines.length - 1))
|
|
296
|
+
let offset = 0
|
|
297
|
+
for (let li = 0; li < line; li++) {
|
|
298
|
+
offset += lines[li].length + 1
|
|
299
|
+
}
|
|
300
|
+
offset += Math.max(0, Math.min(Number(pos.character) || 0, lines[line].length))
|
|
301
|
+
let depth = 0
|
|
302
|
+
let commas = 0
|
|
303
|
+
let open = -1
|
|
304
|
+
for (let i = offset - 1; 0 <= i; i--) {
|
|
305
|
+
const c = text[i]
|
|
306
|
+
if ('"' === c || "'" === c) {
|
|
307
|
+
for (i--; 0 <= i && text[i] !== c; i--) { }
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
if (')' === c) { depth++ }
|
|
311
|
+
else if ('(' === c) {
|
|
312
|
+
if (0 === depth) { open = i; break }
|
|
313
|
+
depth--
|
|
314
|
+
}
|
|
315
|
+
else if (',' === c && 0 === depth) { commas++ }
|
|
316
|
+
else if ('\n' === c && 0 === depth) { break }
|
|
317
|
+
}
|
|
318
|
+
if (0 > open) { return null }
|
|
319
|
+
let start = open
|
|
320
|
+
while (0 < start && /[a-zA-Z0-9_]/.test(text[start - 1])) { start-- }
|
|
321
|
+
const name = text.slice(start, open)
|
|
322
|
+
const sig = funcSig[name]
|
|
323
|
+
if (undefined === sig) { return null }
|
|
324
|
+
const last = sig.args.length - 1
|
|
325
|
+
return {
|
|
326
|
+
signatures: [{
|
|
327
|
+
label: renderSig(sig),
|
|
328
|
+
parameters: sig.args.map((a) => ({ label: renderSigArg(a) })),
|
|
329
|
+
}],
|
|
330
|
+
activeSignature: 0,
|
|
331
|
+
activeParameter: Math.min(commas, 0 <= last ? last : 0),
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
282
336
|
function publishDiagnosticsMsg(uri: string, diagnostics: Diagnostic[]): OutMessage {
|
|
283
337
|
return {
|
|
284
338
|
jsonrpc: '2.0',
|
|
@@ -457,23 +511,26 @@ type CompletionItem = {
|
|
|
457
511
|
detail?: string
|
|
458
512
|
}
|
|
459
513
|
|
|
514
|
+
import { funcSig, renderSig, renderSigArg } from './sig'
|
|
515
|
+
|
|
460
516
|
// LSP CompletionItemKind subset.
|
|
461
517
|
const COMPLETION_FUNCTION = 3
|
|
462
518
|
const COMPLETION_KEYWORD = 14
|
|
463
519
|
|
|
464
|
-
// The
|
|
520
|
+
// The built-in functions. Kept in sync with the engine by
|
|
465
521
|
// `lsp.test.ts`, which asserts each is recognised and no others are.
|
|
466
522
|
// The Go port derives its list from the engine's own name set
|
|
467
523
|
// (`BuiltinFuncNames`, go/func.go), which is why a name added there
|
|
468
524
|
// and forgotten here diverges silently — as `id` and `refer` did
|
|
469
525
|
// between G4 phases 1/2 and G8 phase 1.
|
|
470
526
|
const BUILTIN_FUNCS = [
|
|
471
|
-
'above', 'add', 'below', 'close', 'copy', 'deprecate', 'div',
|
|
527
|
+
'above', 'acyclic', 'add', 'below', 'close', 'copy', 'deprecate', 'div',
|
|
528
|
+
'each',
|
|
472
529
|
'filter', 'greatest',
|
|
473
|
-
'hide', '
|
|
474
|
-
'match', 'max', 'min', 'mod', 'move', 'mul', 'must', 'neq', 'open',
|
|
530
|
+
'hide', 'inverse', 'join', 'key', 'least', 'length', 'list', 'lower',
|
|
531
|
+
'map', 'match', 'max', 'min', 'mod', 'move', 'mul', 'must', 'neq', 'open',
|
|
475
532
|
'pack', 'path', 'pick',
|
|
476
|
-
'pref', 're', 'refer', 'rem', 'sub', 'sum', 'super', 'type', 'unique',
|
|
533
|
+
'pref', 're', 'refer', 'rel', 'rem', 'sub', 'sum', 'super', 'type', 'unique',
|
|
477
534
|
'upper',
|
|
478
535
|
]
|
|
479
536
|
|
|
@@ -493,7 +550,10 @@ const LITERAL_KEYWORDS = ['_', 'true', 'false', 'null', 'top']
|
|
|
493
550
|
function computeCompletions(): CompletionItem[] {
|
|
494
551
|
const out: CompletionItem[] = []
|
|
495
552
|
for (const f of BUILTIN_FUNCS) {
|
|
496
|
-
|
|
553
|
+
// The detail is the rendered SIGNATURE (docs/design/SIGNATURES.0.md)
|
|
554
|
+
// -- the same renderer the hints and the docs table use, so the
|
|
555
|
+
// completion list cannot drift from the declaration.
|
|
556
|
+
out.push({ label: f, kind: COMPLETION_FUNCTION, detail: renderSig(funcSig[f]) })
|
|
497
557
|
}
|
|
498
558
|
for (const k of KIND_KEYWORDS) {
|
|
499
559
|
out.push({ label: k, kind: COMPLETION_KEYWORD, detail: 'scalar kind' })
|
|
@@ -695,6 +755,15 @@ class LspHandler {
|
|
|
695
755
|
case 'textDocument/completion':
|
|
696
756
|
return [{ jsonrpc: '2.0', id: msg.id, result: computeCompletions() }]
|
|
697
757
|
|
|
758
|
+
case 'textDocument/signatureHelp': {
|
|
759
|
+
const uri = msg.params?.textDocument?.uri
|
|
760
|
+
const pos = msg.params?.position
|
|
761
|
+
const text = null != uri ? this.docs.get(uri) : undefined
|
|
762
|
+
const help = (null != text && null != pos)
|
|
763
|
+
? computeSignatureHelp(text, pos) : null
|
|
764
|
+
return [{ jsonrpc: '2.0', id: msg.id, result: help }]
|
|
765
|
+
}
|
|
766
|
+
|
|
698
767
|
default:
|
|
699
768
|
// Unknown request (has an id): reply method-not-found. Unknown
|
|
700
769
|
// notification: ignore.
|
package/src/mcp.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { trimCheck } from './trim'
|
|
|
48
48
|
import { jsonSchema } from './jsonschema'
|
|
49
49
|
import { relationCheck } from './relation'
|
|
50
50
|
import { reachCheck } from './reach'
|
|
51
|
+
import { view } from './view'
|
|
51
52
|
import { patch } from './patch'
|
|
52
53
|
|
|
53
54
|
|
|
@@ -114,7 +115,9 @@ export type ToolDef = {
|
|
|
114
115
|
// applied by each tool for itself. That is deliberate: four of the six
|
|
115
116
|
// original tools once called the library with no profile at all, so a
|
|
116
117
|
// served `@"x.js"` was require()d in the server process, while the
|
|
117
|
-
// module header claimed confinement.
|
|
118
|
+
// module header claimed confinement. (That particular include is
|
|
119
|
+
// refused outright now -- ADR-012 -- but a served document could still
|
|
120
|
+
// read every file the server can.) A tool that must remember to
|
|
118
121
|
// confine itself is a tool that eventually forgets, and the forgetting
|
|
119
122
|
// is silent. With the profile arriving as an argument, a tool cannot
|
|
120
123
|
// run unconfined without visibly discarding it.
|
|
@@ -408,7 +411,7 @@ const TOOLS: ToolDef[] = [
|
|
|
408
411
|
name: 'relations',
|
|
409
412
|
description:
|
|
410
413
|
'Check the declared relations of a finished model: acyclicity ' +
|
|
411
|
-
'and inverse consistency over the
|
|
414
|
+
'and inverse consistency over the link edge set. Returns ' +
|
|
412
415
|
'verdict (pass | fail | error) and relation findings.',
|
|
413
416
|
properties: {
|
|
414
417
|
source: { type: 'string', description: 'The document' },
|
|
@@ -428,16 +431,16 @@ const TOOLS: ToolDef[] = [
|
|
|
428
431
|
{
|
|
429
432
|
name: 'reaches',
|
|
430
433
|
description:
|
|
431
|
-
'Ask whether one
|
|
434
|
+
'Ask whether one node reaches another over the link graph, ' +
|
|
432
435
|
'at any remove — the closure question `relations` cannot ask one ' +
|
|
433
436
|
'edge at a time (blast radius, containment). Returns verdict ' +
|
|
434
437
|
'(reaches | unreachable | error) and, when it reaches, a shortest ' +
|
|
435
|
-
'path. Transitive, not reflexive:
|
|
438
|
+
'path. Transitive, not reflexive: a node reaches itself only ' +
|
|
436
439
|
'through a cycle.',
|
|
437
440
|
properties: {
|
|
438
441
|
source: { type: 'string', description: 'The document' },
|
|
439
|
-
from: { type: 'string', description: 'The
|
|
440
|
-
to: { type: 'string', description: 'The
|
|
442
|
+
from: { type: 'string', description: 'The node path to start at' },
|
|
443
|
+
to: { type: 'string', description: 'The node path to look for' },
|
|
441
444
|
relation: {
|
|
442
445
|
type: 'string',
|
|
443
446
|
description: 'Follow only edges under this relation (optional)',
|
|
@@ -452,6 +455,161 @@ const TOOLS: ToolDef[] = [
|
|
|
452
455
|
relation: null == a.relation ? undefined : str(a.relation),
|
|
453
456
|
}),
|
|
454
457
|
},
|
|
458
|
+
{
|
|
459
|
+
name: 'view',
|
|
460
|
+
description:
|
|
461
|
+
'Draw a figure of the document as deterministic text. Kinds: ' +
|
|
462
|
+
'tree (the dependency tree of a relation), matrix (the ' +
|
|
463
|
+
'dependency matrix, canon or partition order, --closure), graph ' +
|
|
464
|
+
'(node-link, as mermaid, dot or er), layer (the architecture ' +
|
|
465
|
+
'layers as stacked bands, groupBy naming the layer field), sets ' +
|
|
466
|
+
'(the set-intersection ' +
|
|
467
|
+
'panel over a set family), layers (which document contributed ' +
|
|
468
|
+
'which path), ladder (the meet ladder at a path). Returns ' +
|
|
469
|
+
'verdict (rendered | lossy | error), kind, the text, and the ' +
|
|
470
|
+
'loss report. The poset kind compares several files and is CLI ' +
|
|
471
|
+
'only.',
|
|
472
|
+
properties: {
|
|
473
|
+
source: { type: 'string', description: 'The document' },
|
|
474
|
+
kind: {
|
|
475
|
+
type: 'string',
|
|
476
|
+
description:
|
|
477
|
+
'The figure to draw: tree (the default), matrix, graph, layer, ' +
|
|
478
|
+
'sets, layers or ladder',
|
|
479
|
+
},
|
|
480
|
+
as: {
|
|
481
|
+
type: 'string',
|
|
482
|
+
description:
|
|
483
|
+
'The target grammar: text, mermaid, dot, er or svg, per kind ' +
|
|
484
|
+
'(optional; the kind\'s default otherwise)',
|
|
485
|
+
},
|
|
486
|
+
at: {
|
|
487
|
+
type: 'string',
|
|
488
|
+
description:
|
|
489
|
+
'Restrict the figure to nodes under this path; the path the ' +
|
|
490
|
+
'ladder draws (optional)',
|
|
491
|
+
},
|
|
492
|
+
relation: {
|
|
493
|
+
type: 'string',
|
|
494
|
+
description:
|
|
495
|
+
'Draw the tree or matrix over this relation only; keep this ' +
|
|
496
|
+
'predicate in the graph (optional)',
|
|
497
|
+
},
|
|
498
|
+
root: {
|
|
499
|
+
type: 'array',
|
|
500
|
+
items: { type: 'string' },
|
|
501
|
+
description:
|
|
502
|
+
'Draw only the subtrees under these node paths (optional)',
|
|
503
|
+
},
|
|
504
|
+
order: {
|
|
505
|
+
type: 'string',
|
|
506
|
+
description: 'matrix: canon (the default) or partition (optional)',
|
|
507
|
+
},
|
|
508
|
+
closure: {
|
|
509
|
+
type: 'boolean',
|
|
510
|
+
description: 'matrix: mark transitively reachable cells (optional)',
|
|
511
|
+
},
|
|
512
|
+
groupBy: {
|
|
513
|
+
type: 'string',
|
|
514
|
+
description:
|
|
515
|
+
'graph: one subgraph per value of this field (optional); ' +
|
|
516
|
+
'layer: one band per value (required)',
|
|
517
|
+
},
|
|
518
|
+
label: {
|
|
519
|
+
type: 'string',
|
|
520
|
+
description: 'graph: label each node with this field (optional)',
|
|
521
|
+
},
|
|
522
|
+
edges: {
|
|
523
|
+
type: 'string',
|
|
524
|
+
description:
|
|
525
|
+
'layer: which of the relation\'s edges to draw over the bands -- ' +
|
|
526
|
+
'upward (the violations; the default for text and svg), all ' +
|
|
527
|
+
'(mermaid\'s default) or none (optional)',
|
|
528
|
+
},
|
|
529
|
+
layers: {
|
|
530
|
+
type: 'array',
|
|
531
|
+
items: { type: 'string' },
|
|
532
|
+
description: 'layer: the bands in this order, top first (optional)',
|
|
533
|
+
},
|
|
534
|
+
sets: {
|
|
535
|
+
type: 'string',
|
|
536
|
+
description: 'sets: the map whose keys are the sets',
|
|
537
|
+
},
|
|
538
|
+
member: {
|
|
539
|
+
type: 'string',
|
|
540
|
+
description: 'sets: the field holding each set\'s members',
|
|
541
|
+
},
|
|
542
|
+
universe: {
|
|
543
|
+
type: 'string',
|
|
544
|
+
description: 'sets: the full element domain (optional)',
|
|
545
|
+
},
|
|
546
|
+
depth: {
|
|
547
|
+
type: 'integer',
|
|
548
|
+
description: 'doc: how many levels of key to draw (default 3)',
|
|
549
|
+
},
|
|
550
|
+
maxRows: {
|
|
551
|
+
type: 'integer',
|
|
552
|
+
description: 'Refuse a figure above this many rows (default 60)',
|
|
553
|
+
},
|
|
554
|
+
style: {
|
|
555
|
+
type: 'string',
|
|
556
|
+
description:
|
|
557
|
+
'How the figure carries the meaning of its marks: none, ansi ' +
|
|
558
|
+
'(SGR escapes, text only) or css (classes and the embedded ' +
|
|
559
|
+
'stylesheet, svg only). Absent means the profile\'s own ' +
|
|
560
|
+
'default -- svg keeps its stylesheet, everything else has no ' +
|
|
561
|
+
'mechanism. There is no auto here: resolving it needs a ' +
|
|
562
|
+
'terminal, which a server does not have (optional)',
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
required: ['source'],
|
|
566
|
+
docs: ['source'],
|
|
567
|
+
check: (a) => {
|
|
568
|
+
const kinds = ['doc', 'tree', 'matrix', 'graph', 'layer', 'sets',
|
|
569
|
+
'layers', 'ladder']
|
|
570
|
+
if (null != a.kind && !kinds.includes(a.kind)) {
|
|
571
|
+
return `kind must be one of ${kinds.join(', ')}, not ${JSON.stringify(a.kind)}`
|
|
572
|
+
}
|
|
573
|
+
const profiles = ['text', 'mermaid', 'dot', 'er', 'svg']
|
|
574
|
+
if (null != a.as && !profiles.includes(a.as)) {
|
|
575
|
+
return `as must be one of ${profiles.join(', ')}, not ${JSON.stringify(a.as)}`
|
|
576
|
+
}
|
|
577
|
+
const edges = ['upward', 'all', 'none']
|
|
578
|
+
if (null != a.edges && !edges.includes(a.edges)) {
|
|
579
|
+
return `edges must be one of ${edges.join(', ')}, not ${JSON.stringify(a.edges)}`
|
|
580
|
+
}
|
|
581
|
+
const styles = ['none', 'ansi', 'css']
|
|
582
|
+
if (null != a.style && !styles.includes(a.style)) {
|
|
583
|
+
return `style must be one of ${styles.join(', ')}, not ${JSON.stringify(a.style)}`
|
|
584
|
+
}
|
|
585
|
+
return undefined
|
|
586
|
+
},
|
|
587
|
+
refuse: (a, finding) =>
|
|
588
|
+
({ verdict: 'error', kind: a.kind ?? 'tree', loss: [], errors: [finding] }),
|
|
589
|
+
run: (a, trust, paths) =>
|
|
590
|
+
view(str(a.source), {
|
|
591
|
+
kind: null == a.kind ? 'tree' : a.kind,
|
|
592
|
+
as: null == a.as ? undefined : a.as,
|
|
593
|
+
at: null == a.at ? undefined : str(a.at),
|
|
594
|
+
path: paths.source,
|
|
595
|
+
trust,
|
|
596
|
+
relation: null == a.relation ? undefined : str(a.relation),
|
|
597
|
+
relations: null == a.relation ? undefined : [str(a.relation)],
|
|
598
|
+
roots: Array.isArray(a.root) ? a.root.map(str) : undefined,
|
|
599
|
+
order: null == a.order ? undefined : a.order,
|
|
600
|
+
closure: true === a.closure,
|
|
601
|
+
groupBy: null == a.groupBy ? undefined : str(a.groupBy),
|
|
602
|
+
label: null == a.label ? undefined : str(a.label),
|
|
603
|
+
layers: Array.isArray(a.layers) ? a.layers.map(str) : undefined,
|
|
604
|
+
edges: null == a.edges ? undefined : a.edges,
|
|
605
|
+
sets: null == a.sets ? undefined : str(a.sets),
|
|
606
|
+
member: null == a.member ? undefined : str(a.member),
|
|
607
|
+
universe: null == a.universe ? undefined : str(a.universe),
|
|
608
|
+
depth: 'number' === typeof a.depth ? a.depth : undefined,
|
|
609
|
+
maxRows: 'number' === typeof a.maxRows ? a.maxRows : undefined,
|
|
610
|
+
style: null == a.style ? undefined : a.style,
|
|
611
|
+
}),
|
|
612
|
+
},
|
|
455
613
|
{
|
|
456
614
|
name: 'hash',
|
|
457
615
|
description:
|
package/src/mod-tool.ts
CHANGED
|
@@ -24,7 +24,9 @@ import {
|
|
|
24
24
|
} from 'node:fs'
|
|
25
25
|
import { join as pathJoin, dirname as pathDirname } from 'node:path'
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
parseModuleRef, validateModulePath, moduleDir, lockJson,
|
|
29
|
+
} from './mod'
|
|
28
30
|
import { subsume } from './subsume'
|
|
29
31
|
import type { ModuleRef } from './mod'
|
|
30
32
|
|
|
@@ -162,6 +164,30 @@ export function versionCompare(a: string, b: string): number {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
|
|
167
|
+
// A dependency or lockfile key as a module ref this tooling may ACT
|
|
168
|
+
// on, or undefined.
|
|
169
|
+
//
|
|
170
|
+
// Two ways to be unusable, one answer. A key that is not module-shaped
|
|
171
|
+
// names nothing the resolver can find; a key that is shaped but whose
|
|
172
|
+
// path cannot legally be a directory (`..` in it, a reserved device
|
|
173
|
+
// name) must not be turned into one, which is the whole of the
|
|
174
|
+
// traversal fix on this side. Both land in the caller's `missing`
|
|
175
|
+
// bucket, because from the report's point of view they are the same
|
|
176
|
+
// fact: the lockfile names something that cannot be resolved here.
|
|
177
|
+
//
|
|
178
|
+
// A STALE LOCKFILE IS THE REASON THIS EXISTS AT ALL. `resolveModule`
|
|
179
|
+
// gates the evaluator, but `tidy`, `verify` and `vendor` read a
|
|
180
|
+
// lockfile straight off disk -- one that may have been committed
|
|
181
|
+
// before the gate existed -- so the gate has to be here too.
|
|
182
|
+
function usableRef(mod: string): ModuleRef | undefined {
|
|
183
|
+
const ref = parseModuleRef(mod)
|
|
184
|
+
if (undefined === ref || undefined !== validateModulePath(ref.path)) {
|
|
185
|
+
return undefined
|
|
186
|
+
}
|
|
187
|
+
return ref
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
165
191
|
// The directory a module is in, in the local stores: the project's
|
|
166
192
|
// vendor tree first, then the cache under the hash the lockfile pins.
|
|
167
193
|
function storeDir(
|
|
@@ -245,11 +271,11 @@ export function modTidy(root: string, options: ModToolOptions): ModTidyReport {
|
|
|
245
271
|
}
|
|
246
272
|
selected[mod] = want
|
|
247
273
|
|
|
248
|
-
const ref =
|
|
274
|
+
const ref = usableRef(mod)
|
|
249
275
|
if (undefined === ref) {
|
|
250
|
-
// A dependency key
|
|
251
|
-
// resolver can find, which is the same answer as a module
|
|
252
|
-
// is not there.
|
|
276
|
+
// A dependency key this tooling cannot act on names nothing
|
|
277
|
+
// this resolver can find, which is the same answer as a module
|
|
278
|
+
// that is not there (see usableRef).
|
|
253
279
|
missing.push(mod)
|
|
254
280
|
continue
|
|
255
281
|
}
|
|
@@ -278,7 +304,7 @@ export function modTidy(root: string, options: ModToolOptions): ModTidyReport {
|
|
|
278
304
|
if (missing.includes(mod)) {
|
|
279
305
|
continue
|
|
280
306
|
}
|
|
281
|
-
const ref =
|
|
307
|
+
const ref = usableRef(mod) as ModuleRef
|
|
282
308
|
const dir = storeDir(root, ref, previous[mod]?.canon ?? '', options) as string
|
|
283
309
|
const main = pathJoin(dir, mainOf(dir, options))
|
|
284
310
|
// RECOMPUTED, never carried over: the pin is what the module in
|
|
@@ -374,7 +400,7 @@ export function modVerify(root: string, options: ModToolOptions):
|
|
|
374
400
|
.filter((mod) => null == locked[mod]).sort()
|
|
375
401
|
|
|
376
402
|
for (const mod of Object.keys(locked).sort()) {
|
|
377
|
-
const ref =
|
|
403
|
+
const ref = usableRef(mod)
|
|
378
404
|
if (undefined === ref) {
|
|
379
405
|
missing.push(mod)
|
|
380
406
|
continue
|
|
@@ -422,8 +448,10 @@ export function modVendor(root: string, options: ModToolOptions):
|
|
|
422
448
|
const vendored: string[] = []
|
|
423
449
|
const missing: string[] = []
|
|
424
450
|
|
|
451
|
+
const vendorRoot = pathJoin(root, 'aon_vendor')
|
|
452
|
+
|
|
425
453
|
for (const mod of Object.keys(locked).sort()) {
|
|
426
|
-
const ref =
|
|
454
|
+
const ref = usableRef(mod)
|
|
427
455
|
if (undefined === ref) {
|
|
428
456
|
missing.push(mod)
|
|
429
457
|
continue
|
|
@@ -435,7 +463,18 @@ export function modVendor(root: string, options: ModToolOptions):
|
|
|
435
463
|
continue
|
|
436
464
|
}
|
|
437
465
|
|
|
438
|
-
|
|
466
|
+
// WHY THERE IS NO CONTAINMENT CHECK ON `to`, at the one write site
|
|
467
|
+
// that copied a tree outside the project: `usableRef` above is the
|
|
468
|
+
// gate, and after it a store path CANNOT escape. Every element is
|
|
469
|
+
// non-empty and neither begins nor ends with `.`, so none is `.`
|
|
470
|
+
// or `..`; MODULE_RE's element class admits no `/`, no `\` and no
|
|
471
|
+
// leading slash, so no element can re-root the join. A second
|
|
472
|
+
// lexical check here would be unreachable code, which ADR-002 asks
|
|
473
|
+
// to be deleted rather than excluded -- so the invariant is pinned
|
|
474
|
+
// by a test that drives the escape through this verb instead.
|
|
475
|
+
// ANY NEW CALLER of moduleDir must go through usableRef too;
|
|
476
|
+
// `mod get` is the next one.
|
|
477
|
+
const to = moduleDir(vendorRoot, ref)
|
|
439
478
|
if (from !== to) {
|
|
440
479
|
copyTree(from, to)
|
|
441
480
|
}
|
package/src/mod.ts
CHANGED
|
@@ -73,9 +73,94 @@ export function parseModuleRef(spec: string): ModuleRef | undefined {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
// SHAPE IS NOT VALIDITY, and the gap between them was a hole. MODULE_RE
|
|
77
|
+
// answers "does this string route to the module resolver" -- a
|
|
78
|
+
// ROUTING predicate, and it must stay one, because anything it rejects
|
|
79
|
+
// falls through to the file leg and a stricter pattern would silently
|
|
80
|
+
// re-route documents that work today. But its element class
|
|
81
|
+
// `[A-Za-z0-9._-]` admits `..`, and `moduleDir` joins elements with
|
|
82
|
+
// pathJoin, which CLEANS `..` rather than refusing it:
|
|
83
|
+
//
|
|
84
|
+
// moduleDir('/store/aon_vendor', 'corp.example/../../etc/passwd@1')
|
|
85
|
+
// -> /store/etc/passwd@1
|
|
86
|
+
//
|
|
87
|
+
// `mod vendor` then copied a tree THERE, outside the project entirely,
|
|
88
|
+
// and reported `verdict: ok`. So validity is a separate question asked
|
|
89
|
+
// separately, after the shape matched, and asked at every site that
|
|
90
|
+
// turns a module path into a directory.
|
|
91
|
+
//
|
|
92
|
+
// The rules are Go's (golang.org/x/mod/module.CheckPath), for the
|
|
93
|
+
// reason Go has them: a module path becomes a real directory on every
|
|
94
|
+
// platform the toolchain runs on, so it must be a legal one everywhere.
|
|
95
|
+
export const MODULE_MAX_PATH = 512
|
|
96
|
+
export const MODULE_MAX_ELEMS = 32
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
// Windows refuses these as file names whatever the extension, so a
|
|
100
|
+
// module path containing one cannot be materialised there at all. The
|
|
101
|
+
// check is on the element up to its first dot, which is where Windows
|
|
102
|
+
// stops looking too.
|
|
103
|
+
const RESERVED_ELEMS = new Set([
|
|
104
|
+
'con', 'prn', 'aux', 'nul',
|
|
105
|
+
'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
|
|
106
|
+
'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// Why a module path may not be used as a directory, or undefined when
|
|
111
|
+
// it may. The reason is user-facing: it goes in the refusal, because a
|
|
112
|
+
// path refused without saying which rule it broke is a puzzle.
|
|
113
|
+
export function validateModulePath(path: string): string | undefined {
|
|
114
|
+
if (MODULE_MAX_PATH < path.length) {
|
|
115
|
+
return 'longer than ' + MODULE_MAX_PATH + ' characters'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const elems = path.split('/')
|
|
119
|
+
if (MODULE_MAX_ELEMS < elems.length) {
|
|
120
|
+
return 'more than ' + MODULE_MAX_ELEMS + ' elements'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const elem of elems) {
|
|
124
|
+
if ('' === elem) {
|
|
125
|
+
return 'an element is empty'
|
|
126
|
+
}
|
|
127
|
+
// This one rule kills `.` and `..` -- the traversal -- along with
|
|
128
|
+
// `.hidden` and `trailing.`, exactly as Go's does. Stating it as
|
|
129
|
+
// the rule rather than as "no `..`" is deliberate: a check that
|
|
130
|
+
// named the two dangerous spellings would miss the next one.
|
|
131
|
+
if (elem.startsWith('.') || elem.endsWith('.')) {
|
|
132
|
+
return 'an element begins or ends with "."'
|
|
133
|
+
}
|
|
134
|
+
if (RESERVED_ELEMS.has(elem.split('.')[0].toLowerCase())) {
|
|
135
|
+
return 'an element is a reserved device name'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
// An element as it is spelled ON DISK. Uppercase is escaped to
|
|
144
|
+
// `!`+lowercase, Go's rule (go.dev/ref/mod, module proxy protocol) and
|
|
145
|
+
// for Go's reason: `github.com/Alice/Widgets` and
|
|
146
|
+
// `github.com/alice/widgets` are two module identities and, on macOS
|
|
147
|
+
// and Windows, ONE directory -- so without this the second module
|
|
148
|
+
// fetched silently clobbers the first, and an unpinned import resolves
|
|
149
|
+
// to whichever won.
|
|
150
|
+
//
|
|
151
|
+
// The WRITTEN path stays the identity; only the directory is escaped.
|
|
152
|
+
function escapeElem(elem: string): string {
|
|
153
|
+
return elem.replace(/[A-Z]/g, (c) => '!' + c.toLowerCase())
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
76
157
|
// The directory a module's files live in, under a store root.
|
|
158
|
+
//
|
|
159
|
+
// Callers must have validated the path (validateModulePath); this
|
|
160
|
+
// function cannot refuse, because it answers a location rather than a
|
|
161
|
+
// question, and every caller has a refusal shape of its own.
|
|
77
162
|
export function moduleDir(store: string, ref: ModuleRef): string {
|
|
78
|
-
return pathJoin(store, ...ref.path.split('/')) + '@' + ref.major
|
|
163
|
+
return pathJoin(store, ...ref.path.split('/').map(escapeElem)) + '@' + ref.major
|
|
79
164
|
}
|
|
80
165
|
|
|
81
166
|
|
|
@@ -248,6 +333,19 @@ export type ModuleFound = {
|
|
|
248
333
|
}
|
|
249
334
|
|
|
250
335
|
|
|
336
|
+
// EVERY code `resolveModule` can refuse with. The list lives HERE,
|
|
337
|
+
// beside the refusals themselves, because the parse layer has to
|
|
338
|
+
// recognise them to turn the throw into a parse-stage nil
|
|
339
|
+
// (ts/src/lang.ts) -- and when that list was written out longhand
|
|
340
|
+
// there, adding a fourth code left it unhandled and the refusal
|
|
341
|
+
// surfaced as `unexpected error` instead of the message it carries.
|
|
342
|
+
// The Go port has no such list (recordModErr takes any code), which is
|
|
343
|
+
// why only this side could drift.
|
|
344
|
+
export const MODULE_REFUSAL_CODES: ReadonlySet<string> = new Set([
|
|
345
|
+
'module_path', 'module_missing', 'module_integrity', 'module_depth',
|
|
346
|
+
])
|
|
347
|
+
|
|
348
|
+
|
|
251
349
|
// A refusal that carries its code to the parse layer, exactly as a
|
|
252
350
|
// denied include does (makeModelResolver's `deny`): the resolver
|
|
253
351
|
// THROWS, so a bare-member module import cannot vanish in the merge and
|
|
@@ -266,6 +364,17 @@ export function resolveModule(
|
|
|
266
364
|
fs: ModuleFs,
|
|
267
365
|
options: ModuleOptions,
|
|
268
366
|
): ModuleFound {
|
|
367
|
+
// THE PATH IS CHECKED BEFORE ANYTHING IS BUILT FROM IT. This is
|
|
368
|
+
// first because it is a question about the REQUEST, not about the
|
|
369
|
+
// state of the machine: a path that cannot legally be a directory is
|
|
370
|
+
// refused identically whether or not the module is present, and
|
|
371
|
+
// whether or not the depth bound is near.
|
|
372
|
+
const badpath = validateModulePath(ref.path)
|
|
373
|
+
if (undefined !== badpath) {
|
|
374
|
+
refuse('module_path',
|
|
375
|
+
'module path: ' + ref.path + '@' + ref.major + ' (' + badpath + ')')
|
|
376
|
+
}
|
|
377
|
+
|
|
269
378
|
if (MODULE_MAX_DEPTH <= (options.depth ?? 0)) {
|
|
270
379
|
refuse('module_depth',
|
|
271
380
|
'module depth: ' + ref.path + '@' + ref.major +
|
package/src/patch.ts
CHANGED
|
@@ -333,34 +333,36 @@ function editableLiteral(
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
return verifiedSite(overlaySrc, path, literals[0])
|
|
337
|
+
}
|
|
337
338
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
339
|
+
|
|
340
|
+
// THE SPAN MUST CHECK OUT before anything splices. The refusal arm is
|
|
341
|
+
// unreachable through `patch` since ADR-018: the pipe (`x: hello |>
|
|
342
|
+
// upper`) was the one spelling that synthesised a contribution the
|
|
343
|
+
// parser never sited, and denying includes means every WRITTEN
|
|
344
|
+
// contribution's coordinates describe this text by construction. The
|
|
345
|
+
// verification is kept rather than deleted — splicing without it would
|
|
346
|
+
// corrupt the file (a contribution with no `src` would splice ZERO
|
|
347
|
+
// characters, INSERTING the new value into the middle of a line) — and
|
|
348
|
+
// this last step is its own exported seam so the refusal can be tested
|
|
349
|
+
// directly, against conjuncts the engine would never produce, on the
|
|
350
|
+
// same footing as `spanHolds` itself.
|
|
351
|
+
export function verifiedSite(
|
|
352
|
+
overlaySrc: string,
|
|
353
|
+
path: string,
|
|
354
|
+
one: WhyConjunct,
|
|
355
|
+
): { site: PatchReplacement | undefined, finding: VetFinding | undefined } {
|
|
355
356
|
if (!spanHolds(overlaySrc, one.site, one.src)) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
357
|
+
const finding = notEditable('patch_span_mismatch', path,
|
|
358
|
+
'the overlay does not hold ' + JSON.stringify(one.src) + ' at ' +
|
|
359
|
+
one.site.row + ':' + one.site.col + ' (len ' + one.site.len +
|
|
360
|
+
'), so the span cannot be verified before writing',
|
|
361
|
+
[one])
|
|
362
|
+
// The one internal-class refusal: a recorded span failing to check
|
|
363
|
+
// out is the engine's fault, never the document's.
|
|
364
|
+
finding.class = 'internal'
|
|
365
|
+
return { site: undefined, finding }
|
|
364
366
|
}
|
|
365
367
|
|
|
366
368
|
// DOES THE SPAN MEAN THE WHOLE CONTRIBUTION?
|
|
@@ -479,7 +481,9 @@ function notEditable(
|
|
|
479
481
|
): VetFinding {
|
|
480
482
|
return {
|
|
481
483
|
code,
|
|
482
|
-
|
|
484
|
+
// Always `reference`; the one internal-class refusal
|
|
485
|
+
// (patch_span_mismatch) overrides at its call site.
|
|
486
|
+
class: 'reference',
|
|
483
487
|
severity: 'warning',
|
|
484
488
|
path,
|
|
485
489
|
// No separate `note`: the renderer prints both, and a note that
|