aontu 0.60.0 → 0.61.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 +1 -1
- package/dist/aontu.js +1 -1
- package/dist/cli.js +59 -23
- package/dist/cli.js.map +1 -1
- package/dist/format.d.ts +1 -0
- package/dist/format.js +97 -11
- package/dist/format.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/std.js +8 -0
- package/dist/std.js.map +1 -1
- package/dist/template.d.ts +2 -1
- package/dist/template.js +51 -15
- package/dist/template.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/aontu.ts +1 -1
- package/src/cli.ts +65 -25
- package/src/format.ts +116 -11
- package/src/std.ts +8 -0
- package/src/template.ts +55 -15
package/src/cli.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
renderProfile,
|
|
28
28
|
} from './aontu'
|
|
29
29
|
import type { RenderCoverage, RenderReport } from './render'
|
|
30
|
-
import { desugarTemplate, resugarTemplate, markerFor } from './template'
|
|
30
|
+
import { desugarTemplate, resugarTemplate, templateOutputs, markerFor } from './template'
|
|
31
31
|
import { outsideRoot } from './mcp'
|
|
32
32
|
import { sarifReport } from './report-sarif'
|
|
33
33
|
import { main as lspMain } from './lsp-server'
|
|
@@ -83,7 +83,7 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
83
83
|
aontu why <path> [options] <file>
|
|
84
84
|
aontu set <path>=<value>... --entry <file> --overlay <file>
|
|
85
85
|
aontu agentsmd [--write <AGENTS.md>] <file>
|
|
86
|
-
aontu fmt [-w|-l|--check|-d|--lint] <file>...
|
|
86
|
+
aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>...
|
|
87
87
|
aontu lsp
|
|
88
88
|
aontu mcp [--root <dir>]
|
|
89
89
|
|
|
@@ -351,10 +351,18 @@ Fmt options:
|
|
|
351
351
|
--lint Report the style findings, key case and repeated
|
|
352
352
|
shapes, on standard error, and print nothing else
|
|
353
353
|
--strict With --lint, and exit 1 when there is a finding
|
|
354
|
+
--marker <t> The file is a generator, and this is its marker
|
|
355
|
+
(default //-, and #- --- /*- by extension)
|
|
354
356
|
|
|
355
357
|
The fmt verb prints one document in the agreed form; with no file it
|
|
356
358
|
reads standard input. Several files need one of the options above.
|
|
357
359
|
|
|
360
|
+
A file whose extension is not .aon is a GENERATOR, as it is for render:
|
|
361
|
+
the aontu its marker lines carry is formatted, the marker stands at the
|
|
362
|
+
left margin with the aontu indented after it, and every line of output
|
|
363
|
+
is held on a line of its own. A file with no marker line in it is
|
|
364
|
+
another language's, and is refused.
|
|
365
|
+
|
|
358
366
|
Fmt exit codes: 0 formatted or clean, 1 a --check file would change or
|
|
359
367
|
a --strict finding, 2 usage, 4 a document does not parse.
|
|
360
368
|
|
|
@@ -3945,7 +3953,8 @@ function runAgentsMd(argv: string[]): number {
|
|
|
3945
3953
|
// the form itself is the library's (ts/src/format.ts), and the two
|
|
3946
3954
|
// ports agree on it row by row in test/spec/fmt.tsv.
|
|
3947
3955
|
|
|
3948
|
-
const FMT_HELP =
|
|
3956
|
+
const FMT_HELP =
|
|
3957
|
+
'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>... (try --help)'
|
|
3949
3958
|
|
|
3950
3959
|
type FmtFlags = {
|
|
3951
3960
|
write: boolean, list: boolean, check: boolean, diff: boolean, lint: boolean, strict: boolean,
|
|
@@ -3953,11 +3962,13 @@ type FmtFlags = {
|
|
|
3953
3962
|
|
|
3954
3963
|
function runFmt(argv: string[]): number | Promise<number> {
|
|
3955
3964
|
const files: string[] = []
|
|
3965
|
+
let marker: string | undefined = undefined
|
|
3956
3966
|
const flags: FmtFlags = {
|
|
3957
3967
|
write: false, list: false, check: false, diff: false, lint: false, strict: false,
|
|
3958
3968
|
}
|
|
3959
3969
|
|
|
3960
|
-
for (
|
|
3970
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3971
|
+
const arg = argv[i]
|
|
3961
3972
|
if ('-h' === arg || '--help' === arg) {
|
|
3962
3973
|
process.stdout.write(HELP)
|
|
3963
3974
|
return 0
|
|
@@ -3981,6 +3992,16 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
3981
3992
|
flags.lint = true
|
|
3982
3993
|
flags.strict = true
|
|
3983
3994
|
}
|
|
3995
|
+
else if ('--marker' === arg) {
|
|
3996
|
+
// THE MARKER SAYS THE FILE IS A GENERATOR, whatever its
|
|
3997
|
+
// extension: `render` and `template` take the same option for
|
|
3998
|
+
// the same reason, a language the table has never seen.
|
|
3999
|
+
marker = argv[++i]
|
|
4000
|
+
if (null == marker) {
|
|
4001
|
+
process.stderr.write('aontu: --marker needs a token\n')
|
|
4002
|
+
return 2
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
3984
4005
|
else if (arg.startsWith('-')) {
|
|
3985
4006
|
process.stderr.write(`aontu: unknown fmt option ${arg} (try --help)\n`)
|
|
3986
4007
|
return 2
|
|
@@ -4002,7 +4023,7 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4002
4023
|
let src = ''
|
|
4003
4024
|
process.stdin.setEncoding('utf8')
|
|
4004
4025
|
process.stdin.on('data', (d) => (src += d))
|
|
4005
|
-
process.stdin.on('end', () => resolve(fmtOne('<stdin>', src, flags)))
|
|
4026
|
+
process.stdin.on('end', () => resolve(fmtOne('<stdin>', src, flags, marker)))
|
|
4006
4027
|
})
|
|
4007
4028
|
}
|
|
4008
4029
|
|
|
@@ -4018,23 +4039,6 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4018
4039
|
|
|
4019
4040
|
let worst = 0
|
|
4020
4041
|
for (const file of files) {
|
|
4021
|
-
// FMT FORMATS AONTU SOURCE, AND THE EXTENSION SAYS WHAT A FILE IS
|
|
4022
|
-
// (ADR-012's rule, and the one `render` reads a template by).
|
|
4023
|
-
// A TEMPLATE FILE IS NOT AONTU (docs/design/TEMPLATE.0.md; P8):
|
|
4024
|
-
// its marker lines are fragments of a document and its other lines
|
|
4025
|
-
// are the target's, so there is nothing here to format that would
|
|
4026
|
-
// not also rewrite the output. Refused rather than attempted, and
|
|
4027
|
-
// refused BY NAME rather than by a parse failure, because a `#-`
|
|
4028
|
-
// template parses: `#` opens a comment, so every marker line
|
|
4029
|
-
// vanishes and what is left is read as a document that was never
|
|
4030
|
-
// written. The verb answered `0` over one, having understood none
|
|
4031
|
-
// of it.
|
|
4032
|
-
if (!/[.](aon|aontu)$/.test(file)) {
|
|
4033
|
-
process.stderr.write(
|
|
4034
|
-
`aontu: ${file} is not aontu source (.aon, .aontu); a generator ` +
|
|
4035
|
-
'written in the target\'s own syntax is aontu template\'s\n')
|
|
4036
|
-
return 2
|
|
4037
|
-
}
|
|
4038
4042
|
let src: string
|
|
4039
4043
|
try {
|
|
4040
4044
|
src = readFileSync(file, 'utf8')
|
|
@@ -4043,11 +4047,46 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4043
4047
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
4044
4048
|
return 2
|
|
4045
4049
|
}
|
|
4046
|
-
|
|
4050
|
+
const mark = fmtMarker(file, src, marker)
|
|
4051
|
+
if (false === mark) {
|
|
4052
|
+
process.stderr.write(
|
|
4053
|
+
`aontu: ${file} is not aontu source (.aon, .aontu) and carries no ` +
|
|
4054
|
+
`${markerFor(file)} marker line, so there is no aontu in it to ` +
|
|
4055
|
+
'format; --marker names the marker for a language the table does ' +
|
|
4056
|
+
'not know\n')
|
|
4057
|
+
return 2
|
|
4058
|
+
}
|
|
4059
|
+
worst = Math.max(worst, fmtOne(file, src, flags, mark))
|
|
4047
4060
|
}
|
|
4048
4061
|
return worst
|
|
4049
4062
|
}
|
|
4050
4063
|
|
|
4064
|
+
// WHAT A FILE IS, BY ITS EXTENSION (ADR-012's rule, and the one
|
|
4065
|
+
// `render` reads an entry by): `.aon` and `.aontu` are aontu source,
|
|
4066
|
+
// and anything else is a GENERATOR written in the target's own syntax
|
|
4067
|
+
// (docs/design/TEMPLATE.0.md), whose marker lines carry the document
|
|
4068
|
+
// this formats and whose other lines are output. `undefined` is aontu,
|
|
4069
|
+
// a string is the generator's marker, and `false` is neither.
|
|
4070
|
+
//
|
|
4071
|
+
// A FILE WITH NO MARKER LINE IN IT IS NEITHER, and that is what keeps
|
|
4072
|
+
// FMT.0.md §9's boundary where it stood: a `.json`, `.yaml` or `.toml`
|
|
4073
|
+
// include is another language's file, and reading one as a generator
|
|
4074
|
+
// would answer it back unchanged having understood none of it. The
|
|
4075
|
+
// marker is the evidence that a file was written to carry aontu at
|
|
4076
|
+
// all. `--marker` says so outright, and then the file is a generator
|
|
4077
|
+
// whatever it is called.
|
|
4078
|
+
function fmtMarker(
|
|
4079
|
+
file: string, src: string, marker: string | undefined): string | undefined | false {
|
|
4080
|
+
if (undefined !== marker) {
|
|
4081
|
+
return marker
|
|
4082
|
+
}
|
|
4083
|
+
if (/[.](aon|aontu)$/.test(file)) {
|
|
4084
|
+
return undefined
|
|
4085
|
+
}
|
|
4086
|
+
const mark = markerFor(file)
|
|
4087
|
+
return templateOutputs(src, mark).some((out) => !out) ? mark : false
|
|
4088
|
+
}
|
|
4089
|
+
|
|
4051
4090
|
// An option that says what to do with a file, in place of printing
|
|
4052
4091
|
// it: what to do when its form would change, or the lint.
|
|
4053
4092
|
function fmtQuiet(flags: FmtFlags): boolean {
|
|
@@ -4059,8 +4098,9 @@ function fmtQuiet(flags: FmtFlags): boolean {
|
|
|
4059
4098
|
// document that does not format, with the finding that says why. The
|
|
4060
4099
|
// style findings go to standard error, one line each, in the shape
|
|
4061
4100
|
// every linter prints: `file:line:col: rule: message`.
|
|
4062
|
-
function fmtOne(
|
|
4063
|
-
|
|
4101
|
+
function fmtOne(
|
|
4102
|
+
name: string, src: string, flags: FmtFlags, marker?: string): number {
|
|
4103
|
+
const report = format(src, { path: name, lint: flags.lint, template: marker })
|
|
4064
4104
|
if ('error' === report.verdict) {
|
|
4065
4105
|
process.stderr.write(`aontu: ${name} was not formatted\n` +
|
|
4066
4106
|
report.errors.map(renderFinding).join('\n') + '\n')
|
package/src/format.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { Aontu } from './aontu'
|
|
|
26
26
|
import { failureFinding } from './vet'
|
|
27
27
|
import type { VetFinding } from './vet'
|
|
28
28
|
import type { Resolver } from './type'
|
|
29
|
+
import { desugarTemplate, resugarTemplate, templateOutputs } from './template'
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
// The packing budget (§3.1). It decides which of two legal spellings
|
|
@@ -46,6 +47,12 @@ export type FormatOptions = {
|
|
|
46
47
|
// Report the style findings of §4 -- key case, repeated shapes --
|
|
47
48
|
// beside the text. The formatter never acts on them.
|
|
48
49
|
lint?: boolean
|
|
50
|
+
// THE SOURCE IS A GENERATOR, and this is its marker
|
|
51
|
+
// (docs/design/TEMPLATE.0.md; FMT.0.md §3.14). The file is desugared,
|
|
52
|
+
// formatted and resugared, so `text` is a template again -- the aontu
|
|
53
|
+
// in the agreed form, indented after the marker, and every line of
|
|
54
|
+
// output exactly where it was.
|
|
55
|
+
template?: string
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
// A style finding (§4): what the formatter points at and never
|
|
@@ -183,6 +190,10 @@ type Node = {
|
|
|
183
190
|
|
|
184
191
|
// The source index of the node's first token: the lint's positions.
|
|
185
192
|
at?: number
|
|
193
|
+
|
|
194
|
+
// The node begins a line of the target's own file (§3.14), so it has
|
|
195
|
+
// no one-line form and every container holding it opens.
|
|
196
|
+
held?: boolean
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
const BINARY: Record<string, boolean> = { '#E&': true, '#E|': true, '#E+': true }
|
|
@@ -608,7 +619,7 @@ function pairHead(node: Node, tight: boolean): string {
|
|
|
608
619
|
// lines. `tight` is the inline form of a pair, `a:1`, used inside a
|
|
609
620
|
// container; a statement's pair is `a: 1`.
|
|
610
621
|
function inline(node: Node, tight: boolean): string | undefined {
|
|
611
|
-
if (undefined !== node.trail) {
|
|
622
|
+
if (undefined !== node.trail || node.held) {
|
|
612
623
|
return undefined
|
|
613
624
|
}
|
|
614
625
|
switch (node.t) {
|
|
@@ -744,6 +755,15 @@ class Writer {
|
|
|
744
755
|
return this.lines.slice(mark).concat([this.line]).map(rtrim).join('\n') + '\n'
|
|
745
756
|
}
|
|
746
757
|
|
|
758
|
+
// A blank line above the line at an index: the gap of §3.8, opened
|
|
759
|
+
// once the statement below it turns out to be a tree. Never at the
|
|
760
|
+
// top of the page, and never a second time.
|
|
761
|
+
gap(at: number): void {
|
|
762
|
+
if (0 < at && '' !== this.lines[at - 1]) {
|
|
763
|
+
this.lines.splice(at, 0, '')
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
747
767
|
// The lines since a mark replaced by a text: the spelling before,
|
|
748
768
|
// where a rewrite did not pass its check.
|
|
749
769
|
replace(mark: number, text: string): void {
|
|
@@ -776,29 +796,57 @@ function rtrim(s: string): string {
|
|
|
776
796
|
// body of a plain map that is itself the value of a statement) a pair
|
|
777
797
|
// is laid out by §3.4, which may repeat its key; anywhere else -- a
|
|
778
798
|
// list, an operand, an argument -- by §3.5 alone.
|
|
779
|
-
function emitBody(
|
|
799
|
+
function emitBody(
|
|
800
|
+
w: Writer, body: Node[], indent: number, stmt: Stmt | undefined, root?: boolean
|
|
801
|
+
): void {
|
|
780
802
|
let pending = false
|
|
781
803
|
let count = 0
|
|
804
|
+
// Where the run being written begins: a statement, with the comments
|
|
805
|
+
// standing directly above it, so that the gap below opens ABOVE the
|
|
806
|
+
// comments rather than between them and what they describe. A blank
|
|
807
|
+
// line ends a run -- comments across a gap belong to what is above.
|
|
808
|
+
let head = 0
|
|
809
|
+
let noted = false
|
|
782
810
|
for (const node of body) {
|
|
783
811
|
if ('blank' === node.t) {
|
|
784
812
|
pending = 0 < count
|
|
785
813
|
continue
|
|
786
814
|
}
|
|
815
|
+
const gapped = pending
|
|
787
816
|
w.open(indent, pending)
|
|
817
|
+
if (gapped || !noted) {
|
|
818
|
+
head = w.mark()
|
|
819
|
+
}
|
|
788
820
|
pending = false
|
|
789
821
|
count++
|
|
790
822
|
if ('comment' === node.t) {
|
|
823
|
+
noted = true
|
|
791
824
|
w.text(node.text!)
|
|
792
825
|
continue
|
|
793
826
|
}
|
|
827
|
+
noted = false
|
|
828
|
+
const from = w.mark()
|
|
794
829
|
if (undefined !== stmt && 'pair' === node.t) {
|
|
795
830
|
emitStatement(w, node, indent, stmt, '')
|
|
796
|
-
continue
|
|
797
831
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
832
|
+
else {
|
|
833
|
+
const e = chain(node)
|
|
834
|
+
emitValue(w, e, indent)
|
|
835
|
+
if (undefined !== e.trail) {
|
|
836
|
+
w.text(' ' + e.trail)
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// A TOP-LEVEL STATEMENT WRITTEN AS A TREE STANDS APART (§3.8). A
|
|
840
|
+
// document states several things -- a service, then its entities,
|
|
841
|
+
// then its errors -- and where one of them is a tree rather than a
|
|
842
|
+
// line, the eye finds it by the space around it. What counts as a
|
|
843
|
+
// tree is measured rather than guessed: the statement took more
|
|
844
|
+
// than one line to write. So `a: 1` beside `b: 2` is left alone,
|
|
845
|
+
// and this rule cannot fire below the root, where a blank line is
|
|
846
|
+
// the author's (§3.8) and nothing else.
|
|
847
|
+
if (root && from < w.mark()) {
|
|
848
|
+
w.gap(head)
|
|
849
|
+
pending = true
|
|
802
850
|
}
|
|
803
851
|
}
|
|
804
852
|
}
|
|
@@ -1282,7 +1330,7 @@ function emitAt(nodes: Node[], indent: number): string {
|
|
|
1282
1330
|
function emit(root: Node[], meet: Meet | undefined): string {
|
|
1283
1331
|
const w = new Writer()
|
|
1284
1332
|
emitBody(w, undefined === meet ? root : mergeRuns(root), 0,
|
|
1285
|
-
undefined === meet ? undefined : { meet, covered: false })
|
|
1333
|
+
undefined === meet ? undefined : { meet, covered: false }, true)
|
|
1286
1334
|
return w.finish()
|
|
1287
1335
|
}
|
|
1288
1336
|
|
|
@@ -1516,13 +1564,66 @@ function checkFinding(path: string | undefined, expected: string, actual: string
|
|
|
1516
1564
|
}
|
|
1517
1565
|
}
|
|
1518
1566
|
|
|
1567
|
+
// THE TARGET'S OWN LINES ARE HELD ON LINES OF THEIR OWN (§3.14). The
|
|
1568
|
+
// desugaring is line for line, so a line of output is known by the
|
|
1569
|
+
// offset it begins at, and the node beginning there -- the quoted
|
|
1570
|
+
// string the desugaring wrote -- is marked. From there on it has no
|
|
1571
|
+
// one-line form, so every container holding it opens, and no two lines
|
|
1572
|
+
// of the generated file are ever packed onto one.
|
|
1573
|
+
function holdOutput(nodes: Node[], at: Set<number>): void {
|
|
1574
|
+
for (const node of nodes) {
|
|
1575
|
+
if (undefined !== node.at && at.has(node.at)) {
|
|
1576
|
+
node.held = true
|
|
1577
|
+
}
|
|
1578
|
+
holdOutput(node.body ?? [], at)
|
|
1579
|
+
holdOutput(node.args ?? [], at)
|
|
1580
|
+
holdOutput(node.inner ?? [], at)
|
|
1581
|
+
holdOutput(node.items ?? [], at)
|
|
1582
|
+
if (undefined !== node.value) {
|
|
1583
|
+
holdOutput([node.value], at)
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
// The offsets the flagged lines of a document begin at.
|
|
1590
|
+
function outputAt(doc: string, flags: boolean[]): Set<number> {
|
|
1591
|
+
const at = new Set<number>()
|
|
1592
|
+
let off = 0
|
|
1593
|
+
const lines = doc.split('\n')
|
|
1594
|
+
for (let k = 0; k < lines.length; k++) {
|
|
1595
|
+
if (flags[k]) {
|
|
1596
|
+
at.add(off)
|
|
1597
|
+
}
|
|
1598
|
+
off += lines[k].length + 1
|
|
1599
|
+
}
|
|
1600
|
+
return at
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
// A finding's column in the TEMPLATE rather than in the document it
|
|
1605
|
+
// carries (§3.14): the marker and its one space stand before the aontu
|
|
1606
|
+
// on every line the resugaring writes. Every finding is on such a
|
|
1607
|
+
// line -- the two rules point at a key or at a container, and a line of
|
|
1608
|
+
// output is a bare string, which is neither.
|
|
1609
|
+
function shiftFindings(findings: LintFinding[], mark: string | undefined): LintFinding[] {
|
|
1610
|
+
return undefined === mark ? findings :
|
|
1611
|
+
findings.map((f) => ({ ...f, col: f.col + mark.length + 1 }))
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
|
|
1519
1615
|
// Format one document. The text is the agreed form of the source;
|
|
1520
1616
|
// `changed` says whether it differs from what was given, which is
|
|
1521
1617
|
// what `--check` and `--list` report.
|
|
1522
1618
|
export function format(src: string, opts?: FormatOptions, hooks?: FormatHooks): FormatReport {
|
|
1523
1619
|
const text = lf(src)
|
|
1620
|
+
// A GENERATOR IS FORMATTED AS THE DOCUMENT IT CARRIES (§3.14): the
|
|
1621
|
+
// template surface's two transforms stand either side of the
|
|
1622
|
+
// formatter, and between them is what happens to any other document.
|
|
1623
|
+
const mark = opts?.template
|
|
1624
|
+
const doc = undefined === mark ? text : desugarTemplate(text, mark)
|
|
1524
1625
|
const toks: Tok[] = []
|
|
1525
|
-
const parsed = parseDoc(
|
|
1626
|
+
const parsed = parseDoc(doc, opts?.path, toks)
|
|
1526
1627
|
if (undefined !== parsed.errors) {
|
|
1527
1628
|
return { verdict: 'error', errors: parsed.errors }
|
|
1528
1629
|
}
|
|
@@ -1531,6 +1632,9 @@ export function format(src: string, opts?: FormatOptions, hooks?: FormatHooks):
|
|
|
1531
1632
|
if (reader.deep) {
|
|
1532
1633
|
return { verdict: 'error', errors: [depthFinding()] }
|
|
1533
1634
|
}
|
|
1635
|
+
if (undefined !== mark) {
|
|
1636
|
+
holdOutput(root, outputAt(doc, templateOutputs(text, mark)))
|
|
1637
|
+
}
|
|
1534
1638
|
// The syntactic tier first, checked against the parse tree; then the
|
|
1535
1639
|
// lawful tier over it, each rewrite checked by the meet.
|
|
1536
1640
|
const plain = emit(root, undefined)
|
|
@@ -1542,9 +1646,10 @@ export function format(src: string, opts?: FormatOptions, hooks?: FormatHooks):
|
|
|
1542
1646
|
}
|
|
1543
1647
|
}
|
|
1544
1648
|
const out = emit(root, hooks?.meet ?? sameByMeet)
|
|
1649
|
+
const done = undefined === mark ? out : resugarTemplate(out, mark)
|
|
1545
1650
|
return {
|
|
1546
|
-
verdict: 'formatted', text:
|
|
1547
|
-
findings: opts?.lint ? lintOf(root,
|
|
1651
|
+
verdict: 'formatted', text: done, changed: done !== src,
|
|
1652
|
+
findings: opts?.lint ? shiftFindings(lintOf(root, doc), mark) : [],
|
|
1548
1653
|
}
|
|
1549
1654
|
}
|
|
1550
1655
|
|
package/src/std.ts
CHANGED
|
@@ -186,6 +186,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
|
|
|
186
186
|
k: "prim"
|
|
187
187
|
prim: "string" | "int" | "bigint" | "float" | "decimal" | "bool" | "null" | "any"
|
|
188
188
|
})
|
|
189
|
+
|
|
189
190
|
%ref = close({ k:"ref" name:%name unit?:string })
|
|
190
191
|
%leaf = %prim | %ref | %text
|
|
191
192
|
|
|
@@ -208,6 +209,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
|
|
|
208
209
|
})
|
|
209
210
|
|
|
210
211
|
%member = close({ name:%name value?:string | number doc?:%doc x?:{} })
|
|
212
|
+
|
|
211
213
|
%param = close({
|
|
212
214
|
name: %name
|
|
213
215
|
type: %type
|
|
@@ -221,12 +223,14 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
|
|
|
221
223
|
%inline = string & re("^[^\n\r]*$") | %ref
|
|
222
224
|
%line = close({ k:"line" at:*0 | integer & min(0) & max(64) of:[&: %inline] })
|
|
223
225
|
%blank = close({ k:"blank" n:*1 | integer & min(1) & max(16) })
|
|
226
|
+
|
|
224
227
|
%raw = close({
|
|
225
228
|
k: "raw"
|
|
226
229
|
at: *0 | integer & min(0) & max(64)
|
|
227
230
|
text: string
|
|
228
231
|
reindent: *true | boolean
|
|
229
232
|
})
|
|
233
|
+
|
|
230
234
|
%piece = %line | %blank | %raw | string & re("^[^\n\r]*$")
|
|
231
235
|
%frag = close({ k:"frag" of:[&: %piece] })
|
|
232
236
|
%body = %frag | close({ k:"abstract" })
|
|
@@ -241,7 +245,9 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
|
|
|
241
245
|
check?: [&: %check]
|
|
242
246
|
x?: {}
|
|
243
247
|
})
|
|
248
|
+
|
|
244
249
|
%enum = close({ k:"enum" name:%name doc?:%doc members:[&: %member] x?:{} })
|
|
250
|
+
|
|
245
251
|
%alias = close({
|
|
246
252
|
k: "alias"
|
|
247
253
|
name: %name
|
|
@@ -250,6 +256,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
|
|
|
250
256
|
check?: [&: %check]
|
|
251
257
|
x?: {}
|
|
252
258
|
})
|
|
259
|
+
|
|
253
260
|
%const = close({
|
|
254
261
|
k: "const"
|
|
255
262
|
name: %name
|
|
@@ -258,6 +265,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
|
|
|
258
265
|
value: string | number | boolean | null
|
|
259
266
|
x?: {}
|
|
260
267
|
})
|
|
268
|
+
|
|
261
269
|
%func = close({
|
|
262
270
|
k: "func"
|
|
263
271
|
name: %name
|
package/src/template.ts
CHANGED
|
@@ -24,12 +24,22 @@
|
|
|
24
24
|
// comment, and derives for a language this table has never seen: the
|
|
25
25
|
// caller passes the token.
|
|
26
26
|
//
|
|
27
|
-
// A MARKER IS RECOGNISED AFTER LEADING WHITESPACE AND
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// indented two spaces in the
|
|
31
|
-
//
|
|
32
|
-
// the
|
|
27
|
+
// A MARKER IS RECOGNISED AFTER LEADING WHITESPACE, AND THE INDENTATION
|
|
28
|
+
// THE RESUGARING WRITES IS THE AONTU'S (D2, amended 2026-09-07). An
|
|
29
|
+
// output line is verbatim -- a line indented two spaces in the template
|
|
30
|
+
// is indented two spaces in the generated file (D7) -- and that is the
|
|
31
|
+
// half a reader of the TARGET needs. The other half is the document:
|
|
32
|
+
// the marker lines carry a tree, and a tree nobody can see the shape of
|
|
33
|
+
// is the one thing this surface took away. So the marker stands at the
|
|
34
|
+
// left margin and the aontu is indented AFTER it, which `aontu fmt`
|
|
35
|
+
// (FMT.0.md M-BM-'3.14) is what writes.
|
|
36
|
+
//
|
|
37
|
+
// Reading is unchanged and stays generous: a marker after leading
|
|
38
|
+
// whitespace is a marker, and its own indentation still counts toward
|
|
39
|
+
// the aontu it carries, so a template written the other way desugars to
|
|
40
|
+
// the same document. What changes is the spelling the round trip
|
|
41
|
+
// answers, and `template --check` names the difference as the drift it
|
|
42
|
+
// is.
|
|
33
43
|
//
|
|
34
44
|
// THE SUGAR IS THE FIXPOINT OF THE TWO TRANSFORMS (D6), and that is
|
|
35
45
|
// the whole of the resugaring's safety. A canonical line that looks
|
|
@@ -128,13 +138,19 @@ function indentOf(line: string): number {
|
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
|
|
131
|
-
// The line without its
|
|
132
|
-
function
|
|
141
|
+
// The line without its trailing spaces and tabs.
|
|
142
|
+
function trimEnd(line: string): string {
|
|
133
143
|
let end = line.length
|
|
134
144
|
while (0 < end && (' ' === line[end - 1] || '\t' === line[end - 1])) {
|
|
135
145
|
end--
|
|
136
146
|
}
|
|
137
|
-
return line.slice(
|
|
147
|
+
return line.slice(0, end)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
// The line without its indentation or its trailing spaces and tabs.
|
|
152
|
+
function trimLine(line: string): string {
|
|
153
|
+
return trimEnd(line).slice(indentOf(line))
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
|
|
@@ -159,12 +175,15 @@ function readLine(line: string, marker: string): Line {
|
|
|
159
175
|
// The block form's closer is part of the marker, not of the aontu.
|
|
160
176
|
// A block marker line that never closes is not a marker line: the
|
|
161
177
|
// language's own parser would not read it as a comment either.
|
|
178
|
+
// Only the TRAILING space goes: what stands between the opener and
|
|
179
|
+
// the aontu is the aontu's indentation, exactly as it is for a line
|
|
180
|
+
// marker, and the one-space rule below takes the marker's own.
|
|
162
181
|
if (isBlock(marker)) {
|
|
163
182
|
const end = body.lastIndexOf(BLOCK_CLOSE)
|
|
164
183
|
if (end < 0) {
|
|
165
184
|
return { marker: false, indent: '', text: line }
|
|
166
185
|
}
|
|
167
|
-
body =
|
|
186
|
+
body = trimEnd(body.slice(0, end))
|
|
168
187
|
}
|
|
169
188
|
// ONE SPACE AFTER THE MARKER IS THE MARKER'S, so `//- x: 1` carries
|
|
170
189
|
// `x: 1` and the resugaring writes the space back. A marker written
|
|
@@ -273,19 +292,40 @@ function resugarTemplate(src: string, marker?: string): string {
|
|
|
273
292
|
if (undefined !== target && desugarTemplate(target, mark) === trimLine(line)) {
|
|
274
293
|
return target
|
|
275
294
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
295
|
+
// THE MARKER STANDS AT THE LEFT MARGIN and the line's indentation
|
|
296
|
+
// is written after it, so the aontu's own shape is on the page.
|
|
297
|
+
// The one space is the marker's, which the reading takes back.
|
|
298
|
+
const text = trimEnd(line)
|
|
279
299
|
const close = isBlock(mark) ? ' ' + BLOCK_CLOSE : ''
|
|
280
|
-
return '' === text ?
|
|
300
|
+
return '' === text ? mark + close : mark + ' ' + text + close
|
|
281
301
|
})
|
|
282
302
|
return out.join('\n') + (tail ? '\n' : '')
|
|
283
|
-
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
// WHICH LINES OF THE DESUGARED DOCUMENT ARE THE TARGET'S. The
|
|
307
|
+
// desugaring is line for line, so this is one flag per line of what
|
|
308
|
+
// `desugarTemplate` returns: true where the template's line was not a
|
|
309
|
+
// marker, and so where the document's line is one quoted line of the
|
|
310
|
+
// generated file.
|
|
311
|
+
//
|
|
312
|
+
// `aontu fmt` reads it (FMT.0.md M-BM-'3.14) to hold those lines on lines of
|
|
313
|
+
// their own. A body element is a string like any other, and the packing
|
|
314
|
+
// budget would put three of them on one line -- which is aontu where
|
|
315
|
+
// three lines of output were, and a generator that writes them as one.
|
|
316
|
+
function templateOutputs(src: string, marker: string): boolean[] {
|
|
317
|
+
const lines = src.split('\n')
|
|
318
|
+
if (1 < lines.length && '' === lines[lines.length - 1]) {
|
|
319
|
+
lines.pop()
|
|
320
|
+
}
|
|
321
|
+
return lines.map((line) => !readLine(line, marker).marker)
|
|
322
|
+
} /* node:coverage ignore next 9 */
|
|
284
323
|
|
|
285
324
|
|
|
286
325
|
export {
|
|
287
326
|
desugarTemplate,
|
|
288
327
|
resugarTemplate,
|
|
328
|
+
templateOutputs,
|
|
289
329
|
markerFor,
|
|
290
330
|
DEFAULT_MARKER,
|
|
291
331
|
}
|