@hawkeyexl/manni 4.4.1 → 4.5.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 +1 -1
- package/dist/{batch-GFNLTGOH.js → batch-GOE3UXOD.js} +11 -11
- package/dist/{build-COYIQ25L.js → build-M7BMEHL2.js} +13 -11
- package/dist/{calibrate-MDBLJKEE.js → calibrate-6RRAILUY.js} +12 -12
- package/dist/{capture-LRML2PKV.js → capture-RJPQ6B4N.js} +12 -12
- package/dist/{check-EDLBYL5N.js → check-BQD64X3N.js} +11 -11
- package/dist/{check-HYNGXYJV.js → check-ESNS7ZBC.js} +4 -4
- package/dist/{check-ASPIHA6B.js → check-RQIIIURI.js} +12 -12
- package/dist/{chunk-NXV7WE2D.js → chunk-3R5Y3XOL.js} +3 -3
- package/dist/{chunk-A5ULMVHQ.js → chunk-3YHAYXBC.js} +2 -2
- package/dist/{chunk-57NEQRVO.js → chunk-527HHQPS.js} +4 -4
- package/dist/{chunk-57NEQRVO.js.map → chunk-527HHQPS.js.map} +1 -1
- package/dist/{chunk-6OYEI2VV.js → chunk-52PDIZKV.js} +3 -3
- package/dist/{chunk-UJCVAWP7.js → chunk-6XTQDFGP.js} +2 -2
- package/dist/{chunk-UJCVAWP7.js.map → chunk-6XTQDFGP.js.map} +1 -1
- package/dist/{chunk-3VK37OR2.js → chunk-7OQAU4EA.js} +5 -5
- package/dist/{chunk-4IUC2MNL.js → chunk-7ZUCEMCY.js} +8 -8
- package/dist/{chunk-M2JCK5F6.js → chunk-B7XUEQF3.js} +13 -2
- package/dist/chunk-B7XUEQF3.js.map +1 -0
- package/dist/{chunk-MYMBCTDE.js → chunk-BOIF2XGG.js} +2 -2
- package/dist/{chunk-YJOGUXGP.js → chunk-C3KI45RF.js} +8 -6
- package/dist/{chunk-YJOGUXGP.js.map → chunk-C3KI45RF.js.map} +1 -1
- package/dist/{chunk-MMTSSTG7.js → chunk-C6HZ6C5T.js} +2 -2
- package/dist/{chunk-QQUKGCAJ.js → chunk-CP4ZM43V.js} +2 -2
- package/dist/chunk-DE7JSWXV.js +2462 -0
- package/dist/chunk-DE7JSWXV.js.map +1 -0
- package/dist/{chunk-YB4IBW3M.js → chunk-DX32P2DT.js} +2 -2
- package/dist/{chunk-RL5XWQOL.js → chunk-EL2BZTDL.js} +137 -50
- package/dist/chunk-EL2BZTDL.js.map +1 -0
- package/dist/{chunk-WB3QM6PP.js → chunk-ELZ2IIOF.js} +332 -2522
- package/dist/chunk-ELZ2IIOF.js.map +1 -0
- package/dist/{chunk-MWJLZMBL.js → chunk-F2OVHJNC.js} +10 -10
- package/dist/{chunk-I2AH3CJJ.js → chunk-FLF6AECQ.js} +7 -7
- package/dist/{chunk-GCQRPUSB.js → chunk-GAZFKZHM.js} +4 -4
- package/dist/{chunk-QZ6LRSNV.js → chunk-HQFVJDEN.js} +7 -7
- package/dist/{chunk-M3W7J6MU.js → chunk-HVFR66GQ.js} +2 -2
- package/dist/{chunk-2B7ZNTPV.js → chunk-IA7BMIPE.js} +2 -2
- package/dist/{chunk-PIKOVN6W.js → chunk-JQU7FHYH.js} +6 -6
- package/dist/{chunk-NLXTXE5Y.js → chunk-K3U5F2QT.js} +183 -31
- package/dist/chunk-K3U5F2QT.js.map +1 -0
- package/dist/{chunk-JCDXE76J.js → chunk-MZC62HPZ.js} +2 -2
- package/dist/{chunk-TK3FC4RF.js → chunk-NAZRLJAK.js} +3 -3
- package/dist/{chunk-JXLTXMJ7.js → chunk-NGK3N6F2.js} +3 -3
- package/dist/{chunk-PU6DFJMY.js → chunk-NN4ASVTY.js} +3 -3
- package/dist/{chunk-C4SQM22E.js → chunk-OIZ3XMKB.js} +13 -11
- package/dist/chunk-OIZ3XMKB.js.map +1 -0
- package/dist/{chunk-6JSSEWBG.js → chunk-QCBICNKC.js} +6 -6
- package/dist/chunk-QCBICNKC.js.map +1 -0
- package/dist/{chunk-DXNZS72W.js → chunk-QLUMKWQQ.js} +3 -3
- package/dist/{chunk-EPH54CAY.js → chunk-QY3DOZMS.js} +4 -4
- package/dist/{chunk-FBEQCK47.js → chunk-SJO2FTPG.js} +6 -6
- package/dist/{chunk-PNPQTHBT.js → chunk-SNEHX4VE.js} +2 -2
- package/dist/{chunk-SKSDRUUK.js → chunk-TA42STP7.js} +4 -4
- package/dist/{chunk-CK7SKVHR.js → chunk-TBAMBLF3.js} +3 -12
- package/dist/chunk-TBAMBLF3.js.map +1 -0
- package/dist/{chunk-IDV6HJCB.js → chunk-TCC737IS.js} +3 -3
- package/dist/{chunk-QRWGPQKO.js → chunk-TVIX653Q.js} +3 -3
- package/dist/{chunk-YTURO7SI.js → chunk-UK2I3ACY.js} +3 -3
- package/dist/{chunk-7UKO3ASZ.js → chunk-XWFNO5PO.js} +3 -3
- package/dist/{chunk-37LP5HMZ.js → chunk-YA63PVWI.js} +5 -5
- package/dist/chunk-YGFIQ6Y5.js +17 -0
- package/dist/chunk-YGFIQ6Y5.js.map +1 -0
- package/dist/{chunk-UQISLDAV.js → chunk-ZX43SRZR.js} +3 -3
- package/dist/{cli-MN7PYMBI.js → cli-6PGVHP7Y.js} +15 -15
- package/dist/{cli-6UB5N6CU.js → cli-AAG3N4NB.js} +16 -16
- package/dist/{cli-6HIDIFVT.js → cli-E3H6ZP4I.js} +2 -2
- package/dist/{cli-RPM5PELX.js → cli-HUIMNJT6.js} +20 -15
- package/dist/cli-HUIMNJT6.js.map +1 -0
- package/dist/{cli-VI7VHWNZ.js → cli-MHTYPVOD.js} +16 -16
- package/dist/{cli-LFP5ZGYS.js → cli-PRZHQ3QB.js} +15 -13
- package/dist/{cli-LFP5ZGYS.js.map → cli-PRZHQ3QB.js.map} +1 -1
- package/dist/{cli-3BIBYHFK.js → cli-QKIJOXGW.js} +13 -13
- package/dist/{cli-PYPX6O6E.js → cli-TYGOHJOJ.js} +25 -25
- package/dist/{cli-673OTRGE.js → cli-YW6VWBHX.js} +9 -9
- package/dist/cli.js +11 -11
- package/dist/docmeta.js +8 -8
- package/dist/{github-SXPGP5DB.js → github-3WMSTNTK.js} +13 -13
- package/dist/{github-6QMYOHC3.js → github-OXSJQ4PS.js} +11 -11
- package/dist/index.d.ts +30 -8
- package/dist/index.js +42 -38
- package/dist/index.js.map +1 -1
- package/dist/{json-SY7KLNC7.js → json-4DCKDGUV.js} +11 -11
- package/dist/lint-YDHVVZHT.js +30 -0
- package/dist/{list-KZNVTNVC.js → list-3ZO2NIYO.js} +11 -11
- package/dist/{list-Q57YTFUQ.js → list-EJH6VAOW.js} +11 -11
- package/dist/{page-KSGV5RD4.js → page-QEDB7EVU.js} +10 -10
- package/dist/{pretty-A5EGTN35.js → pretty-5DGEBZSV.js} +11 -11
- package/dist/{pretty-CXSDXNAS.js → pretty-SQX33UM2.js} +12 -12
- package/dist/{reporters-HUY7UXFS.js → reporters-OHSI3RMV.js} +12 -11
- package/dist/{reporters-K2VUKSAK.js → reporters-YELDDYJD.js} +4 -4
- package/dist/{run-3DYJNRIG.js → run-IP6NZHAG.js} +11 -11
- package/dist/{term-BIQUPMOR.js → term-2WMDZ4PY.js} +15 -15
- package/dist/{validate-53RKM2XG.js → validate-G6HH3YXL.js} +5 -5
- package/package.json +1 -1
- package/plugin/manni/.claude-plugin/plugin.json +1 -1
- package/dist/chunk-6JSSEWBG.js.map +0 -1
- package/dist/chunk-C4SQM22E.js.map +0 -1
- package/dist/chunk-CK7SKVHR.js.map +0 -1
- package/dist/chunk-M2JCK5F6.js.map +0 -1
- package/dist/chunk-NLXTXE5Y.js.map +0 -1
- package/dist/chunk-RL5XWQOL.js.map +0 -1
- package/dist/chunk-WB3QM6PP.js.map +0 -1
- package/dist/cli-RPM5PELX.js.map +0 -1
- package/dist/lint-ASMJLLPA.js +0 -28
- /package/dist/{batch-GFNLTGOH.js.map → batch-GOE3UXOD.js.map} +0 -0
- /package/dist/{build-COYIQ25L.js.map → build-M7BMEHL2.js.map} +0 -0
- /package/dist/{calibrate-MDBLJKEE.js.map → calibrate-6RRAILUY.js.map} +0 -0
- /package/dist/{capture-LRML2PKV.js.map → capture-RJPQ6B4N.js.map} +0 -0
- /package/dist/{check-ASPIHA6B.js.map → check-BQD64X3N.js.map} +0 -0
- /package/dist/{check-EDLBYL5N.js.map → check-ESNS7ZBC.js.map} +0 -0
- /package/dist/{check-HYNGXYJV.js.map → check-RQIIIURI.js.map} +0 -0
- /package/dist/{chunk-NXV7WE2D.js.map → chunk-3R5Y3XOL.js.map} +0 -0
- /package/dist/{chunk-A5ULMVHQ.js.map → chunk-3YHAYXBC.js.map} +0 -0
- /package/dist/{chunk-6OYEI2VV.js.map → chunk-52PDIZKV.js.map} +0 -0
- /package/dist/{chunk-3VK37OR2.js.map → chunk-7OQAU4EA.js.map} +0 -0
- /package/dist/{chunk-4IUC2MNL.js.map → chunk-7ZUCEMCY.js.map} +0 -0
- /package/dist/{chunk-MYMBCTDE.js.map → chunk-BOIF2XGG.js.map} +0 -0
- /package/dist/{chunk-MMTSSTG7.js.map → chunk-C6HZ6C5T.js.map} +0 -0
- /package/dist/{chunk-QQUKGCAJ.js.map → chunk-CP4ZM43V.js.map} +0 -0
- /package/dist/{chunk-YB4IBW3M.js.map → chunk-DX32P2DT.js.map} +0 -0
- /package/dist/{chunk-MWJLZMBL.js.map → chunk-F2OVHJNC.js.map} +0 -0
- /package/dist/{chunk-I2AH3CJJ.js.map → chunk-FLF6AECQ.js.map} +0 -0
- /package/dist/{chunk-GCQRPUSB.js.map → chunk-GAZFKZHM.js.map} +0 -0
- /package/dist/{chunk-QZ6LRSNV.js.map → chunk-HQFVJDEN.js.map} +0 -0
- /package/dist/{chunk-M3W7J6MU.js.map → chunk-HVFR66GQ.js.map} +0 -0
- /package/dist/{chunk-2B7ZNTPV.js.map → chunk-IA7BMIPE.js.map} +0 -0
- /package/dist/{chunk-PIKOVN6W.js.map → chunk-JQU7FHYH.js.map} +0 -0
- /package/dist/{chunk-JCDXE76J.js.map → chunk-MZC62HPZ.js.map} +0 -0
- /package/dist/{chunk-TK3FC4RF.js.map → chunk-NAZRLJAK.js.map} +0 -0
- /package/dist/{chunk-JXLTXMJ7.js.map → chunk-NGK3N6F2.js.map} +0 -0
- /package/dist/{chunk-PU6DFJMY.js.map → chunk-NN4ASVTY.js.map} +0 -0
- /package/dist/{chunk-DXNZS72W.js.map → chunk-QLUMKWQQ.js.map} +0 -0
- /package/dist/{chunk-EPH54CAY.js.map → chunk-QY3DOZMS.js.map} +0 -0
- /package/dist/{chunk-FBEQCK47.js.map → chunk-SJO2FTPG.js.map} +0 -0
- /package/dist/{chunk-PNPQTHBT.js.map → chunk-SNEHX4VE.js.map} +0 -0
- /package/dist/{chunk-SKSDRUUK.js.map → chunk-TA42STP7.js.map} +0 -0
- /package/dist/{chunk-IDV6HJCB.js.map → chunk-TCC737IS.js.map} +0 -0
- /package/dist/{chunk-QRWGPQKO.js.map → chunk-TVIX653Q.js.map} +0 -0
- /package/dist/{chunk-YTURO7SI.js.map → chunk-UK2I3ACY.js.map} +0 -0
- /package/dist/{chunk-7UKO3ASZ.js.map → chunk-XWFNO5PO.js.map} +0 -0
- /package/dist/{chunk-37LP5HMZ.js.map → chunk-YA63PVWI.js.map} +0 -0
- /package/dist/{chunk-UQISLDAV.js.map → chunk-ZX43SRZR.js.map} +0 -0
- /package/dist/{cli-MN7PYMBI.js.map → cli-6PGVHP7Y.js.map} +0 -0
- /package/dist/{cli-6UB5N6CU.js.map → cli-AAG3N4NB.js.map} +0 -0
- /package/dist/{cli-6HIDIFVT.js.map → cli-E3H6ZP4I.js.map} +0 -0
- /package/dist/{cli-VI7VHWNZ.js.map → cli-MHTYPVOD.js.map} +0 -0
- /package/dist/{cli-3BIBYHFK.js.map → cli-QKIJOXGW.js.map} +0 -0
- /package/dist/{cli-PYPX6O6E.js.map → cli-TYGOHJOJ.js.map} +0 -0
- /package/dist/{cli-673OTRGE.js.map → cli-YW6VWBHX.js.map} +0 -0
- /package/dist/{github-6QMYOHC3.js.map → github-3WMSTNTK.js.map} +0 -0
- /package/dist/{github-SXPGP5DB.js.map → github-OXSJQ4PS.js.map} +0 -0
- /package/dist/{json-SY7KLNC7.js.map → json-4DCKDGUV.js.map} +0 -0
- /package/dist/{lint-ASMJLLPA.js.map → lint-YDHVVZHT.js.map} +0 -0
- /package/dist/{list-KZNVTNVC.js.map → list-3ZO2NIYO.js.map} +0 -0
- /package/dist/{list-Q57YTFUQ.js.map → list-EJH6VAOW.js.map} +0 -0
- /package/dist/{page-KSGV5RD4.js.map → page-QEDB7EVU.js.map} +0 -0
- /package/dist/{pretty-A5EGTN35.js.map → pretty-5DGEBZSV.js.map} +0 -0
- /package/dist/{pretty-CXSDXNAS.js.map → pretty-SQX33UM2.js.map} +0 -0
- /package/dist/{reporters-HUY7UXFS.js.map → reporters-OHSI3RMV.js.map} +0 -0
- /package/dist/{reporters-K2VUKSAK.js.map → reporters-YELDDYJD.js.map} +0 -0
- /package/dist/{run-3DYJNRIG.js.map → run-IP6NZHAG.js.map} +0 -0
- /package/dist/{term-BIQUPMOR.js.map → term-2WMDZ4PY.js.map} +0 -0
- /package/dist/{validate-53RKM2XG.js.map → validate-G6HH3YXL.js.map} +0 -0
|
@@ -1,49 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
LintError
|
|
4
|
+
} from "./chunk-YGFIQ6Y5.js";
|
|
5
5
|
import {
|
|
6
|
-
BLOCK_KINDS,
|
|
7
|
-
BLOCK_KIND_NODE,
|
|
8
|
-
LintError,
|
|
9
|
-
checkContains,
|
|
10
|
-
checkContainsIn,
|
|
11
|
-
checkHeading,
|
|
12
|
-
checkSequence,
|
|
13
|
-
checkSequenceIn,
|
|
14
|
-
isWildcard,
|
|
15
|
-
matchSections,
|
|
16
|
-
occurrenceRange
|
|
17
|
-
} from "./chunk-CK7SKVHR.js";
|
|
18
|
-
import {
|
|
19
|
-
STDIN_TOKEN,
|
|
20
|
-
assertNonEmpty,
|
|
21
6
|
extractFrontmatter,
|
|
22
7
|
extractorForExtension,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
locateFrontmatter,
|
|
26
|
-
resolveTargetSet
|
|
27
|
-
} from "./chunk-57NEQRVO.js";
|
|
28
|
-
import {
|
|
29
|
-
ditaOtHome,
|
|
30
|
-
findConfigFile,
|
|
31
|
-
readConfigFile,
|
|
32
|
-
selectCollections,
|
|
33
|
-
warn
|
|
34
|
-
} from "./chunk-Q35YIDBV.js";
|
|
8
|
+
locateFrontmatter
|
|
9
|
+
} from "./chunk-527HHQPS.js";
|
|
35
10
|
import {
|
|
36
|
-
package_default
|
|
37
|
-
} from "./chunk-UJCVAWP7.js";
|
|
38
|
-
import {
|
|
39
|
-
ToolError,
|
|
40
11
|
errorMessage
|
|
41
12
|
} from "./chunk-G3T47CFV.js";
|
|
42
13
|
|
|
43
|
-
// src/lint/commands/lint.ts
|
|
44
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
45
|
-
import { extname, relative, resolve as resolve2 } from "node:path";
|
|
46
|
-
|
|
47
14
|
// src/lint/parsers/markdown.ts
|
|
48
15
|
import { unified } from "unified";
|
|
49
16
|
import remarkParse from "remark-parse";
|
|
@@ -309,6 +276,8 @@ function sectionize(fragments, docEnd) {
|
|
|
309
276
|
order: 0,
|
|
310
277
|
parentSlug: null,
|
|
311
278
|
titlePosition: { ...fragment.position },
|
|
279
|
+
...fragment.id !== void 0 ? { id: fragment.id } : {},
|
|
280
|
+
...fragment.synthetic ? { synthetic: true } : {},
|
|
312
281
|
position: { start: { ...fragment.position.start }, end: { ...fragment.position.end } },
|
|
313
282
|
children: [],
|
|
314
283
|
sections: []
|
|
@@ -354,7 +323,7 @@ function withMetadataTitle(fragments, metadata2, position) {
|
|
|
354
323
|
if (title === null || title.length === 0) return fragments;
|
|
355
324
|
if (position === null) return fragments;
|
|
356
325
|
if (fragments.some((b) => b.type === "heading" && b.level === 1)) return fragments;
|
|
357
|
-
return [{ type: "heading", level: 1, title, position }, ...fragments];
|
|
326
|
+
return [{ type: "heading", level: 1, title, position, synthetic: true }, ...fragments];
|
|
358
327
|
}
|
|
359
328
|
function withoutFence(content) {
|
|
360
329
|
const loc = locateFrontmatter(content);
|
|
@@ -385,7 +354,9 @@ function parseWith(processor2, format, content, filePath) {
|
|
|
385
354
|
sections: sectionize(
|
|
386
355
|
withMetadataTitle(toFragments(tree), frontmatter, metaPosition),
|
|
387
356
|
documentEnd(tree)
|
|
388
|
-
)
|
|
357
|
+
),
|
|
358
|
+
// Markdown's links are read from mdast by `manni graph` itself.
|
|
359
|
+
links: []
|
|
389
360
|
};
|
|
390
361
|
}
|
|
391
362
|
var SHARED_KINDS = [
|
|
@@ -473,16 +444,18 @@ function positionOf2(node) {
|
|
|
473
444
|
end: { line: loc.endLine, column: loc.endCol, offset: loc.endOffset }
|
|
474
445
|
};
|
|
475
446
|
}
|
|
476
|
-
function rawText(node) {
|
|
447
|
+
function rawText(node, skipHref) {
|
|
477
448
|
if (defaultTreeAdapter.isTextNode(node)) return node.value;
|
|
478
449
|
if (!defaultTreeAdapter.isElementNode(node)) return "";
|
|
479
|
-
|
|
450
|
+
const tag = tagOf(node);
|
|
451
|
+
if (NON_TEXT_TAGS.has(tag)) return "";
|
|
452
|
+
if (skipHref !== void 0 && tag === "a" && attrOf(node, "href") === skipHref) return "";
|
|
480
453
|
let out = "";
|
|
481
|
-
for (const child of node.childNodes) out += rawText(child);
|
|
454
|
+
for (const child of node.childNodes) out += rawText(child, skipHref);
|
|
482
455
|
return out;
|
|
483
456
|
}
|
|
484
|
-
function flatText(node) {
|
|
485
|
-
return rawText(node).replace(/\s+/g, " ").trim();
|
|
457
|
+
function flatText(node, skipHref) {
|
|
458
|
+
return rawText(node, skipHref).replace(/\s+/g, " ").trim();
|
|
486
459
|
}
|
|
487
460
|
function langOf(el) {
|
|
488
461
|
const className = attrOf(el, "class");
|
|
@@ -672,16 +645,16 @@ function listItems(list) {
|
|
|
672
645
|
function childContent2(parent) {
|
|
673
646
|
const out = [];
|
|
674
647
|
let text = "";
|
|
675
|
-
let
|
|
648
|
+
let span = null;
|
|
676
649
|
const extend = (node) => {
|
|
677
650
|
const at = positionOf2(node);
|
|
678
|
-
|
|
651
|
+
span = span === null ? at : { start: span.start, end: at.end };
|
|
679
652
|
};
|
|
680
653
|
const flush = () => {
|
|
681
654
|
const value = text.trim();
|
|
682
|
-
const at =
|
|
655
|
+
const at = span;
|
|
683
656
|
text = "";
|
|
684
|
-
|
|
657
|
+
span = null;
|
|
685
658
|
if (value.length === 0) return;
|
|
686
659
|
out.push({ kind: "paragraph", position: at ?? positionOf2(parent), text: value });
|
|
687
660
|
};
|
|
@@ -707,9 +680,9 @@ function childContent2(parent) {
|
|
|
707
680
|
out.push(...nested);
|
|
708
681
|
continue;
|
|
709
682
|
}
|
|
710
|
-
const
|
|
711
|
-
text +=
|
|
712
|
-
if (
|
|
683
|
+
const inline2 = flatText(child);
|
|
684
|
+
text += inline2;
|
|
685
|
+
if (inline2.trim().length > 0) extend(child);
|
|
713
686
|
}
|
|
714
687
|
flush();
|
|
715
688
|
return out;
|
|
@@ -760,17 +733,21 @@ function contentIn(parent) {
|
|
|
760
733
|
}
|
|
761
734
|
return out;
|
|
762
735
|
}
|
|
763
|
-
function walk(parent, out) {
|
|
736
|
+
function walk(parent, out, ids) {
|
|
764
737
|
for (const child of parent.childNodes) {
|
|
765
738
|
if (!defaultTreeAdapter.isElementNode(child)) continue;
|
|
766
739
|
const tag = tagOf(child);
|
|
767
740
|
const role = roleOf(child, tag);
|
|
768
741
|
if (role === "heading") {
|
|
742
|
+
const id = ids.claim(child);
|
|
769
743
|
out.push({
|
|
770
744
|
type: "heading",
|
|
771
745
|
level: HEADING_LEVELS.get(tag) ?? 1,
|
|
772
|
-
title:
|
|
773
|
-
|
|
746
|
+
// The self-permalink is dropped from the title as it is from the links:
|
|
747
|
+
// Sphinx's `¶` is a widget, and "Install the SDK¶" is no reader's title.
|
|
748
|
+
title: flatText(child, id !== void 0 ? `#${id}` : void 0),
|
|
749
|
+
position: positionOf2(child),
|
|
750
|
+
...id !== void 0 ? { id } : {}
|
|
774
751
|
});
|
|
775
752
|
continue;
|
|
776
753
|
}
|
|
@@ -780,7 +757,44 @@ function walk(parent, out) {
|
|
|
780
757
|
continue;
|
|
781
758
|
}
|
|
782
759
|
if (role === "opaque") continue;
|
|
783
|
-
|
|
760
|
+
if (ID_CARRIERS.has(tag)) {
|
|
761
|
+
ids.enter(child);
|
|
762
|
+
walk(child, out, ids);
|
|
763
|
+
ids.leave();
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
walk(child, out, ids);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
var ID_CARRIERS = /* @__PURE__ */ new Set(["section", "article"]);
|
|
770
|
+
var HeadingIds = class {
|
|
771
|
+
open = [];
|
|
772
|
+
byHeading = /* @__PURE__ */ new Map();
|
|
773
|
+
enter(el) {
|
|
774
|
+
this.open.push({ id: attrOf(el, "id"), spent: false });
|
|
775
|
+
}
|
|
776
|
+
leave() {
|
|
777
|
+
this.open.pop();
|
|
778
|
+
}
|
|
779
|
+
claim(heading) {
|
|
780
|
+
const nearest = this.open.at(-1);
|
|
781
|
+
const inherited = nearest && !nearest.spent ? nearest.id : void 0;
|
|
782
|
+
for (const carrier of this.open) carrier.spent = true;
|
|
783
|
+
const id = attrOf(heading, "id") ?? inherited;
|
|
784
|
+
if (id !== void 0) this.byHeading.set(heading, id);
|
|
785
|
+
return id;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
function collectLinks(parent, ids, heading, out) {
|
|
789
|
+
for (const child of parent.childNodes) {
|
|
790
|
+
if (!defaultTreeAdapter.isElementNode(child)) continue;
|
|
791
|
+
const tag = tagOf(child);
|
|
792
|
+
if (tag === "a" || tag === "area") {
|
|
793
|
+
const href = attrOf(child, "href");
|
|
794
|
+
const permalink = heading !== void 0 && href === `#${heading}`;
|
|
795
|
+
if (href !== void 0 && !permalink) out.push({ target: href, position: positionOf2(child) });
|
|
796
|
+
}
|
|
797
|
+
collectLinks(child, ids, HEADING_LEVELS.has(tag) ? ids.get(child) : heading, out);
|
|
784
798
|
}
|
|
785
799
|
}
|
|
786
800
|
function documentEnd2(content) {
|
|
@@ -817,7 +831,10 @@ function findHead(doc) {
|
|
|
817
831
|
function parseHtml(content, filePath) {
|
|
818
832
|
const doc = parse(content, { sourceCodeLocationInfo: true });
|
|
819
833
|
const fragments = [];
|
|
820
|
-
|
|
834
|
+
const ids = new HeadingIds();
|
|
835
|
+
walk(doc, fragments, ids);
|
|
836
|
+
const links = [];
|
|
837
|
+
collectLinks(doc, ids.byHeading, void 0, links);
|
|
821
838
|
const { frontmatter, position } = metadataOf(content, filePath, doc);
|
|
822
839
|
return {
|
|
823
840
|
format: "html",
|
|
@@ -827,7 +844,8 @@ function parseHtml(content, filePath) {
|
|
|
827
844
|
sections: sectionize(
|
|
828
845
|
withMetadataTitle(fragments, frontmatter, position),
|
|
829
846
|
documentEnd2(content)
|
|
830
|
-
)
|
|
847
|
+
),
|
|
848
|
+
links
|
|
831
849
|
};
|
|
832
850
|
}
|
|
833
851
|
var htmlParser = {
|
|
@@ -1235,11 +1253,13 @@ function scanRange(src, levels, from, to, base, headings) {
|
|
|
1235
1253
|
if (headings) {
|
|
1236
1254
|
const hit = titleAt(src, i, to);
|
|
1237
1255
|
if (hit) {
|
|
1256
|
+
const id = labelAbove(src, hit.first);
|
|
1238
1257
|
fragments.push({
|
|
1239
1258
|
type: "heading",
|
|
1240
1259
|
level: levelFor(levels, hit.style),
|
|
1241
1260
|
title: hit.title,
|
|
1242
|
-
position: spanOf(src, hit.first, hit.last)
|
|
1261
|
+
position: spanOf(src, hit.first, hit.last),
|
|
1262
|
+
...id !== void 0 ? { id } : {}
|
|
1243
1263
|
});
|
|
1244
1264
|
i = hit.last + 1;
|
|
1245
1265
|
continue;
|
|
@@ -1568,6 +1588,106 @@ function readParagraph(src, levels, fragments, from, to, base, headings) {
|
|
|
1568
1588
|
});
|
|
1569
1589
|
return Math.max(blockEnd, end);
|
|
1570
1590
|
}
|
|
1591
|
+
var TARGET = /^[ \t]*\.\.[ \t]+_(?:`([^`]+)`|([^:`]+)):(?:[ \t]+(.*?))?[ \t]*$/;
|
|
1592
|
+
function targetAt(src, line) {
|
|
1593
|
+
const match = TARGET.exec(src.text[line] ?? "");
|
|
1594
|
+
const name = (match?.[1] ?? match?.[2] ?? "").trim();
|
|
1595
|
+
if (!match || name === "" || name === "_") return null;
|
|
1596
|
+
let uri = match[3] ?? "";
|
|
1597
|
+
const base = indentOf(src.text[line] ?? "");
|
|
1598
|
+
for (let next = line + 1; next < src.text.length; next++) {
|
|
1599
|
+
const text = src.text[next] ?? "";
|
|
1600
|
+
if (isBlank(text) || indentOf(text) <= base) break;
|
|
1601
|
+
uri += text;
|
|
1602
|
+
}
|
|
1603
|
+
return { name, uri: uri.replace(/\s+/g, "") };
|
|
1604
|
+
}
|
|
1605
|
+
var refName = (name) => name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
1606
|
+
function labelAbove(src, first) {
|
|
1607
|
+
let line = first - 1;
|
|
1608
|
+
while (line >= 0 && isBlank(src.text[line])) line--;
|
|
1609
|
+
const target = line >= 0 ? targetAt(src, line) : null;
|
|
1610
|
+
return target && target.uri === "" ? target.name : void 0;
|
|
1611
|
+
}
|
|
1612
|
+
function codeLines(nodes, out) {
|
|
1613
|
+
for (const node of nodes) {
|
|
1614
|
+
if (node.kind === "codeBlock") {
|
|
1615
|
+
for (let l = node.position.start.line; l <= node.position.end.line; l++) out.add(l);
|
|
1616
|
+
} else if (node.kind === "list") {
|
|
1617
|
+
codeLines(node.items, out);
|
|
1618
|
+
} else if (node.kind === "definitionItem") {
|
|
1619
|
+
codeLines(node.definition, out);
|
|
1620
|
+
} else if ("children" in node) {
|
|
1621
|
+
codeLines(node.children, out);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
return out;
|
|
1625
|
+
}
|
|
1626
|
+
var blank = (text, index, length) => text.slice(0, index) + " ".repeat(length) + text.slice(index + length);
|
|
1627
|
+
var INLINE_LITERAL = /``.+?``/g;
|
|
1628
|
+
var DOC_ROLE = /:doc:`([^`]*)`/g;
|
|
1629
|
+
var ROLE = /:[\w.+:-]+:`[^`]*`/g;
|
|
1630
|
+
var EMBEDDED = /`([^`<]*?)\s*<([^<>`]+)>`(__?)/g;
|
|
1631
|
+
var PHRASE_REF = /`([^`]+)`_(?!_)/g;
|
|
1632
|
+
var SIMPLE_REF = /(?<![\w\\])([A-Za-z0-9]+(?:[-_.:+][A-Za-z0-9]+)*)_(?!\w)/g;
|
|
1633
|
+
function collectLinks2(src, from, code) {
|
|
1634
|
+
const targets = /* @__PURE__ */ new Map();
|
|
1635
|
+
for (let line = from; line < src.text.length; line++) {
|
|
1636
|
+
const target = targetAt(src, line);
|
|
1637
|
+
if (target && target.uri !== "") targets.set(refName(target.name), target.uri);
|
|
1638
|
+
}
|
|
1639
|
+
for (let line = from; line < src.text.length; line++) {
|
|
1640
|
+
for (const m of (src.text[line] ?? "").matchAll(EMBEDDED)) {
|
|
1641
|
+
const name = refName(m[1] ?? "");
|
|
1642
|
+
if (m[3] === "_" && name !== "" && !targets.has(name)) {
|
|
1643
|
+
targets.set(name, (m[2] ?? "").replace(/\s+/g, ""));
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
const resolve = (name) => {
|
|
1648
|
+
let uri = targets.get(refName(name));
|
|
1649
|
+
for (let hops = 0; uri?.endsWith("_") && hops < targets.size; hops++) {
|
|
1650
|
+
uri = targets.get(refName(uri.slice(0, -1)));
|
|
1651
|
+
}
|
|
1652
|
+
return uri?.endsWith("_") ? void 0 : uri;
|
|
1653
|
+
};
|
|
1654
|
+
const links = [];
|
|
1655
|
+
for (let line = from; line < src.text.length; line++) {
|
|
1656
|
+
let text = src.text[line] ?? "";
|
|
1657
|
+
if (code.has(line + 1) || TARGET.test(text)) continue;
|
|
1658
|
+
const hits = [];
|
|
1659
|
+
const take = (pattern, toTarget) => {
|
|
1660
|
+
for (const m of text.matchAll(pattern)) {
|
|
1661
|
+
const target = toTarget(m);
|
|
1662
|
+
if (target) hits.push({ column: m.index + 1, length: m[0].length, target });
|
|
1663
|
+
text = blank(text, m.index, m[0].length);
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
take(INLINE_LITERAL, () => void 0);
|
|
1667
|
+
take(DOC_ROLE, (m) => {
|
|
1668
|
+
const inner = (m[1] ?? "").trim();
|
|
1669
|
+
return /<([^<>]+)>$/.exec(inner)?.[1]?.trim() ?? inner;
|
|
1670
|
+
});
|
|
1671
|
+
take(ROLE, () => void 0);
|
|
1672
|
+
take(EMBEDDED, (m) => {
|
|
1673
|
+
const uri = (m[2] ?? "").replace(/\s+/g, "");
|
|
1674
|
+
return uri.endsWith("_") ? resolve(uri.slice(0, -1)) : uri;
|
|
1675
|
+
});
|
|
1676
|
+
take(PHRASE_REF, (m) => resolve(m[1] ?? ""));
|
|
1677
|
+
take(SIMPLE_REF, (m) => resolve(m[1] ?? ""));
|
|
1678
|
+
hits.sort((a, b) => a.column - b.column);
|
|
1679
|
+
for (const hit of hits) {
|
|
1680
|
+
links.push({
|
|
1681
|
+
target: hit.target,
|
|
1682
|
+
position: {
|
|
1683
|
+
start: pointAt(src, line, hit.column),
|
|
1684
|
+
end: pointAt(src, line, hit.column + hit.length)
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return links;
|
|
1690
|
+
}
|
|
1571
1691
|
function docinfoPosition(src) {
|
|
1572
1692
|
const limit = src.text.length;
|
|
1573
1693
|
let i = 0;
|
|
@@ -1647,6 +1767,14 @@ var rstParser = {
|
|
|
1647
1767
|
sections: sectionize(
|
|
1648
1768
|
withMetadataTitle(fragments, frontmatter, position),
|
|
1649
1769
|
src.end
|
|
1770
|
+
),
|
|
1771
|
+
links: collectLinks2(
|
|
1772
|
+
src,
|
|
1773
|
+
Math.max(bodyStart, 0),
|
|
1774
|
+
codeLines(
|
|
1775
|
+
fragments.flatMap((f) => f.type === "content" ? [f.node] : []),
|
|
1776
|
+
/* @__PURE__ */ new Set()
|
|
1777
|
+
)
|
|
1650
1778
|
)
|
|
1651
1779
|
};
|
|
1652
1780
|
}
|
|
@@ -1719,7 +1847,12 @@ var DITA = {
|
|
|
1719
1847
|
definitionLists: ["dl"],
|
|
1720
1848
|
definitionItems: ["dlentry"],
|
|
1721
1849
|
definitionTerms: ["dt"],
|
|
1722
|
-
definitionBodies: ["dd"]
|
|
1850
|
+
definitionBodies: ["dd"],
|
|
1851
|
+
// `keyref`, `conkeyref` and `conref` are not here: a key resolves only
|
|
1852
|
+
// against a map at build time, and a conref pulls content in rather than
|
|
1853
|
+
// pointing a reader anywhere.
|
|
1854
|
+
links: [{ elements: ["xref", "link"], attribute: "href" }],
|
|
1855
|
+
ditaHrefs: true
|
|
1723
1856
|
};
|
|
1724
1857
|
var DOCBOOK = {
|
|
1725
1858
|
name: "docbook",
|
|
@@ -1784,34 +1917,40 @@ var DOCBOOK = {
|
|
|
1784
1917
|
definitionTerms: ["term"],
|
|
1785
1918
|
// DocBook reuses `<listitem>` for a variable list's own body, distinct from
|
|
1786
1919
|
// the `listItems` bucket above which reads it inside `<itemizedlist>`.
|
|
1787
|
-
definitionBodies: ["listitem"]
|
|
1920
|
+
definitionBodies: ["listitem"],
|
|
1921
|
+
links: [
|
|
1922
|
+
{ elements: ["link"], attribute: "href" },
|
|
1923
|
+
{ elements: ["ulink"], attribute: "url" },
|
|
1924
|
+
{ elements: ["xref", "link"], attribute: "linkend", idref: true }
|
|
1925
|
+
]
|
|
1788
1926
|
};
|
|
1927
|
+
var MAP_ENTRIES = [
|
|
1928
|
+
"map",
|
|
1929
|
+
"bookmap",
|
|
1930
|
+
"topicref",
|
|
1931
|
+
"topichead",
|
|
1932
|
+
"mapref",
|
|
1933
|
+
"glossref",
|
|
1934
|
+
"anchorref",
|
|
1935
|
+
"navref",
|
|
1936
|
+
"topicset",
|
|
1937
|
+
"topicsetref",
|
|
1938
|
+
"chapter",
|
|
1939
|
+
"part",
|
|
1940
|
+
"appendix",
|
|
1941
|
+
"appendices",
|
|
1942
|
+
"preface",
|
|
1943
|
+
"notices",
|
|
1944
|
+
"dedication",
|
|
1945
|
+
"colophon",
|
|
1946
|
+
"amendments"
|
|
1947
|
+
];
|
|
1789
1948
|
var DITAMAP = {
|
|
1790
1949
|
name: "ditamap",
|
|
1791
1950
|
label: "DITA map",
|
|
1792
1951
|
namespaces: [],
|
|
1793
1952
|
roots: ["map", "bookmap"],
|
|
1794
|
-
sections:
|
|
1795
|
-
"map",
|
|
1796
|
-
"bookmap",
|
|
1797
|
-
"topicref",
|
|
1798
|
-
"topichead",
|
|
1799
|
-
"mapref",
|
|
1800
|
-
"glossref",
|
|
1801
|
-
"anchorref",
|
|
1802
|
-
"navref",
|
|
1803
|
-
"topicset",
|
|
1804
|
-
"topicsetref",
|
|
1805
|
-
"chapter",
|
|
1806
|
-
"part",
|
|
1807
|
-
"appendix",
|
|
1808
|
-
"appendices",
|
|
1809
|
-
"preface",
|
|
1810
|
-
"notices",
|
|
1811
|
-
"dedication",
|
|
1812
|
-
"colophon",
|
|
1813
|
-
"amendments"
|
|
1814
|
-
],
|
|
1953
|
+
sections: MAP_ENTRIES,
|
|
1815
1954
|
// `navtitle` sits in both buckets on purpose, because DITA spells it either
|
|
1816
1955
|
// way. The element form wins: `headingOf` asks `titleOf` first, so
|
|
1817
1956
|
// `@navtitle` is read only when the entry has no `<topicmeta><navtitle>`.
|
|
@@ -1831,7 +1970,12 @@ var DITAMAP = {
|
|
|
1831
1970
|
codeLangAttributes: [],
|
|
1832
1971
|
unorderedLists: [],
|
|
1833
1972
|
orderedLists: [],
|
|
1834
|
-
listItems: []
|
|
1973
|
+
listItems: [],
|
|
1974
|
+
// Every navigation entry's `href`. `<keydef>` is not an entry - it binds a
|
|
1975
|
+
// key to a resource and never appears in a table of contents - so its
|
|
1976
|
+
// `href` is not a link, for the reason it is not a section.
|
|
1977
|
+
links: [{ elements: MAP_ENTRIES, attribute: "href" }],
|
|
1978
|
+
ditaHrefs: true
|
|
1835
1979
|
};
|
|
1836
1980
|
var XML_VOCABULARIES = [
|
|
1837
1981
|
DITA,
|
|
@@ -1865,6 +2009,11 @@ function compile(vocab) {
|
|
|
1865
2009
|
definitionItems: lower(vocab.definitionItems),
|
|
1866
2010
|
definitionTerms: lower(vocab.definitionTerms),
|
|
1867
2011
|
definitionBodies: lower(vocab.definitionBodies),
|
|
2012
|
+
links: (vocab.links ?? []).map((l) => ({
|
|
2013
|
+
elements: lower(l.elements),
|
|
2014
|
+
attribute: l.attribute.toLowerCase(),
|
|
2015
|
+
idref: l.idref ?? false
|
|
2016
|
+
})),
|
|
1868
2017
|
known: /* @__PURE__ */ new Set()
|
|
1869
2018
|
};
|
|
1870
2019
|
for (const set of [
|
|
@@ -1907,6 +2056,19 @@ function* elementChildren2(el) {
|
|
|
1907
2056
|
if (child.nodeType === ELEMENT_NODE) yield child;
|
|
1908
2057
|
}
|
|
1909
2058
|
}
|
|
2059
|
+
function attributeByLocalName(el, name) {
|
|
2060
|
+
for (let i = 0; i < el.attributes.length; i++) {
|
|
2061
|
+
const attr = el.attributes.item(i);
|
|
2062
|
+
if (attr && localName(attr) === name) return attr.value;
|
|
2063
|
+
}
|
|
2064
|
+
return null;
|
|
2065
|
+
}
|
|
2066
|
+
function ditaTarget(href) {
|
|
2067
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(href)) return href;
|
|
2068
|
+
const hash = href.indexOf("#");
|
|
2069
|
+
const slash = href.lastIndexOf("/");
|
|
2070
|
+
return hash === -1 || slash < hash ? href : href.slice(0, hash + 1) + href.slice(slash + 1);
|
|
2071
|
+
}
|
|
1910
2072
|
function findDirectChild(el, names) {
|
|
1911
2073
|
for (const child of elementChildren2(el)) {
|
|
1912
2074
|
if (names.has(localName(child))) return child;
|
|
@@ -1942,7 +2104,7 @@ var NAMESPACE_MATCH = 1e3;
|
|
|
1942
2104
|
var EXCLUSIVE_ELEMENT = 10;
|
|
1943
2105
|
var ROOT_NAME = 5;
|
|
1944
2106
|
var SHARED_ELEMENT = 1;
|
|
1945
|
-
function chooseVocabulary(root,
|
|
2107
|
+
function chooseVocabulary(root, compiled) {
|
|
1946
2108
|
const counts = /* @__PURE__ */ new Map();
|
|
1947
2109
|
const tally = (node) => {
|
|
1948
2110
|
for (const child of elementChildren2(node)) {
|
|
@@ -1954,14 +2116,14 @@ function chooseVocabulary(root, compiled3) {
|
|
|
1954
2116
|
counts.set(localName(root), 1);
|
|
1955
2117
|
tally(root);
|
|
1956
2118
|
const claims = /* @__PURE__ */ new Map();
|
|
1957
|
-
for (const c of
|
|
2119
|
+
for (const c of compiled) {
|
|
1958
2120
|
for (const name of c.known) claims.set(name, (claims.get(name) ?? 0) + 1);
|
|
1959
2121
|
}
|
|
1960
2122
|
const ns = (root.namespaceURI ?? "").toLowerCase();
|
|
1961
2123
|
const rootName = localName(root);
|
|
1962
2124
|
let best;
|
|
1963
2125
|
let bestScore = -1;
|
|
1964
|
-
for (const c of
|
|
2126
|
+
for (const c of compiled) {
|
|
1965
2127
|
let score = 0;
|
|
1966
2128
|
if (ns && c.vocab.namespaces.some((u) => u.toLowerCase() === ns)) {
|
|
1967
2129
|
score += NAMESPACE_MATCH;
|
|
@@ -2104,10 +2266,12 @@ var Flattener = class {
|
|
|
2104
2266
|
return;
|
|
2105
2267
|
}
|
|
2106
2268
|
const next = Math.min(level + 1, 6);
|
|
2269
|
+
const id = attributeByLocalName(el, "id");
|
|
2107
2270
|
this.fragments.push({
|
|
2108
2271
|
type: "heading",
|
|
2109
2272
|
level: next,
|
|
2110
2273
|
title: heading.title,
|
|
2274
|
+
...id ? { id } : {},
|
|
2111
2275
|
// From the container's start tag through the end of its title: the
|
|
2112
2276
|
// container is the section, so a section that began at its `<title>`
|
|
2113
2277
|
// would leave its own opening tag outside itself and stop adjacent
|
|
@@ -2124,6 +2288,23 @@ var Flattener = class {
|
|
|
2124
2288
|
const node = this.content(el);
|
|
2125
2289
|
if (node) this.fragments.push({ type: "content", node });
|
|
2126
2290
|
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Every link target under `el`, in document order. A walk of its own rather
|
|
2293
|
+
* than part of `visit`, because `visit` skips unmapped subtrees and
|
|
2294
|
+
* `<related-links>` is one of them.
|
|
2295
|
+
*/
|
|
2296
|
+
links(el, out = []) {
|
|
2297
|
+
const name = localName(el);
|
|
2298
|
+
for (const source of this.c.links) {
|
|
2299
|
+
if (!source.elements.has(name)) continue;
|
|
2300
|
+
const value = attributeByLocalName(el, source.attribute);
|
|
2301
|
+
if (!value) continue;
|
|
2302
|
+
const target = source.idref ? `#${value}` : this.c.vocab.ditaHrefs ? ditaTarget(value) : value;
|
|
2303
|
+
out.push({ target, position: this.span(el) });
|
|
2304
|
+
}
|
|
2305
|
+
for (const child of elementChildren2(el)) this.links(child, out);
|
|
2306
|
+
return out;
|
|
2307
|
+
}
|
|
2127
2308
|
walkChildren(el, level) {
|
|
2128
2309
|
for (const child of elementChildren2(el)) this.visit(child, level);
|
|
2129
2310
|
}
|
|
@@ -2423,12 +2604,12 @@ function readMetadata2(content, filePath) {
|
|
|
2423
2604
|
function parseXml(content, filePath, vocabularies = XML_VOCABULARIES) {
|
|
2424
2605
|
const map = new SourceMap(content);
|
|
2425
2606
|
const root = parseDocument(map.text, filePath);
|
|
2426
|
-
const
|
|
2427
|
-
if (
|
|
2607
|
+
const compiled = vocabularies.map(compile);
|
|
2608
|
+
if (compiled.length === 0) {
|
|
2428
2609
|
throw new LintError(`${filePath}: no XML vocabularies are configured.`);
|
|
2429
2610
|
}
|
|
2430
|
-
const { best, score } = chooseVocabulary(root,
|
|
2431
|
-
const names =
|
|
2611
|
+
const { best, score } = chooseVocabulary(root, compiled);
|
|
2612
|
+
const names = compiled.map((c) => c.vocab.label).join(", ");
|
|
2432
2613
|
if (score <= 0) {
|
|
2433
2614
|
throw new LintError(
|
|
2434
2615
|
`${filePath}: no known XML vocabulary matched <${localName(root)}>. manni lint understands ${names}; add an entry to XML_VOCABULARIES in src/parsers/xml.ts to describe another schema.`
|
|
@@ -2453,7 +2634,8 @@ function parseXml(content, filePath, vocabularies = XML_VOCABULARIES) {
|
|
|
2453
2634
|
start: map.point(root) ?? { ...map.docEnd },
|
|
2454
2635
|
end: map.point(root.firstChild) ?? map.endOfLine(map.point(root)?.line ?? 1)
|
|
2455
2636
|
} : null,
|
|
2456
|
-
sections: sectionize(fragments, map.docEnd)
|
|
2637
|
+
sections: sectionize(fragments, map.docEnd),
|
|
2638
|
+
links: flattener.links(root)
|
|
2457
2639
|
};
|
|
2458
2640
|
}
|
|
2459
2641
|
var xmlParser = {
|
|
@@ -2560,7 +2742,10 @@ var NAMED_ENTITIES = {
|
|
|
2560
2742
|
};
|
|
2561
2743
|
function flattenInline2(html) {
|
|
2562
2744
|
if (!html) return "";
|
|
2563
|
-
return html.replace(/<[^>]*>/g, "")
|
|
2745
|
+
return decodeEntities(html.replace(/<[^>]*>/g, ""));
|
|
2746
|
+
}
|
|
2747
|
+
function decodeEntities(text) {
|
|
2748
|
+
return text.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(
|
|
2564
2749
|
/&#x([0-9a-f]+);/gi,
|
|
2565
2750
|
(_, hex) => String.fromCodePoint(Number.parseInt(hex, 16))
|
|
2566
2751
|
).replace(
|
|
@@ -2568,6 +2753,21 @@ function flattenInline2(html) {
|
|
|
2568
2753
|
(whole, name) => NAMED_ENTITIES[name.toLowerCase()] ?? whole
|
|
2569
2754
|
);
|
|
2570
2755
|
}
|
|
2756
|
+
var harvested = [];
|
|
2757
|
+
var ANCHOR = /<a\s([^>]*)>/gi;
|
|
2758
|
+
var HREF = /(?:^|\s)href="([^"]*)"/;
|
|
2759
|
+
var CLASS = /(?:^|\s)class="([^"]*)"/;
|
|
2760
|
+
function inline(html, line) {
|
|
2761
|
+
for (const match of (html ?? "").matchAll(ANCHOR)) {
|
|
2762
|
+
const attrs = match[1] ?? "";
|
|
2763
|
+
const href = HREF.exec(attrs)?.[1];
|
|
2764
|
+
if (href === void 0) continue;
|
|
2765
|
+
const classes = (CLASS.exec(attrs)?.[1] ?? "").split(/\s+/);
|
|
2766
|
+
if (classes.includes("include") || classes.includes("footnote")) continue;
|
|
2767
|
+
harvested.push({ target: decodeEntities(href), line });
|
|
2768
|
+
}
|
|
2769
|
+
return flattenInline2(html);
|
|
2770
|
+
}
|
|
2571
2771
|
function stringAttribute(node, name) {
|
|
2572
2772
|
const value = node.getAttribute?.(name);
|
|
2573
2773
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -2578,7 +2778,7 @@ function draftList(node, ordered) {
|
|
|
2578
2778
|
const item = asItem(raw);
|
|
2579
2779
|
if (!item) continue;
|
|
2580
2780
|
const line = lineOf(item);
|
|
2581
|
-
const text =
|
|
2781
|
+
const text = inline(item.getText(), line);
|
|
2582
2782
|
const children = [];
|
|
2583
2783
|
if (text.length > 0) children.push({ kind: "paragraph", line, text });
|
|
2584
2784
|
collect(
|
|
@@ -2608,7 +2808,7 @@ function admonitionVariant(node) {
|
|
|
2608
2808
|
function draftAdmonition(node, variant) {
|
|
2609
2809
|
const children = draftChildren(node);
|
|
2610
2810
|
if (children.length === 0) {
|
|
2611
|
-
const text =
|
|
2811
|
+
const text = inline(node.getContent?.(), lineOf(node));
|
|
2612
2812
|
if (text.length > 0) children.push({ kind: "paragraph", line: lineOf(node), text });
|
|
2613
2813
|
}
|
|
2614
2814
|
return { kind: "admonition", line: lineOf(node), variant, children };
|
|
@@ -2616,7 +2816,7 @@ function draftAdmonition(node, variant) {
|
|
|
2616
2816
|
function draftBlockquote(node) {
|
|
2617
2817
|
const children = draftChildren(node);
|
|
2618
2818
|
if (children.length === 0) {
|
|
2619
|
-
const text =
|
|
2819
|
+
const text = inline(node.getContent?.(), lineOf(node));
|
|
2620
2820
|
if (text.length > 0) children.push({ kind: "paragraph", line: lineOf(node), text });
|
|
2621
2821
|
}
|
|
2622
2822
|
return { kind: "blockquote", line: lineOf(node), children };
|
|
@@ -2626,10 +2826,10 @@ function draftTable(node) {
|
|
|
2626
2826
|
const toRow = (header) => (cells) => ({
|
|
2627
2827
|
line: cells[0]?.getLineNumber?.() ?? lineOf(node),
|
|
2628
2828
|
header,
|
|
2629
|
-
cells: cells.map((cell) =>
|
|
2630
|
-
line
|
|
2631
|
-
text:
|
|
2632
|
-
})
|
|
2829
|
+
cells: cells.map((cell) => {
|
|
2830
|
+
const line = cell.getLineNumber?.() ?? lineOf(node);
|
|
2831
|
+
return { line, text: inline(cell.getText(), line) };
|
|
2832
|
+
})
|
|
2633
2833
|
});
|
|
2634
2834
|
return {
|
|
2635
2835
|
kind: "table",
|
|
@@ -2652,7 +2852,7 @@ function draftDefinitionList(node) {
|
|
|
2652
2852
|
const children = [];
|
|
2653
2853
|
const desc = asItem(rawDesc);
|
|
2654
2854
|
if (desc) {
|
|
2655
|
-
const text =
|
|
2855
|
+
const text = inline(desc.getText(), lineOf(desc));
|
|
2656
2856
|
if (text.length > 0) children.push({ kind: "paragraph", line: lineOf(desc), text });
|
|
2657
2857
|
collect(
|
|
2658
2858
|
desc.getBlocks().map(asNode).filter((n) => n !== null),
|
|
@@ -2661,7 +2861,7 @@ function draftDefinitionList(node) {
|
|
|
2661
2861
|
}
|
|
2662
2862
|
items.push({
|
|
2663
2863
|
line: lineOf(first),
|
|
2664
|
-
term: terms.map((t) =>
|
|
2864
|
+
term: terms.map((t) => inline(t.getText(), lineOf(t))).join(", "),
|
|
2665
2865
|
children
|
|
2666
2866
|
});
|
|
2667
2867
|
}
|
|
@@ -2673,7 +2873,7 @@ function draftOf(node) {
|
|
|
2673
2873
|
return {
|
|
2674
2874
|
kind: "paragraph",
|
|
2675
2875
|
line: lineOf(node),
|
|
2676
|
-
text:
|
|
2876
|
+
text: inline(node.getContent?.(), lineOf(node))
|
|
2677
2877
|
};
|
|
2678
2878
|
case "listing":
|
|
2679
2879
|
case "literal": {
|
|
@@ -2700,7 +2900,7 @@ function draftOf(node) {
|
|
|
2700
2900
|
case "image": {
|
|
2701
2901
|
const url = stringAttribute(node, "target");
|
|
2702
2902
|
if (!url) return null;
|
|
2703
|
-
const title =
|
|
2903
|
+
const title = inline(node.getTitle?.(), lineOf(node));
|
|
2704
2904
|
return {
|
|
2705
2905
|
kind: "image",
|
|
2706
2906
|
line: lineOf(node),
|
|
@@ -2721,13 +2921,15 @@ function collect(nodes, out) {
|
|
|
2721
2921
|
for (const node of nodes) {
|
|
2722
2922
|
const context = node.getContext();
|
|
2723
2923
|
if (context === "section") {
|
|
2924
|
+
const id = node.getId?.();
|
|
2724
2925
|
out.push({
|
|
2725
2926
|
kind: "heading",
|
|
2726
2927
|
line: lineOf(node),
|
|
2727
2928
|
// Asciidoctor's `=` title is level 0, so every level is one shallower
|
|
2728
2929
|
// than the heading level the rest of the tool speaks in.
|
|
2729
2930
|
level: (node.getLevel?.() ?? 0) + 1,
|
|
2730
|
-
title:
|
|
2931
|
+
title: inline(node.getTitle?.(), lineOf(node)),
|
|
2932
|
+
...id ? { id } : {}
|
|
2731
2933
|
});
|
|
2732
2934
|
collect(childrenOf(node), out);
|
|
2733
2935
|
continue;
|
|
@@ -2749,6 +2951,7 @@ function place(drafts, end, index) {
|
|
|
2749
2951
|
type: "heading",
|
|
2750
2952
|
level: draft.level,
|
|
2751
2953
|
title: draft.title,
|
|
2954
|
+
...draft.id !== void 0 ? { id: draft.id } : {},
|
|
2752
2955
|
position: {
|
|
2753
2956
|
start: index.start(draft.line),
|
|
2754
2957
|
end: index.endOfLine(draft.line)
|
|
@@ -2922,9 +3125,18 @@ function load(content, filePath) {
|
|
|
2922
3125
|
// Without this every block's `getLineNumber()` is undefined and there are
|
|
2923
3126
|
// no positions to derive at all.
|
|
2924
3127
|
sourcemap: true,
|
|
2925
|
-
//
|
|
2926
|
-
//
|
|
2927
|
-
|
|
3128
|
+
// Asciidoctor.js's default already, pinned because it is what keeps
|
|
3129
|
+
// `include::` from reading the disk: a refused include renders as a link
|
|
3130
|
+
// to its target, which `inline` then drops.
|
|
3131
|
+
safe: "secure",
|
|
3132
|
+
attributes: {
|
|
3133
|
+
// A leading `---` fence is a thematic break in AsciiDoc; this consumes
|
|
3134
|
+
// it as metadata instead, without disturbing line numbering.
|
|
3135
|
+
"skip-front-matter": true,
|
|
3136
|
+
// `xref:other.adoc[]` keeps its source suffix rather than becoming
|
|
3137
|
+
// `other.html`, so a harvested link names the file that exists.
|
|
3138
|
+
relfilesuffix: ".adoc"
|
|
3139
|
+
}
|
|
2928
3140
|
});
|
|
2929
3141
|
} catch (err) {
|
|
2930
3142
|
throw new LintError(
|
|
@@ -2936,7 +3148,7 @@ function headerDraft(doc) {
|
|
|
2936
3148
|
if (!doc.hasHeader()) return null;
|
|
2937
3149
|
const header = asNode(doc.getHeader());
|
|
2938
3150
|
if (!header) return null;
|
|
2939
|
-
const title =
|
|
3151
|
+
const title = inline(header.getTitle?.(), lineOf(header));
|
|
2940
3152
|
if (title.length === 0) return null;
|
|
2941
3153
|
return {
|
|
2942
3154
|
kind: "heading",
|
|
@@ -2948,10 +3160,15 @@ function headerDraft(doc) {
|
|
|
2948
3160
|
function parse2(content, filePath) {
|
|
2949
3161
|
const doc = load(content, filePath);
|
|
2950
3162
|
const index = lineIndex(content);
|
|
3163
|
+
harvested = [];
|
|
2951
3164
|
const drafts = [];
|
|
2952
3165
|
const header = headerDraft(doc);
|
|
2953
3166
|
if (header) drafts.push(header);
|
|
2954
3167
|
collect(childrenOf(doc), drafts);
|
|
3168
|
+
const links = harvested.sort((a, b) => a.line - b.line).map(({ target, line }) => ({
|
|
3169
|
+
target,
|
|
3170
|
+
position: { start: index.start(line), end: index.endOfLine(line) }
|
|
3171
|
+
}));
|
|
2955
3172
|
const meta = metadata(content, filePath, index);
|
|
2956
3173
|
const end = index.documentEnd();
|
|
2957
3174
|
return {
|
|
@@ -2962,7 +3179,8 @@ function parse2(content, filePath) {
|
|
|
2962
3179
|
sections: sectionize(
|
|
2963
3180
|
withMetadataTitle(place(drafts, end, index), meta.data, meta.position),
|
|
2964
3181
|
end
|
|
2965
|
-
)
|
|
3182
|
+
),
|
|
3183
|
+
links
|
|
2966
3184
|
};
|
|
2967
3185
|
}
|
|
2968
3186
|
var asciidocParser = {
|
|
@@ -3010,6 +3228,9 @@ function parserByName(name) {
|
|
|
3010
3228
|
function supportedExtensions() {
|
|
3011
3229
|
return PARSERS.flatMap((p) => p.walkExtensions ?? p.extensions);
|
|
3012
3230
|
}
|
|
3231
|
+
function unsupportedFormatMessage(name) {
|
|
3232
|
+
return `no parser is registered for "${name}". Supported extensions: ${supportedExtensions().join(", ")}. Use --as to override.`;
|
|
3233
|
+
}
|
|
3013
3234
|
function listFormats() {
|
|
3014
3235
|
return PARSERS.map((p) => ({
|
|
3015
3236
|
name: p.name,
|
|
@@ -3019,2422 +3240,11 @@ function listFormats() {
|
|
|
3019
3240
|
}));
|
|
3020
3241
|
}
|
|
3021
3242
|
|
|
3022
|
-
// src/lint/core/template-registry.ts
|
|
3023
|
-
import { readFile } from "node:fs/promises";
|
|
3024
|
-
import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
3025
|
-
import { parse as parseYaml } from "yaml";
|
|
3026
|
-
import { dereference } from "@apidevtools/json-schema-ref-parser";
|
|
3027
|
-
import * as AjvNs from "ajv";
|
|
3028
|
-
|
|
3029
|
-
// schemas/lint/template.json
|
|
3030
|
-
var template_default = {
|
|
3031
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
3032
|
-
$id: "manni-lint:template:2",
|
|
3033
|
-
title: "manni lint template file",
|
|
3034
|
-
description: "A template file: named doctype templates, plus the reusable fragments they point at with `$ref`. Validated after dereferencing, so no `$ref` survives to be seen here. A template is itself a rule: it describes the page, and it takes every rule key plus `title`, `types` and `extends`.",
|
|
3035
|
-
type: "object",
|
|
3036
|
-
additionalProperties: false,
|
|
3037
|
-
required: ["templates"],
|
|
3038
|
-
properties: {
|
|
3039
|
-
$schema: {
|
|
3040
|
-
description: "URL of this schema, so an editor can offer completion. Read by editors, never by the linter.",
|
|
3041
|
-
type: "string"
|
|
3042
|
-
},
|
|
3043
|
-
info: {
|
|
3044
|
-
description: "Free-form metadata about the file (title, version, author). Never read by the linter; declared so a file that carries it is not rejected.",
|
|
3045
|
-
type: "object"
|
|
3046
|
-
},
|
|
3047
|
-
templates: {
|
|
3048
|
-
description: "The doctype templates this file defines, keyed by name.",
|
|
3049
|
-
type: "object",
|
|
3050
|
-
additionalProperties: false,
|
|
3051
|
-
patternProperties: {
|
|
3052
|
-
"^[A-Za-z][A-Za-z0-9-_]*$": { $ref: "#/definitions/template" }
|
|
3053
|
-
}
|
|
3054
|
-
},
|
|
3055
|
-
components: {
|
|
3056
|
-
description: "Reusable fragments, targeted by `$ref` from elsewhere in the file. Left unconstrained: a fragment is validated where it is inlined, not where it is declared.",
|
|
3057
|
-
type: "object"
|
|
3058
|
-
}
|
|
3059
|
-
},
|
|
3060
|
-
definitions: {
|
|
3061
|
-
min: {
|
|
3062
|
-
description: "Fewest occurrences. Absent means one. Deliberately carries no JSON Schema `default`, and neither does any other key in this file: Ajv's `useDefaults` would write one in at load time, and a written-in value is indistinguishable from a value the author typed, so it wins the `extends` merge against the parent's real value. The code reads `undefined` instead.",
|
|
3063
|
-
type: "integer",
|
|
3064
|
-
minimum: 0
|
|
3065
|
-
},
|
|
3066
|
-
max: {
|
|
3067
|
-
description: "Most occurrences. Absent is unbounded, so write `max: 1` for exactly once and `max: 0` to forbid. Must be at least `min` where both are written; the loader checks that, because JSON Schema cannot compare two siblings.",
|
|
3068
|
-
type: "integer",
|
|
3069
|
-
minimum: 0
|
|
3070
|
-
},
|
|
3071
|
-
id: {
|
|
3072
|
-
description: "Handle for this rule, unique among its siblings. It appears in messages, and `extends` targets it to replace one inherited rule in place.",
|
|
3073
|
-
type: "string",
|
|
3074
|
-
pattern: "^[A-Za-z][A-Za-z0-9-_]*$"
|
|
3075
|
-
},
|
|
3076
|
-
description: {
|
|
3077
|
-
description: "Prose description of what the rule is for. Never checked.",
|
|
3078
|
-
type: "string"
|
|
3079
|
-
},
|
|
3080
|
-
heading: {
|
|
3081
|
-
description: "The heading of the section this rule describes. Absent means any heading, or none. A string is exact text, a list is one-of, `{ pattern }` is an unanchored regular expression, and `false` means the section has no heading of its own.",
|
|
3082
|
-
oneOf: [
|
|
3083
|
-
{ type: "string", minLength: 1 },
|
|
3084
|
-
{
|
|
3085
|
-
type: "array",
|
|
3086
|
-
minItems: 1,
|
|
3087
|
-
items: { type: "string", minLength: 1 }
|
|
3088
|
-
},
|
|
3089
|
-
{
|
|
3090
|
-
type: "object",
|
|
3091
|
-
additionalProperties: false,
|
|
3092
|
-
required: ["pattern"],
|
|
3093
|
-
properties: {
|
|
3094
|
-
pattern: { type: "string", minLength: 1 }
|
|
3095
|
-
}
|
|
3096
|
-
},
|
|
3097
|
-
{ const: false }
|
|
3098
|
-
]
|
|
3099
|
-
},
|
|
3100
|
-
attributes: {
|
|
3101
|
-
description: "Literal attributes the node must carry. A string is the required value; `true` requires the attribute to be present, and `false` requires it to be absent.",
|
|
3102
|
-
type: "object",
|
|
3103
|
-
additionalProperties: {
|
|
3104
|
-
oneOf: [{ type: "string" }, { type: "boolean" }]
|
|
3105
|
-
}
|
|
3106
|
-
},
|
|
3107
|
-
oneOfSequenceOrContains: {
|
|
3108
|
-
description: "A rule says what it holds in order (`sequence`) or in any order (`contains`), never both.",
|
|
3109
|
-
type: "object",
|
|
3110
|
-
not: { required: ["sequence", "contains"] }
|
|
3111
|
-
},
|
|
3112
|
-
noHeadingOnRepeat: {
|
|
3113
|
-
description: "`repeat` groups sibling rules into one repeated unit, so the group has no heading of its own. Put the heading on the first rule inside it.",
|
|
3114
|
-
type: "object",
|
|
3115
|
-
not: { required: ["heading", "repeat"] }
|
|
3116
|
-
},
|
|
3117
|
-
noInstructions: {
|
|
3118
|
-
description: "`instructions` was the language-model hook of doc-structure-lint. manni lint is deterministic and has no model, so a template carrying it is rejected outright rather than silently ignored; the loader turns this into a migration message pointing at manni docevals.",
|
|
3119
|
-
type: "object",
|
|
3120
|
-
not: { required: ["instructions"] }
|
|
3121
|
-
},
|
|
3122
|
-
template: {
|
|
3123
|
-
description: "One doctype template. A template is a rule describing the page itself, so it takes every rule key, plus `title`, `types` and `extends`.",
|
|
3124
|
-
type: "object",
|
|
3125
|
-
additionalProperties: false,
|
|
3126
|
-
allOf: [
|
|
3127
|
-
{ $ref: "#/definitions/noInstructions" },
|
|
3128
|
-
{ $ref: "#/definitions/oneOfSequenceOrContains" },
|
|
3129
|
-
{ $ref: "#/definitions/noHeadingOnRepeat" }
|
|
3130
|
-
],
|
|
3131
|
-
properties: {
|
|
3132
|
-
title: {
|
|
3133
|
-
description: "Human-readable name for the template, shown by `manni lint templates`.",
|
|
3134
|
-
type: "string"
|
|
3135
|
-
},
|
|
3136
|
-
types: {
|
|
3137
|
-
description: "Doctypes this template serves, matched against a page's `type` frontmatter.",
|
|
3138
|
-
type: "array",
|
|
3139
|
-
items: { type: "string", minLength: 1 }
|
|
3140
|
-
},
|
|
3141
|
-
extends: {
|
|
3142
|
-
description: "Template reference (built-in id, path, or URL, optionally with a `#name` fragment) whose rules this template inherits.",
|
|
3143
|
-
type: "string",
|
|
3144
|
-
minLength: 1
|
|
3145
|
-
},
|
|
3146
|
-
id: { $ref: "#/definitions/id" },
|
|
3147
|
-
description: { $ref: "#/definitions/description" },
|
|
3148
|
-
heading: { $ref: "#/definitions/heading" },
|
|
3149
|
-
repeat: { $ref: "#/definitions/repeat" },
|
|
3150
|
-
min: {
|
|
3151
|
-
description: "Refused on a template: a page is one page. Declared here only so the loader can say that in a sentence, rather than leaving Ajv to report an unexpected key.",
|
|
3152
|
-
type: "integer",
|
|
3153
|
-
minimum: 0
|
|
3154
|
-
},
|
|
3155
|
-
max: {
|
|
3156
|
-
description: "Refused on a template, for the same reason as `min`.",
|
|
3157
|
-
type: "integer",
|
|
3158
|
-
minimum: 0
|
|
3159
|
-
},
|
|
3160
|
-
sequence: { $ref: "#/definitions/sequence" },
|
|
3161
|
-
contains: { $ref: "#/definitions/blockRules" },
|
|
3162
|
-
sections: { $ref: "#/definitions/rules" }
|
|
3163
|
-
}
|
|
3164
|
-
},
|
|
3165
|
-
rule: {
|
|
3166
|
-
description: "One rule. It describes a section of the page, or, with `repeat`, a run of sections matched as one unit.",
|
|
3167
|
-
type: "object",
|
|
3168
|
-
additionalProperties: false,
|
|
3169
|
-
allOf: [
|
|
3170
|
-
{ $ref: "#/definitions/noInstructions" },
|
|
3171
|
-
{ $ref: "#/definitions/oneOfSequenceOrContains" },
|
|
3172
|
-
{ $ref: "#/definitions/noHeadingOnRepeat" }
|
|
3173
|
-
],
|
|
3174
|
-
properties: {
|
|
3175
|
-
id: { $ref: "#/definitions/id" },
|
|
3176
|
-
description: { $ref: "#/definitions/description" },
|
|
3177
|
-
heading: { $ref: "#/definitions/heading" },
|
|
3178
|
-
repeat: { $ref: "#/definitions/repeat" },
|
|
3179
|
-
min: { $ref: "#/definitions/min" },
|
|
3180
|
-
max: { $ref: "#/definitions/max" },
|
|
3181
|
-
sequence: { $ref: "#/definitions/sequence" },
|
|
3182
|
-
contains: { $ref: "#/definitions/blockRules" },
|
|
3183
|
-
sections: { $ref: "#/definitions/rules" }
|
|
3184
|
-
}
|
|
3185
|
-
},
|
|
3186
|
-
rules: {
|
|
3187
|
-
description: "Rules for the subsections, in document order.",
|
|
3188
|
-
type: "array",
|
|
3189
|
-
items: { $ref: "#/definitions/rule" }
|
|
3190
|
-
},
|
|
3191
|
-
repeat: {
|
|
3192
|
-
description: "Rules matched in order as one unit, repeated by this rule's `min` and `max`.",
|
|
3193
|
-
type: "array",
|
|
3194
|
-
minItems: 1,
|
|
3195
|
-
items: { $ref: "#/definitions/rule" }
|
|
3196
|
-
},
|
|
3197
|
-
sequence: {
|
|
3198
|
-
description: "What the section holds, in order. Each entry is one run of one content kind.",
|
|
3199
|
-
type: "array",
|
|
3200
|
-
minItems: 1,
|
|
3201
|
-
items: {
|
|
3202
|
-
allOf: [
|
|
3203
|
-
{ $ref: "#/definitions/blockRules" },
|
|
3204
|
-
{ type: "object", maxProperties: 1 }
|
|
3205
|
-
]
|
|
3206
|
-
}
|
|
3207
|
-
},
|
|
3208
|
-
blockRules: {
|
|
3209
|
-
description: "Block rules, keyed by content kind. The key is plural because it carries a count: the node model says `codeBlock`, a template says `codeBlocks: { min: 1 }`.",
|
|
3210
|
-
type: "object",
|
|
3211
|
-
additionalProperties: false,
|
|
3212
|
-
minProperties: 1,
|
|
3213
|
-
properties: {
|
|
3214
|
-
paragraphs: { $ref: "#/definitions/paragraphs" },
|
|
3215
|
-
codeBlocks: { $ref: "#/definitions/codeBlocks" },
|
|
3216
|
-
lists: { $ref: "#/definitions/lists" },
|
|
3217
|
-
tables: { $ref: "#/definitions/tables" },
|
|
3218
|
-
admonitions: { $ref: "#/definitions/admonitions" },
|
|
3219
|
-
images: { $ref: "#/definitions/images" },
|
|
3220
|
-
blockquotes: { $ref: "#/definitions/counted" },
|
|
3221
|
-
definitionLists: { $ref: "#/definitions/counted" },
|
|
3222
|
-
elements: { $ref: "#/definitions/elements" }
|
|
3223
|
-
}
|
|
3224
|
-
},
|
|
3225
|
-
counted: {
|
|
3226
|
-
description: "A block rule that only counts.",
|
|
3227
|
-
type: "object",
|
|
3228
|
-
additionalProperties: false,
|
|
3229
|
-
properties: {
|
|
3230
|
-
min: { $ref: "#/definitions/min" },
|
|
3231
|
-
max: { $ref: "#/definitions/max" }
|
|
3232
|
-
}
|
|
3233
|
-
},
|
|
3234
|
-
paragraphs: {
|
|
3235
|
-
description: "Paragraph requirements.",
|
|
3236
|
-
type: "object",
|
|
3237
|
-
additionalProperties: false,
|
|
3238
|
-
properties: {
|
|
3239
|
-
min: { $ref: "#/definitions/min" },
|
|
3240
|
-
max: { $ref: "#/definitions/max" },
|
|
3241
|
-
pattern: {
|
|
3242
|
-
description: "Unanchored regular expression every paragraph in the run must match.",
|
|
3243
|
-
type: "string",
|
|
3244
|
-
minLength: 1
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
},
|
|
3248
|
-
codeBlocks: {
|
|
3249
|
-
description: "Code block requirements.",
|
|
3250
|
-
type: "object",
|
|
3251
|
-
additionalProperties: false,
|
|
3252
|
-
properties: {
|
|
3253
|
-
min: { $ref: "#/definitions/min" },
|
|
3254
|
-
max: { $ref: "#/definitions/max" },
|
|
3255
|
-
language: {
|
|
3256
|
-
description: "Language every code block must be tagged with, as the node model spells it. A list is one-of: the block must declare one of them.",
|
|
3257
|
-
oneOf: [
|
|
3258
|
-
{ type: "string", minLength: 1 },
|
|
3259
|
-
{
|
|
3260
|
-
type: "array",
|
|
3261
|
-
minItems: 1,
|
|
3262
|
-
items: { type: "string", minLength: 1 }
|
|
3263
|
-
}
|
|
3264
|
-
]
|
|
3265
|
-
},
|
|
3266
|
-
fenceInfo: {
|
|
3267
|
-
description: "Info-string tail after the language, where the format carries one.",
|
|
3268
|
-
type: "string"
|
|
3269
|
-
}
|
|
3270
|
-
}
|
|
3271
|
-
},
|
|
3272
|
-
lists: {
|
|
3273
|
-
description: "List requirements.",
|
|
3274
|
-
type: "object",
|
|
3275
|
-
additionalProperties: false,
|
|
3276
|
-
properties: {
|
|
3277
|
-
min: { $ref: "#/definitions/min" },
|
|
3278
|
-
max: { $ref: "#/definitions/max" },
|
|
3279
|
-
ordered: {
|
|
3280
|
-
description: "Whether the list must be ordered. Absent accepts either.",
|
|
3281
|
-
type: "boolean"
|
|
3282
|
-
},
|
|
3283
|
-
items: { $ref: "#/definitions/listItems" }
|
|
3284
|
-
}
|
|
3285
|
-
},
|
|
3286
|
-
listItems: {
|
|
3287
|
-
description: "Requirements applied to every item of every list in the run. Recurses arbitrarily deep.",
|
|
3288
|
-
type: "object",
|
|
3289
|
-
additionalProperties: false,
|
|
3290
|
-
allOf: [{ $ref: "#/definitions/oneOfSequenceOrContains" }],
|
|
3291
|
-
properties: {
|
|
3292
|
-
min: { $ref: "#/definitions/min" },
|
|
3293
|
-
max: { $ref: "#/definitions/max" },
|
|
3294
|
-
sequence: { $ref: "#/definitions/sequence" },
|
|
3295
|
-
contains: { $ref: "#/definitions/blockRules" }
|
|
3296
|
-
}
|
|
3297
|
-
},
|
|
3298
|
-
tables: {
|
|
3299
|
-
description: "Table requirements.",
|
|
3300
|
-
type: "object",
|
|
3301
|
-
additionalProperties: false,
|
|
3302
|
-
properties: {
|
|
3303
|
-
min: { $ref: "#/definitions/min" },
|
|
3304
|
-
max: { $ref: "#/definitions/max" },
|
|
3305
|
-
columns: {
|
|
3306
|
-
description: "The header cells, in order.",
|
|
3307
|
-
type: "array",
|
|
3308
|
-
items: { type: "string" }
|
|
3309
|
-
}
|
|
3310
|
-
}
|
|
3311
|
-
},
|
|
3312
|
-
admonitions: {
|
|
3313
|
-
description: "Admonition requirements.",
|
|
3314
|
-
type: "object",
|
|
3315
|
-
additionalProperties: false,
|
|
3316
|
-
properties: {
|
|
3317
|
-
min: { $ref: "#/definitions/min" },
|
|
3318
|
-
max: { $ref: "#/definitions/max" },
|
|
3319
|
-
variant: {
|
|
3320
|
-
description: "Which flavor of admonition. The node model's `variant`, not any one format's spelling.",
|
|
3321
|
-
enum: [
|
|
3322
|
-
"note",
|
|
3323
|
-
"tip",
|
|
3324
|
-
"important",
|
|
3325
|
-
"caution",
|
|
3326
|
-
"warning",
|
|
3327
|
-
"danger"
|
|
3328
|
-
]
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
},
|
|
3332
|
-
images: {
|
|
3333
|
-
description: "Image requirements.",
|
|
3334
|
-
type: "object",
|
|
3335
|
-
additionalProperties: false,
|
|
3336
|
-
properties: {
|
|
3337
|
-
min: { $ref: "#/definitions/min" },
|
|
3338
|
-
max: { $ref: "#/definitions/max" },
|
|
3339
|
-
url: {
|
|
3340
|
-
description: "Where the image must point.",
|
|
3341
|
-
type: "string"
|
|
3342
|
-
},
|
|
3343
|
-
alt: {
|
|
3344
|
-
description: "Alternative text the image must carry.",
|
|
3345
|
-
type: "string"
|
|
3346
|
-
},
|
|
3347
|
-
attributes: { $ref: "#/definitions/attributes" }
|
|
3348
|
-
}
|
|
3349
|
-
},
|
|
3350
|
-
elements: {
|
|
3351
|
-
description: "Named-wrapper requirements. `sequence` and `contains` here describe what the element holds, which is never the enclosing section's content.",
|
|
3352
|
-
type: "object",
|
|
3353
|
-
additionalProperties: false,
|
|
3354
|
-
allOf: [{ $ref: "#/definitions/oneOfSequenceOrContains" }],
|
|
3355
|
-
properties: {
|
|
3356
|
-
min: { $ref: "#/definitions/min" },
|
|
3357
|
-
max: { $ref: "#/definitions/max" },
|
|
3358
|
-
tag: {
|
|
3359
|
-
description: "The element's name, as an author of the format would write it. A list is one-of: an element counts when it carries any of them.",
|
|
3360
|
-
oneOf: [
|
|
3361
|
-
{ type: "string", minLength: 1 },
|
|
3362
|
-
{
|
|
3363
|
-
type: "array",
|
|
3364
|
-
minItems: 1,
|
|
3365
|
-
items: { type: "string", minLength: 1 }
|
|
3366
|
-
}
|
|
3367
|
-
]
|
|
3368
|
-
},
|
|
3369
|
-
attributes: { $ref: "#/definitions/attributes" },
|
|
3370
|
-
sequence: { $ref: "#/definitions/sequence" },
|
|
3371
|
-
contains: { $ref: "#/definitions/blockRules" }
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
}
|
|
3375
|
-
};
|
|
3376
|
-
|
|
3377
|
-
// templates/lint/tgdp/manifest.json
|
|
3378
|
-
var manifest_default = {
|
|
3379
|
-
$comment: 'Built-in doctype templates derived from The Good Docs Project templates, pinned to release v1.6.0 ("Iron"): https://gitlab.com/tgdp/templates/-/tree/v1.6.0. Each entry\'s `file` is a YAML template beside this manifest; `source` is the upstream Markdown it was derived from, vendored under test/lint/fixtures/tgdp/ and linted against the derived template so the two cannot drift. Run `npm run check:tgdp-pin` to see whether upstream has moved past the pin.',
|
|
3380
|
-
vendor: "tgdp",
|
|
3381
|
-
upstream: "https://gitlab.com/tgdp/templates",
|
|
3382
|
-
pin: "v1.6.0",
|
|
3383
|
-
templates: [
|
|
3384
|
-
{
|
|
3385
|
-
id: "tgdp:how-to:1.6",
|
|
3386
|
-
file: "tgdp/how-to.yaml",
|
|
3387
|
-
title: "How-to guide (The Good Docs Project)",
|
|
3388
|
-
types: ["how-to"],
|
|
3389
|
-
source: "how-to/template_how-to.md"
|
|
3390
|
-
},
|
|
3391
|
-
{
|
|
3392
|
-
id: "tgdp:tutorial:1.6",
|
|
3393
|
-
file: "tgdp/tutorial.yaml",
|
|
3394
|
-
title: "Tutorial (The Good Docs Project)",
|
|
3395
|
-
types: ["tutorial"],
|
|
3396
|
-
source: "tutorial/template_tutorial.md"
|
|
3397
|
-
},
|
|
3398
|
-
{
|
|
3399
|
-
id: "tgdp:reference:1.6",
|
|
3400
|
-
file: "tgdp/reference.yaml",
|
|
3401
|
-
title: "Reference (The Good Docs Project)",
|
|
3402
|
-
types: ["reference"],
|
|
3403
|
-
source: "reference/template_reference.md"
|
|
3404
|
-
},
|
|
3405
|
-
{
|
|
3406
|
-
id: "tgdp:concept:1.6",
|
|
3407
|
-
file: "tgdp/concept.yaml",
|
|
3408
|
-
title: "Concept (The Good Docs Project)",
|
|
3409
|
-
types: ["concept", "explanation"],
|
|
3410
|
-
source: "concept/template_concept.md"
|
|
3411
|
-
},
|
|
3412
|
-
{
|
|
3413
|
-
id: "tgdp:troubleshooting:1.6",
|
|
3414
|
-
file: "tgdp/troubleshooting.yaml",
|
|
3415
|
-
title: "Troubleshooting (The Good Docs Project)",
|
|
3416
|
-
types: ["troubleshooting"],
|
|
3417
|
-
source: "troubleshooting/template_troubleshooting.md"
|
|
3418
|
-
},
|
|
3419
|
-
{
|
|
3420
|
-
id: "tgdp:release-notes:1.6",
|
|
3421
|
-
file: "tgdp/release-notes.yaml",
|
|
3422
|
-
title: "Release notes (The Good Docs Project)",
|
|
3423
|
-
types: ["release-notes"],
|
|
3424
|
-
source: "release-notes/template_release-notes.md"
|
|
3425
|
-
},
|
|
3426
|
-
{
|
|
3427
|
-
id: "tgdp:readme:1.6",
|
|
3428
|
-
file: "tgdp/readme.yaml",
|
|
3429
|
-
title: "README (The Good Docs Project)",
|
|
3430
|
-
types: ["readme"],
|
|
3431
|
-
source: "readme/template_readme.md"
|
|
3432
|
-
}
|
|
3433
|
-
]
|
|
3434
|
-
};
|
|
3435
|
-
|
|
3436
|
-
// src/lint/core/template-registry.ts
|
|
3437
|
-
var Ajv = AjvNs.default;
|
|
3438
|
-
var DEREFERENCE_OPTIONS = { resolve: { external: false } };
|
|
3439
|
-
var MANIFESTS = [manifest_default];
|
|
3440
|
-
var BUILTINS = /* @__PURE__ */ new Map();
|
|
3441
|
-
for (const manifest of MANIFESTS) {
|
|
3442
|
-
for (const entry of manifest.templates) BUILTINS.set(entry.id, entry);
|
|
3443
|
-
}
|
|
3444
|
-
var builtinCache = /* @__PURE__ */ new Map();
|
|
3445
|
-
function listBuiltins() {
|
|
3446
|
-
return [...BUILTINS.values()].map((entry) => ({
|
|
3447
|
-
id: entry.id,
|
|
3448
|
-
title: entry.title,
|
|
3449
|
-
types: entry.types
|
|
3450
|
-
}));
|
|
3451
|
-
}
|
|
3452
|
-
async function readBuiltinFile(id, file) {
|
|
3453
|
-
const path = join(packageRoot(import.meta.url), "templates", "lint", file);
|
|
3454
|
-
try {
|
|
3455
|
-
return await readFile(path, "utf8");
|
|
3456
|
-
} catch (err) {
|
|
3457
|
-
throw new LintError(
|
|
3458
|
-
`Built-in template "${id}" is registered but its file could not be read (${path}): ${errorMessage(err)}`
|
|
3459
|
-
);
|
|
3460
|
-
}
|
|
3461
|
-
}
|
|
3462
|
-
async function loadBuiltin(id) {
|
|
3463
|
-
const cached = builtinCache.get(id);
|
|
3464
|
-
if (cached) return cached;
|
|
3465
|
-
const entry = BUILTINS.get(id);
|
|
3466
|
-
if (!entry) {
|
|
3467
|
-
throw new LintError(`No built-in template is registered under "${id}".`);
|
|
3468
|
-
}
|
|
3469
|
-
const raw = await readBuiltinFile(id, entry.file);
|
|
3470
|
-
const parsed = parseYaml(raw);
|
|
3471
|
-
if (!isRecord(parsed)) {
|
|
3472
|
-
throw new LintError(
|
|
3473
|
-
`Built-in template "${id}" (${entry.file}) is not a template file: expected an object at the top level, got ${parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed}.`
|
|
3474
|
-
);
|
|
3475
|
-
}
|
|
3476
|
-
const file = validateTemplateFile(
|
|
3477
|
-
await dereference(parsed, DEREFERENCE_OPTIONS),
|
|
3478
|
-
entry.file
|
|
3479
|
-
);
|
|
3480
|
-
const names = Object.keys(file.templates ?? {});
|
|
3481
|
-
const template = file.templates?.[names[0] ?? ""];
|
|
3482
|
-
if (!template || names.length !== 1) {
|
|
3483
|
-
throw new LintError(
|
|
3484
|
-
`Built-in template "${id}" must define exactly one template, found ${names.length}.`
|
|
3485
|
-
);
|
|
3486
|
-
}
|
|
3487
|
-
const resolved = { types: entry.types, ...template };
|
|
3488
|
-
builtinCache.set(id, resolved);
|
|
3489
|
-
return resolved;
|
|
3490
|
-
}
|
|
3491
|
-
var BUILTIN_ID = /^[a-z0-9][a-z0-9._-]*(?::[a-z0-9][a-z0-9._-]*)+$/i;
|
|
3492
|
-
var FILE_EXTENSIONS = [".yaml", ".yml", ".json"];
|
|
3493
|
-
function classifyRef(ref) {
|
|
3494
|
-
if (/^https?:\/\//i.test(ref)) return { kind: "url", ref };
|
|
3495
|
-
const lower2 = ref.toLowerCase();
|
|
3496
|
-
if (!ref.includes("/") && !ref.includes("\\") && !FILE_EXTENSIONS.some((ext) => lower2.endsWith(ext)) && BUILTIN_ID.test(ref)) {
|
|
3497
|
-
return { kind: "builtin", ref };
|
|
3498
|
-
}
|
|
3499
|
-
return { kind: "file", ref };
|
|
3500
|
-
}
|
|
3501
|
-
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
3502
|
-
var ajv = new Ajv();
|
|
3503
|
-
var compiled = null;
|
|
3504
|
-
function fileValidator() {
|
|
3505
|
-
compiled ??= ajv.compile(template_default);
|
|
3506
|
-
return compiled;
|
|
3507
|
-
}
|
|
3508
|
-
function isRecord(value) {
|
|
3509
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3510
|
-
}
|
|
3511
|
-
function stringsOf(value) {
|
|
3512
|
-
if (typeof value === "string") return [value];
|
|
3513
|
-
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
3514
|
-
return [];
|
|
3515
|
-
}
|
|
3516
|
-
function suggestEvalName(segments) {
|
|
3517
|
-
const name = segments.filter((segment) => segment !== "templates" && segment !== "sections").join("-").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3518
|
-
return name || "section-instructions";
|
|
3519
|
-
}
|
|
3520
|
-
function findInstructions(node, segments = [], seen = /* @__PURE__ */ new WeakSet()) {
|
|
3521
|
-
if (typeof node !== "object" || node === null) return null;
|
|
3522
|
-
if (seen.has(node)) return null;
|
|
3523
|
-
seen.add(node);
|
|
3524
|
-
if (Array.isArray(node)) {
|
|
3525
|
-
for (const [index, item] of node.entries()) {
|
|
3526
|
-
const hit = findInstructions(item, [...segments, String(index)], seen);
|
|
3527
|
-
if (hit) return hit;
|
|
3528
|
-
}
|
|
3529
|
-
return null;
|
|
3530
|
-
}
|
|
3531
|
-
const record = node;
|
|
3532
|
-
const insideSectionsMap = segments.at(-1) === "sections";
|
|
3533
|
-
if (!insideSectionsMap && "instructions" in record) {
|
|
3534
|
-
return {
|
|
3535
|
-
path: segments.join("."),
|
|
3536
|
-
name: suggestEvalName(segments),
|
|
3537
|
-
values: stringsOf(record.instructions)
|
|
3538
|
-
};
|
|
3539
|
-
}
|
|
3540
|
-
for (const [key, value] of Object.entries(record)) {
|
|
3541
|
-
const hit = findInstructions(value, [...segments, key], seen);
|
|
3542
|
-
if (hit) return hit;
|
|
3543
|
-
}
|
|
3544
|
-
return null;
|
|
3545
|
-
}
|
|
3546
|
-
function instructionsMessage(source, hit) {
|
|
3547
|
-
const assertion = hit.values[0] ?? "";
|
|
3548
|
-
return [
|
|
3549
|
-
`${source}: "${hit.path}" uses \`instructions\`, which manni lint no longer evaluates \u2014 structure checking is deterministic. Move it to a manni docevals assertion eval in manni.config.yaml:`,
|
|
3550
|
-
"",
|
|
3551
|
-
" docevals:",
|
|
3552
|
-
" evals:",
|
|
3553
|
-
` ${hit.name}:`,
|
|
3554
|
-
` assertion: ${assertion}`,
|
|
3555
|
-
" grader: ai"
|
|
3556
|
-
].join("\n");
|
|
3557
|
-
}
|
|
3558
|
-
var V1_KEYS = {
|
|
3559
|
-
required: '"required" is not a template key. A rule is optional with "min: 0".',
|
|
3560
|
-
additionalSections: '"additionalSections" is not a template key. v2 has no equivalent, so drop it.',
|
|
3561
|
-
code_blocks: '"code_blocks" is not a template key. The v2 spelling is "codeBlocks".'
|
|
3562
|
-
};
|
|
3563
|
-
var V1_SECTIONS_MAP = '"sections" is a list of rules, not a map. Name each rule with "id" and list them in order.';
|
|
3564
|
-
var V1_REPEAT_BOOLEAN = '"repeat" is a list of rules, not a boolean. A rule repeats through "min" and "max".';
|
|
3565
|
-
var V1_PARAGRAPH_PATTERNS = '"patterns" is not a paragraphs key. The v2 spelling is "pattern", one regular expression.';
|
|
3566
|
-
function findV1Key(node, seen = /* @__PURE__ */ new WeakSet()) {
|
|
3567
|
-
if (typeof node !== "object" || node === null) return null;
|
|
3568
|
-
if (seen.has(node)) return null;
|
|
3569
|
-
seen.add(node);
|
|
3570
|
-
if (Array.isArray(node)) {
|
|
3571
|
-
for (const item of node) {
|
|
3572
|
-
const hit = findV1Key(item, seen);
|
|
3573
|
-
if (hit) return hit;
|
|
3574
|
-
}
|
|
3575
|
-
return null;
|
|
3576
|
-
}
|
|
3577
|
-
const record = node;
|
|
3578
|
-
for (const [key, message] of Object.entries(V1_KEYS)) {
|
|
3579
|
-
if (key in record) return message;
|
|
3580
|
-
}
|
|
3581
|
-
if (isRecord(record.sections)) return V1_SECTIONS_MAP;
|
|
3582
|
-
if (typeof record.repeat === "boolean") return V1_REPEAT_BOOLEAN;
|
|
3583
|
-
if (isRecord(record.paragraphs) && "patterns" in record.paragraphs) {
|
|
3584
|
-
return V1_PARAGRAPH_PATTERNS;
|
|
3585
|
-
}
|
|
3586
|
-
for (const value of Object.values(record)) {
|
|
3587
|
-
const hit = findV1Key(value, seen);
|
|
3588
|
-
if (hit) return hit;
|
|
3589
|
-
}
|
|
3590
|
-
return null;
|
|
3591
|
-
}
|
|
3592
|
-
function findV1File(data) {
|
|
3593
|
-
if (!isRecord(data)) return null;
|
|
3594
|
-
return findV1Key(data.templates) ?? findV1Key(data.components);
|
|
3595
|
-
}
|
|
3596
|
-
function ruleLabel(rule, path) {
|
|
3597
|
-
return rule.id === void 0 ? path : `"${rule.id}"`;
|
|
3598
|
-
}
|
|
3599
|
-
function checkRange(range, source, label) {
|
|
3600
|
-
const { min, max } = range;
|
|
3601
|
-
if (min !== void 0 && max !== void 0 && max < min) {
|
|
3602
|
-
throw new LintError(`${source}: rule ${label} sets max ${max} below min ${min}.`);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
function sameOccurrenceRange(a, b) {
|
|
3606
|
-
const rangeA = occurrenceRange(a);
|
|
3607
|
-
const rangeB = occurrenceRange(b);
|
|
3608
|
-
return rangeA.min === rangeB.min && rangeA.max === rangeB.max;
|
|
3609
|
-
}
|
|
3610
|
-
function checkPattern(pattern, source) {
|
|
3611
|
-
if (pattern === void 0) return;
|
|
3612
|
-
try {
|
|
3613
|
-
new RegExp(pattern);
|
|
3614
|
-
} catch (err) {
|
|
3615
|
-
throw new LintError(
|
|
3616
|
-
`Invalid pattern "${pattern}" in ${source}: ${errorMessage(err)}.`
|
|
3617
|
-
);
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
function checkListItems(items, source, path) {
|
|
3621
|
-
checkRange(items, source, path);
|
|
3622
|
-
if (items.contains) checkBlockRule(items.contains, source, `${path}.contains`);
|
|
3623
|
-
items.sequence?.forEach((entry, index) => {
|
|
3624
|
-
checkBlockRule(entry, source, `${path}.sequence[${index}]`);
|
|
3625
|
-
});
|
|
3626
|
-
}
|
|
3627
|
-
function checkBlockRule(block, source, path) {
|
|
3628
|
-
for (const kind of BLOCK_KINDS) {
|
|
3629
|
-
const counted = block[kind];
|
|
3630
|
-
if (counted) checkRange(counted, source, `${path}.${kind}`);
|
|
3631
|
-
}
|
|
3632
|
-
checkPattern(block.paragraphs?.pattern, source);
|
|
3633
|
-
if (block.lists?.items) {
|
|
3634
|
-
checkListItems(block.lists.items, source, `${path}.lists.items`);
|
|
3635
|
-
}
|
|
3636
|
-
const element = block.elements;
|
|
3637
|
-
if (element?.contains) {
|
|
3638
|
-
checkBlockRule(element.contains, source, `${path}.elements.contains`);
|
|
3639
|
-
}
|
|
3640
|
-
element?.sequence?.forEach((entry, index) => {
|
|
3641
|
-
checkBlockRule(entry, source, `${path}.elements.sequence[${index}]`);
|
|
3642
|
-
});
|
|
3643
|
-
}
|
|
3644
|
-
function checkRuleBody(rule, source, path) {
|
|
3645
|
-
const heading = rule.heading;
|
|
3646
|
-
if (typeof heading === "object" && !Array.isArray(heading)) {
|
|
3647
|
-
checkPattern(heading.pattern, source);
|
|
3648
|
-
}
|
|
3649
|
-
if (rule.contains) checkBlockRule(rule.contains, source, `${path}.contains`);
|
|
3650
|
-
rule.sequence?.forEach((entry, index) => {
|
|
3651
|
-
checkBlockRule(entry, source, `${path}.sequence[${index}]`);
|
|
3652
|
-
});
|
|
3653
|
-
checkRuleList(rule.repeat, source, `${path}.repeat`);
|
|
3654
|
-
checkRuleList(rule.sections, source, `${path}.sections`);
|
|
3655
|
-
}
|
|
3656
|
-
function checkRuleList(rules, source, path) {
|
|
3657
|
-
if (!rules) return;
|
|
3658
|
-
const ids = /* @__PURE__ */ new Set();
|
|
3659
|
-
for (const [index, rule] of rules.entries()) {
|
|
3660
|
-
const here = `${path}[${index}]`;
|
|
3661
|
-
if (rule.id !== void 0) {
|
|
3662
|
-
if (ids.has(rule.id)) {
|
|
3663
|
-
throw new LintError(`${source}: two sibling rules share the id "${rule.id}".`);
|
|
3664
|
-
}
|
|
3665
|
-
ids.add(rule.id);
|
|
3666
|
-
}
|
|
3667
|
-
checkRange(rule, source, ruleLabel(rule, here));
|
|
3668
|
-
checkRuleBody(rule, source, here);
|
|
3669
|
-
}
|
|
3670
|
-
for (const [index, rule] of rules.entries()) {
|
|
3671
|
-
const next = rules[index + 1];
|
|
3672
|
-
if (!next) break;
|
|
3673
|
-
if (isWildcard(rule) && rule.repeat === void 0 && isWildcard(next) && next.repeat === void 0 && sameOccurrenceRange(rule, next)) {
|
|
3674
|
-
warn(
|
|
3675
|
-
`${source}: two adjacent rules have no heading and no repeat; only rule order tells them apart.`
|
|
3676
|
-
);
|
|
3677
|
-
break;
|
|
3678
|
-
}
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
function checkTemplateFile(file, source) {
|
|
3682
|
-
for (const [name, template] of Object.entries(file.templates ?? {})) {
|
|
3683
|
-
if (template.min !== void 0 || template.max !== void 0) {
|
|
3684
|
-
throw new LintError(
|
|
3685
|
-
`${source}: a template may not set min or max; a page is one page.`
|
|
3686
|
-
);
|
|
3687
|
-
}
|
|
3688
|
-
checkRuleBody(template, source, `templates.${name}`);
|
|
3689
|
-
}
|
|
3690
|
-
}
|
|
3691
|
-
var NOT_CONSTRAINTS = {
|
|
3692
|
-
oneOfSequenceOrContains: "sets both sequence and contains; a rule says what it holds in order, or in any order, never both",
|
|
3693
|
-
noHeadingOnRepeat: "sets both heading and repeat; a repeat group has no heading of its own, so put the heading on the first rule inside it",
|
|
3694
|
-
noInstructions: "carries an instructions key, which this tool does not read"
|
|
3695
|
-
};
|
|
3696
|
-
function describeError(error) {
|
|
3697
|
-
const where = error.instancePath || "/";
|
|
3698
|
-
if (error.keyword === "not") {
|
|
3699
|
-
for (const [name, why] of Object.entries(NOT_CONSTRAINTS)) {
|
|
3700
|
-
if (error.schemaPath.includes(name)) return `${where} ${why}`;
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
const params = error.params;
|
|
3704
|
-
const extra = typeof params.additionalProperty === "string" ? ` ("${params.additionalProperty}")` : "";
|
|
3705
|
-
return `${where} ${error.message ?? "is invalid"}${extra}`;
|
|
3706
|
-
}
|
|
3707
|
-
function validateTemplateFile(data, source) {
|
|
3708
|
-
const instructions = findInstructions(data);
|
|
3709
|
-
if (instructions) throw new LintError(instructionsMessage(source, instructions));
|
|
3710
|
-
const v1 = findV1File(data);
|
|
3711
|
-
if (v1) throw new LintError(`${source}: ${v1}`);
|
|
3712
|
-
const validate = fileValidator();
|
|
3713
|
-
if (!validate(data)) {
|
|
3714
|
-
const errors = validate.errors ?? [];
|
|
3715
|
-
const detail = errors.map(describeError).join("; ") || "does not match the template schema";
|
|
3716
|
-
throw new LintError(`${source} is not a valid template file: ${detail}.`);
|
|
3717
|
-
}
|
|
3718
|
-
const file = data;
|
|
3719
|
-
checkTemplateFile(file, source);
|
|
3720
|
-
return file;
|
|
3721
|
-
}
|
|
3722
|
-
function splitFragment(ref) {
|
|
3723
|
-
const hash = ref.lastIndexOf("#");
|
|
3724
|
-
if (hash === -1) return { base: ref, fragment: null };
|
|
3725
|
-
return { base: ref.slice(0, hash), fragment: ref.slice(hash + 1) || null };
|
|
3726
|
-
}
|
|
3727
|
-
function extensionSource(ref, kind) {
|
|
3728
|
-
if (kind !== "url") return ref;
|
|
3729
|
-
try {
|
|
3730
|
-
return new URL(ref).pathname;
|
|
3731
|
-
} catch {
|
|
3732
|
-
return ref;
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
function parseTemplateFile(raw, source, kind) {
|
|
3736
|
-
const lower2 = extensionSource(source, kind).toLowerCase();
|
|
3737
|
-
if (lower2.endsWith(".json")) {
|
|
3738
|
-
try {
|
|
3739
|
-
return JSON.parse(raw);
|
|
3740
|
-
} catch (err) {
|
|
3741
|
-
throw new LintError(`${source} is not valid JSON: ${errorMessage(err)}`);
|
|
3742
|
-
}
|
|
3743
|
-
}
|
|
3744
|
-
try {
|
|
3745
|
-
return parseYaml(raw);
|
|
3746
|
-
} catch (err) {
|
|
3747
|
-
throw new LintError(`${source} is not valid YAML: ${errorMessage(err)}`);
|
|
3748
|
-
}
|
|
3749
|
-
}
|
|
3750
|
-
async function readText(ref) {
|
|
3751
|
-
try {
|
|
3752
|
-
return await readFile(ref, "utf8");
|
|
3753
|
-
} catch (err) {
|
|
3754
|
-
const code = err?.code;
|
|
3755
|
-
if (code === "ENOENT") {
|
|
3756
|
-
throw new LintError(`Template file not found: "${ref}".`);
|
|
3757
|
-
}
|
|
3758
|
-
throw new LintError(
|
|
3759
|
-
`Template file "${ref}" could not be read: ${errorMessage(err)}`
|
|
3760
|
-
);
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
var urlCache = /* @__PURE__ */ new Map();
|
|
3764
|
-
function clearTemplateCaches() {
|
|
3765
|
-
builtinCache.clear();
|
|
3766
|
-
urlCache.clear();
|
|
3767
|
-
}
|
|
3768
|
-
async function fetchText(ref, timeoutMs) {
|
|
3769
|
-
let res;
|
|
3770
|
-
try {
|
|
3771
|
-
res = await fetch(ref, { signal: AbortSignal.timeout(timeoutMs) });
|
|
3772
|
-
} catch (err) {
|
|
3773
|
-
const e = err;
|
|
3774
|
-
if (e.name === "TimeoutError" || e.name === "AbortError") {
|
|
3775
|
-
throw new LintError(
|
|
3776
|
-
`Failed to fetch template file "${ref}": timed out after ${timeoutMs}ms.`
|
|
3777
|
-
);
|
|
3778
|
-
}
|
|
3779
|
-
throw new LintError(`Failed to fetch template file "${ref}": ${e.message}`);
|
|
3780
|
-
}
|
|
3781
|
-
if (!res.ok) {
|
|
3782
|
-
throw new LintError(`Failed to fetch template file "${ref}": HTTP ${res.status}.`);
|
|
3783
|
-
}
|
|
3784
|
-
return await res.text();
|
|
3785
|
-
}
|
|
3786
|
-
async function dereferenceTemplates(data, source) {
|
|
3787
|
-
try {
|
|
3788
|
-
return await dereference(data, DEREFERENCE_OPTIONS);
|
|
3789
|
-
} catch (err) {
|
|
3790
|
-
throw new LintError(`${source}: could not resolve a "$ref": ${errorMessage(err)}`);
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
async function loadTemplateFile(ref, options = {}) {
|
|
3794
|
-
const { kind } = classifyRef(ref);
|
|
3795
|
-
if (kind === "builtin") {
|
|
3796
|
-
throw new LintError(
|
|
3797
|
-
`"${ref}" is a built-in template id, not a template file. Load it with loadTemplate().`
|
|
3798
|
-
);
|
|
3799
|
-
}
|
|
3800
|
-
const cached = kind === "url" ? urlCache.get(ref) : void 0;
|
|
3801
|
-
if (cached) return cached;
|
|
3802
|
-
const raw = kind === "url" ? await fetchText(ref, options.timeoutMs ?? DEFAULT_TIMEOUT_MS) : await readText(ref);
|
|
3803
|
-
const parsed = parseTemplateFile(raw, ref, kind);
|
|
3804
|
-
if (!isRecord(parsed)) {
|
|
3805
|
-
throw new LintError(
|
|
3806
|
-
`${ref} is not a template file: expected an object at the top level, got ${parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed}.`
|
|
3807
|
-
);
|
|
3808
|
-
}
|
|
3809
|
-
const file = validateTemplateFile(await dereferenceTemplates(parsed, ref), ref);
|
|
3810
|
-
if (kind === "url") urlCache.set(ref, file);
|
|
3811
|
-
return file;
|
|
3812
|
-
}
|
|
3813
|
-
async function loadTemplate(ref, options = {}) {
|
|
3814
|
-
const { base, fragment } = splitFragment(ref);
|
|
3815
|
-
const { kind } = classifyRef(base);
|
|
3816
|
-
if (kind === "builtin") {
|
|
3817
|
-
if (fragment !== null) {
|
|
3818
|
-
throw new LintError(
|
|
3819
|
-
`"${ref}" adds a "#${fragment}" fragment to a built-in template id. A built-in id names one template on its own - drop the fragment and use "${base}". Fragments name a template inside a file, as in "./templates.yaml#how-to".`
|
|
3820
|
-
);
|
|
3821
|
-
}
|
|
3822
|
-
if (!BUILTINS.has(base)) {
|
|
3823
|
-
const available = [...BUILTINS.keys()].join(", ");
|
|
3824
|
-
throw new LintError(
|
|
3825
|
-
`Unknown built-in template "${base}". Available: ${available || "(none)"}.`
|
|
3826
|
-
);
|
|
3827
|
-
}
|
|
3828
|
-
return loadBuiltin(base);
|
|
3829
|
-
}
|
|
3830
|
-
const file = await loadTemplateFile(base, options);
|
|
3831
|
-
const templates = file.templates ?? {};
|
|
3832
|
-
const names = Object.keys(templates);
|
|
3833
|
-
if (fragment !== null) {
|
|
3834
|
-
const named = Object.hasOwn(templates, fragment) ? templates[fragment] : void 0;
|
|
3835
|
-
if (named === void 0) {
|
|
3836
|
-
throw new LintError(
|
|
3837
|
-
`${base} has no template named "${fragment}". Available: ${names.join(", ") || "(none)"}.`
|
|
3838
|
-
);
|
|
3839
|
-
}
|
|
3840
|
-
return named;
|
|
3841
|
-
}
|
|
3842
|
-
const [only] = Object.values(templates);
|
|
3843
|
-
if (names.length === 1 && only !== void 0) return only;
|
|
3844
|
-
if (names.length === 0) throw new LintError(`${base} defines no templates.`);
|
|
3845
|
-
throw new LintError(
|
|
3846
|
-
`${base} defines ${names.length} templates; name one with a "#" fragment (e.g. "${base}#${names[0] ?? ""}"). Available: ${names.join(", ")}.`
|
|
3847
|
-
);
|
|
3848
|
-
}
|
|
3849
|
-
function mergeRuleLists(parent, child) {
|
|
3850
|
-
if (!parent) return child;
|
|
3851
|
-
if (!child) return parent;
|
|
3852
|
-
const merged = [...parent];
|
|
3853
|
-
for (const childRule of child) {
|
|
3854
|
-
const id = childRule.id;
|
|
3855
|
-
const index = id === void 0 ? -1 : merged.findIndex((rule) => rule.id === id);
|
|
3856
|
-
const target = index === -1 ? void 0 : merged[index];
|
|
3857
|
-
if (target === void 0) merged.push(childRule);
|
|
3858
|
-
else merged[index] = mergeRules(target, childRule);
|
|
3859
|
-
}
|
|
3860
|
-
return merged;
|
|
3861
|
-
}
|
|
3862
|
-
function mergeRules(parent, child) {
|
|
3863
|
-
const sections = mergeRuleLists(parent.sections, child.sections);
|
|
3864
|
-
const { sections: _parentSections, ...parentRest } = parent;
|
|
3865
|
-
const { sections: _childSections, ...childRest } = child;
|
|
3866
|
-
const merged = { ...parentRest, ...childRest };
|
|
3867
|
-
if (sections !== void 0) merged.sections = sections;
|
|
3868
|
-
return merged;
|
|
3869
|
-
}
|
|
3870
|
-
function mergeTemplates(parent, child) {
|
|
3871
|
-
const sections = mergeRuleLists(parent.sections, child.sections);
|
|
3872
|
-
const { sections: _parentSections, ...parentRest } = parent;
|
|
3873
|
-
const { sections: _childSections, ...childRest } = child;
|
|
3874
|
-
const merged = { ...parentRest, ...childRest };
|
|
3875
|
-
if (sections !== void 0) merged.sections = sections;
|
|
3876
|
-
delete merged.extends;
|
|
3877
|
-
return merged;
|
|
3878
|
-
}
|
|
3879
|
-
async function resolveExtends(template, load2 = loadTemplate, chain = []) {
|
|
3880
|
-
const parentRef = template.extends;
|
|
3881
|
-
if (parentRef === void 0) return template;
|
|
3882
|
-
if (chain.includes(parentRef)) {
|
|
3883
|
-
throw new LintError(
|
|
3884
|
-
`Template "extends" cycle: ${[...chain, parentRef].join(" -> ")}.`
|
|
3885
|
-
);
|
|
3886
|
-
}
|
|
3887
|
-
const parent = await resolveExtends(await load2(parentRef), load2, [...chain, parentRef]);
|
|
3888
|
-
return mergeTemplates(parent, template);
|
|
3889
|
-
}
|
|
3890
|
-
function refRelativeTo(baseRef, ref) {
|
|
3891
|
-
const { base, fragment } = splitFragment(ref);
|
|
3892
|
-
if (classifyRef(base).kind !== "file" || isAbsolute(base)) return ref;
|
|
3893
|
-
const { base: fromBase } = splitFragment(baseRef);
|
|
3894
|
-
const from = classifyRef(fromBase).kind;
|
|
3895
|
-
if (from === "url") {
|
|
3896
|
-
const rebased2 = new URL(base, fromBase).href;
|
|
3897
|
-
return fragment === null ? rebased2 : `${rebased2}#${fragment}`;
|
|
3898
|
-
}
|
|
3899
|
-
if (from !== "file") return ref;
|
|
3900
|
-
const rebased = resolvePath(dirname(fromBase), base);
|
|
3901
|
-
return fragment === null ? rebased : `${rebased}#${fragment}`;
|
|
3902
|
-
}
|
|
3903
|
-
async function loadResolvedTemplate(ref, options = {}, chain = []) {
|
|
3904
|
-
const seen = chain.length === 0 ? [ref] : chain;
|
|
3905
|
-
const template = await loadTemplate(ref, options);
|
|
3906
|
-
const parentRef = template.extends;
|
|
3907
|
-
if (parentRef === void 0) return template;
|
|
3908
|
-
const absolute = refRelativeTo(ref, parentRef);
|
|
3909
|
-
if (seen.includes(absolute)) {
|
|
3910
|
-
throw new LintError(
|
|
3911
|
-
`Template "extends" cycle: ${[...seen, absolute].join(" -> ")}.`
|
|
3912
|
-
);
|
|
3913
|
-
}
|
|
3914
|
-
const parent = await loadResolvedTemplate(absolute, options, [
|
|
3915
|
-
...seen,
|
|
3916
|
-
absolute
|
|
3917
|
-
]);
|
|
3918
|
-
return mergeTemplates(parent, template);
|
|
3919
|
-
}
|
|
3920
|
-
|
|
3921
|
-
// src/lint/core/resolve-template.ts
|
|
3922
|
-
import picomatch from "picomatch";
|
|
3923
|
-
var FILE_TEMPLATE_KEY = "$template";
|
|
3924
|
-
var TYPE_KEY = "type";
|
|
3925
|
-
function buildTypeIndex(params) {
|
|
3926
|
-
const index = /* @__PURE__ */ new Map();
|
|
3927
|
-
for (const builtin of params.builtins ?? []) {
|
|
3928
|
-
for (const type of builtin.types) {
|
|
3929
|
-
index.set(type, { ref: builtin.id, source: "builtin" });
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
for (const { ref, file } of params.userFiles ?? []) {
|
|
3933
|
-
for (const [name, template] of Object.entries(file.templates ?? {})) {
|
|
3934
|
-
for (const type of template.types ?? []) {
|
|
3935
|
-
index.set(type, { ref: `${ref}#${name}`, source: "user" });
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
3938
|
-
}
|
|
3939
|
-
for (const [type, ref] of Object.entries(params.explicitTypes ?? {})) {
|
|
3940
|
-
index.set(type, { ref, source: "config" });
|
|
3941
|
-
}
|
|
3942
|
-
return index;
|
|
3943
|
-
}
|
|
3944
|
-
var matcherCache = /* @__PURE__ */ new Map();
|
|
3945
|
-
function clearMatcherCache() {
|
|
3946
|
-
matcherCache.clear();
|
|
3947
|
-
}
|
|
3948
|
-
function matchesGlob(glob, filePath) {
|
|
3949
|
-
let m = matcherCache.get(glob);
|
|
3950
|
-
if (!m) {
|
|
3951
|
-
m = picomatch(glob, { dot: true });
|
|
3952
|
-
matcherCache.set(glob, m);
|
|
3953
|
-
}
|
|
3954
|
-
return m(filePath.replace(/\\/g, "/"));
|
|
3955
|
-
}
|
|
3956
|
-
function suggestTypes(unknown, known) {
|
|
3957
|
-
const target = unknown.toLowerCase();
|
|
3958
|
-
const scored = [];
|
|
3959
|
-
for (const type of known) {
|
|
3960
|
-
const distance = editDistance(target, type.toLowerCase());
|
|
3961
|
-
const threshold = target.length <= 4 ? 1 : 3;
|
|
3962
|
-
if (distance <= threshold) scored.push({ type, distance });
|
|
3963
|
-
}
|
|
3964
|
-
return scored.sort((a, b) => a.distance - b.distance || a.type.localeCompare(b.type)).slice(0, 3).map((s) => s.type);
|
|
3965
|
-
}
|
|
3966
|
-
function editDistance(a, b) {
|
|
3967
|
-
if (a === b) return 0;
|
|
3968
|
-
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
3969
|
-
let last = b.length;
|
|
3970
|
-
for (let i = 0; i < a.length; i++) {
|
|
3971
|
-
const row = [i + 1];
|
|
3972
|
-
let left = i + 1;
|
|
3973
|
-
let diagonal = i;
|
|
3974
|
-
for (const [j, above] of prev.slice(1).entries()) {
|
|
3975
|
-
const cost = a.charCodeAt(i) === b.charCodeAt(j) ? 0 : 1;
|
|
3976
|
-
const cell = Math.min(left + 1, above + 1, diagonal + cost);
|
|
3977
|
-
row.push(cell);
|
|
3978
|
-
diagonal = above;
|
|
3979
|
-
left = cell;
|
|
3980
|
-
}
|
|
3981
|
-
prev = row;
|
|
3982
|
-
last = left;
|
|
3983
|
-
}
|
|
3984
|
-
return last;
|
|
3985
|
-
}
|
|
3986
|
-
function refFromFrontmatter(frontmatter, key) {
|
|
3987
|
-
const value = frontmatter?.[key];
|
|
3988
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3989
|
-
}
|
|
3990
|
-
function resolveTemplateRef(params) {
|
|
3991
|
-
const steps = [];
|
|
3992
|
-
const typeIndex = params.typeIndex ?? /* @__PURE__ */ new Map();
|
|
3993
|
-
const decide = (stage, ref) => ({
|
|
3994
|
-
ref,
|
|
3995
|
-
stage,
|
|
3996
|
-
steps
|
|
3997
|
-
});
|
|
3998
|
-
if (params.cliTemplate) {
|
|
3999
|
-
steps.push({ stage: "cli", ref: params.cliTemplate, detail: "--template" });
|
|
4000
|
-
return decide("cli", params.cliTemplate);
|
|
4001
|
-
}
|
|
4002
|
-
steps.push({ stage: "cli", ref: null, detail: "--template not given" });
|
|
4003
|
-
const fileTemplate = refFromFrontmatter(params.frontmatter, FILE_TEMPLATE_KEY);
|
|
4004
|
-
if (fileTemplate) {
|
|
4005
|
-
steps.push({
|
|
4006
|
-
stage: "frontmatter-template",
|
|
4007
|
-
ref: fileTemplate,
|
|
4008
|
-
detail: `${FILE_TEMPLATE_KEY} in frontmatter`
|
|
4009
|
-
});
|
|
4010
|
-
return decide("frontmatter-template", fileTemplate);
|
|
4011
|
-
}
|
|
4012
|
-
steps.push({
|
|
4013
|
-
stage: "frontmatter-template",
|
|
4014
|
-
ref: null,
|
|
4015
|
-
detail: `no ${FILE_TEMPLATE_KEY} in frontmatter`
|
|
4016
|
-
});
|
|
4017
|
-
for (const override of params.overrides ?? []) {
|
|
4018
|
-
if (matchesGlob(override.files, params.filePath) || params.absolutePath !== void 0 && matchesGlob(override.files, params.absolutePath)) {
|
|
4019
|
-
steps.push({
|
|
4020
|
-
stage: "config-override",
|
|
4021
|
-
ref: override.template,
|
|
4022
|
-
detail: `matched override "${override.files}"`
|
|
4023
|
-
});
|
|
4024
|
-
return decide("config-override", override.template);
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
|
-
steps.push({
|
|
4028
|
-
stage: "config-override",
|
|
4029
|
-
ref: null,
|
|
4030
|
-
detail: (params.overrides ?? []).length ? "no override glob matched" : "no overrides configured"
|
|
4031
|
-
});
|
|
4032
|
-
const type = refFromFrontmatter(params.frontmatter, TYPE_KEY);
|
|
4033
|
-
if (type) {
|
|
4034
|
-
const entry = typeIndex.get(type);
|
|
4035
|
-
if (entry) {
|
|
4036
|
-
steps.push({
|
|
4037
|
-
stage: "type",
|
|
4038
|
-
ref: entry.ref,
|
|
4039
|
-
detail: `type: ${type} -> ${entry.source} template`
|
|
4040
|
-
});
|
|
4041
|
-
return decide("type", entry.ref);
|
|
4042
|
-
}
|
|
4043
|
-
steps.push({
|
|
4044
|
-
stage: "type",
|
|
4045
|
-
ref: null,
|
|
4046
|
-
detail: `type: ${type} matches no template`
|
|
4047
|
-
});
|
|
4048
|
-
return {
|
|
4049
|
-
ref: null,
|
|
4050
|
-
stage: null,
|
|
4051
|
-
cause: "unknown-type",
|
|
4052
|
-
unknownType: type,
|
|
4053
|
-
suggestions: suggestTypes(type, typeIndex.keys()),
|
|
4054
|
-
knownTypes: knownTypes(typeIndex),
|
|
4055
|
-
steps
|
|
4056
|
-
};
|
|
4057
|
-
}
|
|
4058
|
-
steps.push({ stage: "type", ref: null, detail: "no type in frontmatter" });
|
|
4059
|
-
if (params.defaultTemplate) {
|
|
4060
|
-
steps.push({
|
|
4061
|
-
stage: "config-default",
|
|
4062
|
-
ref: params.defaultTemplate,
|
|
4063
|
-
detail: "config default"
|
|
4064
|
-
});
|
|
4065
|
-
return decide("config-default", params.defaultTemplate);
|
|
4066
|
-
}
|
|
4067
|
-
steps.push({ stage: "config-default", ref: null, detail: "no default configured" });
|
|
4068
|
-
return { ref: null, stage: null, cause: "no-type", steps };
|
|
4069
|
-
}
|
|
4070
|
-
function knownTypes(index) {
|
|
4071
|
-
return [...index.keys()].sort();
|
|
4072
|
-
}
|
|
4073
|
-
|
|
4074
|
-
// src/lint/core/validator.ts
|
|
4075
|
-
function origin2() {
|
|
4076
|
-
return {
|
|
4077
|
-
start: { line: 1, column: 1, offset: 0 },
|
|
4078
|
-
end: { line: 1, column: 1, offset: 0 }
|
|
4079
|
-
};
|
|
4080
|
-
}
|
|
4081
|
-
function span(sections) {
|
|
4082
|
-
const first = sections[0];
|
|
4083
|
-
const last = sections[sections.length - 1];
|
|
4084
|
-
if (!first || !last) return origin2();
|
|
4085
|
-
return { start: { ...first.position.start }, end: { ...last.position.end } };
|
|
4086
|
-
}
|
|
4087
|
-
function pageSection(tree) {
|
|
4088
|
-
const [only] = tree.sections;
|
|
4089
|
-
if (tree.sections.length === 1 && only && only.level <= 1) return only;
|
|
4090
|
-
return {
|
|
4091
|
-
slug: "",
|
|
4092
|
-
title: "",
|
|
4093
|
-
level: 0,
|
|
4094
|
-
order: 0,
|
|
4095
|
-
parentSlug: null,
|
|
4096
|
-
titlePosition: null,
|
|
4097
|
-
position: span(tree.sections),
|
|
4098
|
-
children: [],
|
|
4099
|
-
sections: tree.sections
|
|
4100
|
-
};
|
|
4101
|
-
}
|
|
4102
|
-
var BLOCK_NOUNS = {
|
|
4103
|
-
paragraphs: "paragraphs",
|
|
4104
|
-
codeBlocks: "code blocks",
|
|
4105
|
-
lists: "lists",
|
|
4106
|
-
tables: "tables",
|
|
4107
|
-
admonitions: "admonitions",
|
|
4108
|
-
images: "images",
|
|
4109
|
-
blockquotes: "blockquotes",
|
|
4110
|
-
definitionLists: "definition lists",
|
|
4111
|
-
elements: "elements"
|
|
4112
|
-
};
|
|
4113
|
-
var Pruner = class {
|
|
4114
|
-
emitted;
|
|
4115
|
-
/** Keyed by message, so one rule asked twice is reported once. */
|
|
4116
|
-
missing = /* @__PURE__ */ new Map();
|
|
4117
|
-
constructor(kinds) {
|
|
4118
|
-
this.emitted = new Set(kinds);
|
|
4119
|
-
}
|
|
4120
|
-
get gaps() {
|
|
4121
|
-
return [...this.missing.values()];
|
|
4122
|
-
}
|
|
4123
|
-
emits(kind) {
|
|
4124
|
-
return this.emitted.has(kind);
|
|
4125
|
-
}
|
|
4126
|
-
drop(rule, noun) {
|
|
4127
|
-
this.missing.set(`${rule}: ${noun}`, { rule, noun });
|
|
4128
|
-
}
|
|
4129
|
-
/** A table rule without its `columns`, when the parser reports no cells. */
|
|
4130
|
-
tables(rule) {
|
|
4131
|
-
if (rule.columns === void 0 || this.emits("tableCell")) return rule;
|
|
4132
|
-
this.drop("columns", "table columns");
|
|
4133
|
-
const { columns: _columns, ...kept } = rule;
|
|
4134
|
-
return kept;
|
|
4135
|
-
}
|
|
4136
|
-
/**
|
|
4137
|
-
* A list rule with its `items` pruned.
|
|
4138
|
-
*
|
|
4139
|
-
* `items` has no capability of its own to check. A parser that reports a
|
|
4140
|
-
* list reports its items, since `ListNode.items` is not optional, and
|
|
4141
|
-
* `listItem` is not one of the `BLOCK_KINDS` a block rule can target. So
|
|
4142
|
-
* the `lists` gap covers the whole rule, and only what the items *hold* can
|
|
4143
|
-
* go unchecked.
|
|
4144
|
-
*
|
|
4145
|
-
* `listItem` is a `ContentKind` and parsers do declare it. That is a fact
|
|
4146
|
-
* about the node model rather than about this pruner: a template names
|
|
4147
|
-
* `lists`, and reaches an item only through `items`, so there is no rule
|
|
4148
|
-
* whose capability would be `listItem` on its own.
|
|
4149
|
-
*/
|
|
4150
|
-
lists(rule) {
|
|
4151
|
-
const items = rule.items;
|
|
4152
|
-
if (items === void 0) return rule;
|
|
4153
|
-
const pruned = this.listItems(items);
|
|
4154
|
-
return pruned === items ? rule : { ...rule, items: pruned };
|
|
4155
|
-
}
|
|
4156
|
-
listItems(rule) {
|
|
4157
|
-
return this.holder(rule);
|
|
4158
|
-
}
|
|
4159
|
-
elements(rule) {
|
|
4160
|
-
return this.holder(rule);
|
|
4161
|
-
}
|
|
4162
|
-
/** The `sequence`/`contains` pair every content holder carries. */
|
|
4163
|
-
holder(rule) {
|
|
4164
|
-
const kept = { ...rule };
|
|
4165
|
-
let changed = false;
|
|
4166
|
-
if (rule.sequence) {
|
|
4167
|
-
const sequence = this.sequence(rule.sequence);
|
|
4168
|
-
if (sequence === void 0) {
|
|
4169
|
-
delete kept.sequence;
|
|
4170
|
-
changed = true;
|
|
4171
|
-
} else if (sequence !== rule.sequence) {
|
|
4172
|
-
kept.sequence = sequence;
|
|
4173
|
-
changed = true;
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
if (rule.contains) {
|
|
4177
|
-
const contains = this.block(rule.contains);
|
|
4178
|
-
if (contains === void 0) {
|
|
4179
|
-
delete kept.contains;
|
|
4180
|
-
changed = true;
|
|
4181
|
-
} else if (contains !== rule.contains) {
|
|
4182
|
-
kept.contains = contains;
|
|
4183
|
-
changed = true;
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
return changed ? kept : rule;
|
|
4187
|
-
}
|
|
4188
|
-
/**
|
|
4189
|
-
* One `sequence`, with the runs this format cannot see taken out.
|
|
4190
|
-
*
|
|
4191
|
-
* Taking the entry out rather than leaving it empty is what keeps the rest of
|
|
4192
|
-
* the sequence meaningful: a run of a kind the parser never emits can never
|
|
4193
|
-
* appear among the actual runs, so an entry left standing would report an
|
|
4194
|
-
* order error on every file of that format. `undefined` means the key goes.
|
|
4195
|
-
*/
|
|
4196
|
-
sequence(entries) {
|
|
4197
|
-
const kept = [];
|
|
4198
|
-
let changed = false;
|
|
4199
|
-
for (const entry of entries) {
|
|
4200
|
-
const pruned = this.block(entry);
|
|
4201
|
-
if (pruned === void 0) {
|
|
4202
|
-
changed = true;
|
|
4203
|
-
continue;
|
|
4204
|
-
}
|
|
4205
|
-
if (pruned !== entry) changed = true;
|
|
4206
|
-
kept.push(pruned);
|
|
4207
|
-
}
|
|
4208
|
-
if (!changed) return entries;
|
|
4209
|
-
return kept.length > 0 ? kept : void 0;
|
|
4210
|
-
}
|
|
4211
|
-
/** One block rule, or `undefined` when nothing in it can be checked. */
|
|
4212
|
-
block(block) {
|
|
4213
|
-
const kept = { ...block };
|
|
4214
|
-
let changed = false;
|
|
4215
|
-
for (const key of BLOCK_KINDS) {
|
|
4216
|
-
if (kept[key] === void 0) continue;
|
|
4217
|
-
if (this.emits(BLOCK_KIND_NODE[key])) continue;
|
|
4218
|
-
this.drop(key, BLOCK_NOUNS[key]);
|
|
4219
|
-
Object.assign(kept, { [key]: void 0 });
|
|
4220
|
-
changed = true;
|
|
4221
|
-
}
|
|
4222
|
-
const tables = kept.tables;
|
|
4223
|
-
if (tables) {
|
|
4224
|
-
const pruned = this.tables(tables);
|
|
4225
|
-
if (pruned !== tables) {
|
|
4226
|
-
kept.tables = pruned;
|
|
4227
|
-
changed = true;
|
|
4228
|
-
}
|
|
4229
|
-
}
|
|
4230
|
-
const lists = kept.lists;
|
|
4231
|
-
if (lists) {
|
|
4232
|
-
const pruned = this.lists(lists);
|
|
4233
|
-
if (pruned !== lists) {
|
|
4234
|
-
kept.lists = pruned;
|
|
4235
|
-
changed = true;
|
|
4236
|
-
}
|
|
4237
|
-
}
|
|
4238
|
-
const elements = kept.elements;
|
|
4239
|
-
if (elements) {
|
|
4240
|
-
const pruned = this.elements(elements);
|
|
4241
|
-
if (pruned !== elements) {
|
|
4242
|
-
kept.elements = pruned;
|
|
4243
|
-
changed = true;
|
|
4244
|
-
}
|
|
4245
|
-
}
|
|
4246
|
-
if (!changed) return block;
|
|
4247
|
-
return BLOCK_KINDS.some((key) => kept[key] !== void 0) ? kept : void 0;
|
|
4248
|
-
}
|
|
4249
|
-
rules(rules) {
|
|
4250
|
-
const kept = [];
|
|
4251
|
-
let changed = false;
|
|
4252
|
-
for (const rule of rules) {
|
|
4253
|
-
const pruned = this.rule(rule);
|
|
4254
|
-
if (pruned !== rule) changed = true;
|
|
4255
|
-
kept.push(pruned);
|
|
4256
|
-
}
|
|
4257
|
-
return changed ? kept : rules;
|
|
4258
|
-
}
|
|
4259
|
-
rule(rule) {
|
|
4260
|
-
const kept = this.holder(rule);
|
|
4261
|
-
let changed = kept !== rule;
|
|
4262
|
-
const out = changed ? kept : { ...rule };
|
|
4263
|
-
if (rule.repeat) {
|
|
4264
|
-
const repeat = this.rules(rule.repeat);
|
|
4265
|
-
if (repeat !== rule.repeat) {
|
|
4266
|
-
out.repeat = repeat;
|
|
4267
|
-
changed = true;
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4270
|
-
if (rule.sections) {
|
|
4271
|
-
const sections = this.rules(rule.sections);
|
|
4272
|
-
if (sections !== rule.sections) {
|
|
4273
|
-
out.sections = sections;
|
|
4274
|
-
changed = true;
|
|
4275
|
-
}
|
|
4276
|
-
}
|
|
4277
|
-
return changed ? out : rule;
|
|
4278
|
-
}
|
|
4279
|
-
};
|
|
4280
|
-
var pruneCache = /* @__PURE__ */ new WeakMap();
|
|
4281
|
-
function prune(template, kinds) {
|
|
4282
|
-
const key = [...kinds].sort().join(",");
|
|
4283
|
-
let byKinds = pruneCache.get(template);
|
|
4284
|
-
if (!byKinds) {
|
|
4285
|
-
byKinds = /* @__PURE__ */ new Map();
|
|
4286
|
-
pruneCache.set(template, byKinds);
|
|
4287
|
-
}
|
|
4288
|
-
const hit = byKinds.get(key);
|
|
4289
|
-
if (hit) return hit;
|
|
4290
|
-
const pruner = new Pruner(kinds);
|
|
4291
|
-
const pruned = { template: pruner.rule(template), gaps: pruner.gaps };
|
|
4292
|
-
byKinds.set(key, pruned);
|
|
4293
|
-
return pruned;
|
|
4294
|
-
}
|
|
4295
|
-
function uncheckedFinding(format, name, gap) {
|
|
4296
|
-
return {
|
|
4297
|
-
type: "unsupported_content_kind",
|
|
4298
|
-
heading: null,
|
|
4299
|
-
message: `The ${format} parser does not report ${gap.noun}, so the "${gap.rule}" rule in template "${name}" is not checked for this file.`,
|
|
4300
|
-
position: origin2(),
|
|
4301
|
-
// A warning, because nothing about the document is wrong: the rule asks
|
|
4302
|
-
// about something this format cannot express. It never changes the exit
|
|
4303
|
-
// code - `runLint` counts a file as failed only on an error-severity
|
|
4304
|
-
// finding - so a docset of mixed formats stays green while still saying
|
|
4305
|
-
// which rules went unchecked, and where.
|
|
4306
|
-
severity: "warning"
|
|
4307
|
-
};
|
|
4308
|
-
}
|
|
4309
|
-
function checkSection(section, rule) {
|
|
4310
|
-
return [
|
|
4311
|
-
...checkHeading(section, rule.heading),
|
|
4312
|
-
...checkSequence(section, rule.sequence),
|
|
4313
|
-
...checkContains(section, rule.contains)
|
|
4314
|
-
];
|
|
4315
|
-
}
|
|
4316
|
-
function validateSections(sections, rules, options = {}) {
|
|
4317
|
-
const { matches, findings } = matchSections(sections, rules, options);
|
|
4318
|
-
const all = [...findings];
|
|
4319
|
-
for (const match of matches) {
|
|
4320
|
-
all.push(...checkSection(match.section, match.rule));
|
|
4321
|
-
all.push(
|
|
4322
|
-
...validateSections(match.section.sections, match.rule.sections, {
|
|
4323
|
-
...options,
|
|
4324
|
-
parent: match.section
|
|
4325
|
-
})
|
|
4326
|
-
);
|
|
4327
|
-
}
|
|
4328
|
-
return all;
|
|
4329
|
-
}
|
|
4330
|
-
function validateDocument(tree, template, options = {}) {
|
|
4331
|
-
const kinds = options.kinds ?? parserByName(tree.format)?.kinds ?? [];
|
|
4332
|
-
const pruned = prune(template, kinds);
|
|
4333
|
-
const rules = pruned.template;
|
|
4334
|
-
const name = options.template ?? template.title ?? "unnamed";
|
|
4335
|
-
const findings = pruned.gaps.map(
|
|
4336
|
-
(gap) => uncheckedFinding(tree.format, name, gap)
|
|
4337
|
-
);
|
|
4338
|
-
const page = pageSection(tree);
|
|
4339
|
-
const ctx = {
|
|
4340
|
-
heading: page.title || null,
|
|
4341
|
-
position: page.position
|
|
4342
|
-
};
|
|
4343
|
-
findings.push(...checkHeading(page, rules.heading));
|
|
4344
|
-
findings.push(...checkSequenceIn(page.children, rules.sequence, ctx));
|
|
4345
|
-
findings.push(...checkContainsIn(page.children, rules.contains, ctx));
|
|
4346
|
-
findings.push(
|
|
4347
|
-
...validateSections(page.sections, rules.sections, {
|
|
4348
|
-
parent: page,
|
|
4349
|
-
source: options.source,
|
|
4350
|
-
template: options.template
|
|
4351
|
-
})
|
|
4352
|
-
);
|
|
4353
|
-
return findings.sort(
|
|
4354
|
-
(a, b) => a.position.start.offset - b.position.start.offset
|
|
4355
|
-
);
|
|
4356
|
-
}
|
|
4357
|
-
|
|
4358
|
-
// src/lint/core/config.ts
|
|
4359
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, resolve } from "node:path";
|
|
4360
|
-
import { parse as parseYaml2 } from "yaml";
|
|
4361
|
-
import * as AjvNs2 from "ajv";
|
|
4362
|
-
|
|
4363
|
-
// schemas/lint/config.json
|
|
4364
|
-
var config_default = {
|
|
4365
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
4366
|
-
$id: "manni-lint:config:1",
|
|
4367
|
-
title: "manni lint config section",
|
|
4368
|
-
description: "The `lint:` section of a shared `manni.config.yaml`. The file's root is a mapping of tool name to that tool's settings, so this schema describes the section only - never the file. The document set is not here: proposal 0041 declares it once for every tool, under the family-level `collections:` key, and `paths`/`exclude` under `lint:` are refused by name. Every key is optional: a repo that configures no manni lint at all is a legitimate config, and so is one that carries only a sibling tool's section. `additionalProperties: false` applies at every level below `lint:`, which is what turns a typo'd key into a loud failure instead of a silent default.",
|
|
4369
|
-
type: "object",
|
|
4370
|
-
additionalProperties: false,
|
|
4371
|
-
properties: {
|
|
4372
|
-
allowEmpty: {
|
|
4373
|
-
description: "Treat a run that matched zero files as a success rather than an operational error. The same key, with the same meaning, as `cite.allowEmpty`: for a shared CI template or a pre-commit hook whose file list may legitimately be empty. `--allow-empty` on the command line sets it for one run.",
|
|
4374
|
-
type: "boolean"
|
|
4375
|
-
},
|
|
4376
|
-
overrides: {
|
|
4377
|
-
description: "Repo policy: everything under a glob is linted with one template, whatever the pages say they are. First matching entry wins, and an override outranks a page's own `type`.",
|
|
4378
|
-
type: "array",
|
|
4379
|
-
items: {
|
|
4380
|
-
description: "One policy entry. Both keys are required: an entry naming no template, or no files, is a line that silently does nothing.",
|
|
4381
|
-
type: "object",
|
|
4382
|
-
additionalProperties: false,
|
|
4383
|
-
required: ["files", "template"],
|
|
4384
|
-
properties: {
|
|
4385
|
-
files: {
|
|
4386
|
-
description: "Glob matched against each file's path, written with forward slashes whatever the platform uses.",
|
|
4387
|
-
type: "string",
|
|
4388
|
-
minLength: 1
|
|
4389
|
-
},
|
|
4390
|
-
template: {
|
|
4391
|
-
description: "Template ref applied to the files this entry matches.",
|
|
4392
|
-
type: "string",
|
|
4393
|
-
minLength: 1
|
|
4394
|
-
}
|
|
4395
|
-
}
|
|
4396
|
-
}
|
|
4397
|
-
},
|
|
4398
|
-
structure: {
|
|
4399
|
-
description: "The structure job, which `manni lint structure` and `manni lint check` both run. Declaring it with no keys is how a repo says the job applies. Its one setting is the tool that performs it.",
|
|
4400
|
-
type: "object",
|
|
4401
|
-
additionalProperties: false,
|
|
4402
|
-
properties: {
|
|
4403
|
-
tool: {
|
|
4404
|
-
description: "Which tool performs the job. `manni` is manni's own template-structure engine; `dita-ot` is DITA Open Toolkit, which validates a DITA map and the topics it reaches and is located through the top-level `tools.dita-ot.home`. `--tool <name>` on the command line names the same thing.",
|
|
4405
|
-
type: "string",
|
|
4406
|
-
enum: ["manni", "dita-ot"]
|
|
4407
|
-
}
|
|
4408
|
-
}
|
|
4409
|
-
},
|
|
4410
|
-
template: {
|
|
4411
|
-
description: "Template ref applied to a page that declares no doctype and matches no override. The last stage of the resolution chain, not an override: `--template`, a page's `$template`, an `overrides` entry, and the page's own `type` all outrank it.",
|
|
4412
|
-
type: "string",
|
|
4413
|
-
minLength: 1
|
|
4414
|
-
},
|
|
4415
|
-
templates: {
|
|
4416
|
-
description: "Template files to load. Their templates' `types:` join the doctype routing table, outranking the built-ins, so adopting a repo's own how-to shape is one entry here and no flag.",
|
|
4417
|
-
type: "array",
|
|
4418
|
-
items: {
|
|
4419
|
-
type: "string",
|
|
4420
|
-
minLength: 1
|
|
4421
|
-
}
|
|
4422
|
-
},
|
|
4423
|
-
types: {
|
|
4424
|
-
description: "Explicit doctype -> template ref map, for a doctype no template file claims with its own `types:`. Keys are the vocabulary a page's `type` frontmatter is written in, so they are the author's names rather than this schema's - constrained only to the shape a doctype slug takes, not to a fixed set.",
|
|
4425
|
-
type: "object",
|
|
4426
|
-
additionalProperties: false,
|
|
4427
|
-
patternProperties: {
|
|
4428
|
-
"^[A-Za-z0-9._:-]+$": {
|
|
4429
|
-
description: "A template ref: a built-in id, a path, or a URL, optionally with a `#name` fragment.",
|
|
4430
|
-
type: "string",
|
|
4431
|
-
minLength: 1
|
|
4432
|
-
}
|
|
4433
|
-
}
|
|
4434
|
-
}
|
|
4435
|
-
}
|
|
4436
|
-
};
|
|
4437
|
-
|
|
4438
|
-
// src/lint/core/config.ts
|
|
4439
|
-
var Ajv2 = AjvNs2.default;
|
|
4440
|
-
var LINT_TOOLS = ["manni", "dita-ot"];
|
|
4441
|
-
var LINT_JOBS = ["structure"];
|
|
4442
|
-
var TOOLS_BY_JOB = {
|
|
4443
|
-
structure: ["manni", "dita-ot"]
|
|
4444
|
-
};
|
|
4445
|
-
function isLintTool(value) {
|
|
4446
|
-
return LINT_TOOLS.includes(value);
|
|
4447
|
-
}
|
|
4448
|
-
var SECTION_KEY = "lint";
|
|
4449
|
-
var CONFIG_FILE = {
|
|
4450
|
-
section: SECTION_KEY,
|
|
4451
|
-
legacyNames: [],
|
|
4452
|
-
toError: (message) => new LintError(message)
|
|
4453
|
-
};
|
|
4454
|
-
var SECTION_KEYS = Object.keys(config_default.properties);
|
|
4455
|
-
var STRUCTURE_KEYS = Object.keys(
|
|
4456
|
-
config_default.properties.structure.properties
|
|
4457
|
-
);
|
|
4458
|
-
var CONFIG_REF = "https://hawkeyexl.github.io/manni/meta/reference/configuration/";
|
|
4459
|
-
var MOVED_KEYS = ["paths", "exclude"];
|
|
4460
|
-
function assertNoMovedKeys(raw, source) {
|
|
4461
|
-
for (const key of MOVED_KEYS) {
|
|
4462
|
-
if (Object.hasOwn(raw, key)) {
|
|
4463
|
-
throw new LintError(
|
|
4464
|
-
`${source}: "${key}" is no longer a ${SECTION_KEY} key. Document sets are declared once for every tool, under a top-level collections: list. See ${CONFIG_REF}#collections`
|
|
4465
|
-
);
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
}
|
|
4469
|
-
function rejectUnknownKeys(obj, allowed, where, source) {
|
|
4470
|
-
for (const key of Object.keys(obj)) {
|
|
4471
|
-
if (!allowed.includes(key)) {
|
|
4472
|
-
throw new LintError(
|
|
4473
|
-
`Unknown key "${key}" under ${where}: in ${source}. Supported keys: ${allowed.join(", ")}.`
|
|
4474
|
-
);
|
|
4475
|
-
}
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
|
-
function checkJobs(section, source) {
|
|
4479
|
-
for (const job of LINT_JOBS) {
|
|
4480
|
-
const value = section[job];
|
|
4481
|
-
if (value == null || !isRecord2(value)) continue;
|
|
4482
|
-
rejectUnknownKeys(value, STRUCTURE_KEYS, `${SECTION_KEY}.${job}`, source);
|
|
4483
|
-
const tool = value.tool;
|
|
4484
|
-
if (tool === void 0) continue;
|
|
4485
|
-
if (typeof tool !== "string" || !TOOLS_BY_JOB[job].includes(tool)) {
|
|
4486
|
-
throw new LintError(
|
|
4487
|
-
`${source}: ${SECTION_KEY}.${job}.tool must be one of: ${TOOLS_BY_JOB[job].join(", ")}.`
|
|
4488
|
-
);
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
}
|
|
4492
|
-
var ajv2 = new Ajv2({ allErrors: true });
|
|
4493
|
-
var compiled2 = null;
|
|
4494
|
-
function sectionValidator() {
|
|
4495
|
-
compiled2 ??= ajv2.compile(config_default);
|
|
4496
|
-
return compiled2;
|
|
4497
|
-
}
|
|
4498
|
-
function isRecord2(value) {
|
|
4499
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4500
|
-
}
|
|
4501
|
-
function describeError2(error) {
|
|
4502
|
-
const params = error.params;
|
|
4503
|
-
const extra = typeof params.additionalProperty === "string" ? ` ("${params.additionalProperty}")` : "";
|
|
4504
|
-
return `${SECTION_KEY}${error.instancePath} ${error.message ?? "is invalid"}${extra}`;
|
|
4505
|
-
}
|
|
4506
|
-
function rejectUnNested(root, source) {
|
|
4507
|
-
const stray = SECTION_KEYS.filter((key) => key in root);
|
|
4508
|
-
if (stray.length === 0) return;
|
|
4509
|
-
throw new LintError(
|
|
4510
|
-
`${source}: found ${stray.map((key) => `"${key}:"`).join(", ")} at the top level, but no "${SECTION_KEY}:" key. This file is shared across the manni family, so every manni lint setting belongs under a top-level "${SECTION_KEY}:" key. Indent the file's contents one level and add that key.`
|
|
4511
|
-
);
|
|
4512
|
-
}
|
|
4513
|
-
function rejectMiscasedWrapper(root, source) {
|
|
4514
|
-
const miscased = Object.keys(root).find(
|
|
4515
|
-
(key) => key !== SECTION_KEY && key.toLowerCase() === SECTION_KEY
|
|
4516
|
-
);
|
|
4517
|
-
if (miscased === void 0) return;
|
|
4518
|
-
throw new LintError(
|
|
4519
|
-
`${source}: found "${miscased}:" at the top level, but the key is case-sensitive and manni lint reads "${SECTION_KEY}:" exactly. Rename "${miscased}:" to "${SECTION_KEY}:".`
|
|
4520
|
-
);
|
|
4521
|
-
}
|
|
4522
|
-
function parseConfigSection(section, source) {
|
|
4523
|
-
if (section == null) return {};
|
|
4524
|
-
if (isRecord2(section)) {
|
|
4525
|
-
assertNoMovedKeys(section, source);
|
|
4526
|
-
rejectUnknownKeys(section, SECTION_KEYS, SECTION_KEY, source);
|
|
4527
|
-
checkJobs(section, source);
|
|
4528
|
-
}
|
|
4529
|
-
const validate = sectionValidator();
|
|
4530
|
-
if (!validate(section)) {
|
|
4531
|
-
const detail = (validate.errors ?? []).map(describeError2).join("; ") || `does not match the ${SECTION_KEY} config schema`;
|
|
4532
|
-
throw new LintError(
|
|
4533
|
-
`${source}: invalid "${SECTION_KEY}" section: ${detail}.`
|
|
4534
|
-
);
|
|
4535
|
-
}
|
|
4536
|
-
return section;
|
|
4537
|
-
}
|
|
4538
|
-
function parseConfig(text, source) {
|
|
4539
|
-
let raw;
|
|
4540
|
-
try {
|
|
4541
|
-
raw = parseYaml2(text);
|
|
4542
|
-
} catch (err) {
|
|
4543
|
-
throw new LintError(
|
|
4544
|
-
`${source}: invalid YAML: ${errorMessage(err)}`
|
|
4545
|
-
);
|
|
4546
|
-
}
|
|
4547
|
-
if (raw == null) return {};
|
|
4548
|
-
if (!isRecord2(raw)) {
|
|
4549
|
-
throw new LintError(
|
|
4550
|
-
`${source}: top level must be a mapping of tool name to that tool's settings, with manni lint's under "${SECTION_KEY}:".`
|
|
4551
|
-
);
|
|
4552
|
-
}
|
|
4553
|
-
if (!(SECTION_KEY in raw)) {
|
|
4554
|
-
rejectUnNested(raw, source);
|
|
4555
|
-
rejectMiscasedWrapper(raw, source);
|
|
4556
|
-
return {};
|
|
4557
|
-
}
|
|
4558
|
-
return parseConfigSection(raw[SECTION_KEY], source);
|
|
4559
|
-
}
|
|
4560
|
-
async function loadConfig(explicitPath, cwd = process.cwd()) {
|
|
4561
|
-
const file = await readLintConfigFile(explicitPath, cwd);
|
|
4562
|
-
if (file === null) return null;
|
|
4563
|
-
return { config: sectionOf(file), path: file.path };
|
|
4564
|
-
}
|
|
4565
|
-
function readLintConfigFile(explicitPath, cwd) {
|
|
4566
|
-
return explicitPath ? readConfigFile(explicitPath, cwd, CONFIG_FILE) : findConfigFile(cwd, CONFIG_FILE);
|
|
4567
|
-
}
|
|
4568
|
-
function sectionOf(file) {
|
|
4569
|
-
if (!file.wrapped && isRecord2(file.value)) {
|
|
4570
|
-
rejectMiscasedWrapper(file.value, file.source);
|
|
4571
|
-
}
|
|
4572
|
-
return parseConfigSection(file.value, file.source);
|
|
4573
|
-
}
|
|
4574
|
-
function rebaseConfig(found) {
|
|
4575
|
-
if (!found) return {};
|
|
4576
|
-
const dir = dirname2(resolve(found.path));
|
|
4577
|
-
const config = found.config;
|
|
4578
|
-
const ref = (value) => refRelativeTo(found.path, value);
|
|
4579
|
-
const glob = (value) => isAbsolute2(value) ? value : resolve(dir, value).replace(/\\/g, "/");
|
|
4580
|
-
return {
|
|
4581
|
-
...config,
|
|
4582
|
-
...config.templates ? { templates: config.templates.map(ref) } : {},
|
|
4583
|
-
...config.template ? { template: ref(config.template) } : {},
|
|
4584
|
-
...config.types ? {
|
|
4585
|
-
types: Object.fromEntries(
|
|
4586
|
-
Object.entries(config.types).map(([type, value]) => [type, ref(value)])
|
|
4587
|
-
)
|
|
4588
|
-
} : {},
|
|
4589
|
-
...config.overrides ? {
|
|
4590
|
-
overrides: config.overrides.map((o) => ({
|
|
4591
|
-
files: glob(o.files),
|
|
4592
|
-
template: ref(o.template)
|
|
4593
|
-
}))
|
|
4594
|
-
} : {}
|
|
4595
|
-
};
|
|
4596
|
-
}
|
|
4597
|
-
var STDIN = "-";
|
|
4598
|
-
async function resolveLintRun(opts) {
|
|
4599
|
-
const cwd = resolve(opts.cwd);
|
|
4600
|
-
const wanted = opts.collection ?? [];
|
|
4601
|
-
if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN)) {
|
|
4602
|
-
throw new LintError(
|
|
4603
|
-
"--collection selects a configured collection; it cannot be combined with paths."
|
|
4604
|
-
);
|
|
4605
|
-
}
|
|
4606
|
-
const file = opts.noConfig ? null : await readLintConfigFile(opts.configPath, cwd);
|
|
4607
|
-
if (wanted.length > 0 && file === null) {
|
|
4608
|
-
throw new LintError("--collection needs a config file to select from.");
|
|
4609
|
-
}
|
|
4610
|
-
if (file !== null) opts.onConfigLoaded?.({ path: file.path, dir: file.dir });
|
|
4611
|
-
const config = rebaseConfig(
|
|
4612
|
-
file === null ? null : { config: sectionOf(file), path: file.path }
|
|
4613
|
-
);
|
|
4614
|
-
const collections = file === null ? [] : selectCollections(
|
|
4615
|
-
file.collections,
|
|
4616
|
-
wanted,
|
|
4617
|
-
file.source,
|
|
4618
|
-
(message) => new LintError(message)
|
|
4619
|
-
);
|
|
4620
|
-
const fromCollections = wanted.length > 0 || opts.inputs.length === 0;
|
|
4621
|
-
const collectionPaths = fromCollections ? collections.flatMap((c) => c.paths) : [];
|
|
4622
|
-
const inputs = fromCollections ? [...opts.inputs, ...collectionPaths] : opts.inputs;
|
|
4623
|
-
const base = collectionPaths.length > 0 && file ? file.dir : cwd;
|
|
4624
|
-
return {
|
|
4625
|
-
config,
|
|
4626
|
-
inputs,
|
|
4627
|
-
base,
|
|
4628
|
-
collections,
|
|
4629
|
-
fromCollections,
|
|
4630
|
-
tools: file?.tools ?? {},
|
|
4631
|
-
...file === null ? {} : { configDir: file.dir, configPath: file.path, configSource: file.source }
|
|
4632
|
-
};
|
|
4633
|
-
}
|
|
4634
|
-
|
|
4635
|
-
// src/lint/tools/dita-ot.ts
|
|
4636
|
-
import { execFile } from "node:child_process";
|
|
4637
|
-
import { mkdtemp, readFile as readFile2, rm, stat } from "node:fs/promises";
|
|
4638
|
-
import { tmpdir } from "node:os";
|
|
4639
|
-
import { delimiter, join as join2 } from "node:path";
|
|
4640
|
-
import { fileURLToPath } from "node:url";
|
|
4641
|
-
var MAX_BUFFER = 64 * 1024 * 1024;
|
|
4642
|
-
var NOT_ON_PATH = "dita is not on PATH. Install DITA Open Toolkit to lint structure, or set tools.dita-ot.home.";
|
|
4643
|
-
var noLauncher = (home) => `no DITA Open Toolkit at "${home}". tools.dita-ot.home must be a DITA-OT installation directory.`;
|
|
4644
|
-
function ditaOtLauncher(home, platform = process.platform) {
|
|
4645
|
-
const name = platform === "win32" ? "dita.bat" : "dita";
|
|
4646
|
-
return home === void 0 ? name : join2(home, "bin", name);
|
|
4647
|
-
}
|
|
4648
|
-
function isBatch(launcher) {
|
|
4649
|
-
const lower2 = launcher.toLowerCase();
|
|
4650
|
-
return lower2.endsWith(".bat") || lower2.endsWith(".cmd");
|
|
4651
|
-
}
|
|
4652
|
-
function quoteForCmd(value) {
|
|
4653
|
-
if (value.includes('"')) {
|
|
4654
|
-
throw new LintError(
|
|
4655
|
-
`DITA Open Toolkit cannot be given an argument containing a quote: ${value}`
|
|
4656
|
-
);
|
|
4657
|
-
}
|
|
4658
|
-
return `"${value}"`;
|
|
4659
|
-
}
|
|
4660
|
-
function ditaOtCommandLine(launcher, args) {
|
|
4661
|
-
if (!isBatch(launcher)) {
|
|
4662
|
-
return { command: launcher, argv: args, verbatim: false };
|
|
4663
|
-
}
|
|
4664
|
-
const line = [launcher, ...args].map(quoteForCmd).join(" ");
|
|
4665
|
-
return {
|
|
4666
|
-
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
4667
|
-
argv: ["/d", "/s", "/c", `"${line}"`],
|
|
4668
|
-
verbatim: true
|
|
4669
|
-
};
|
|
4670
|
-
}
|
|
4671
|
-
var spawnDitaOt = (launcher, args, opts) => new Promise((settle, reject) => {
|
|
4672
|
-
const line = ditaOtCommandLine(launcher, args);
|
|
4673
|
-
execFile(
|
|
4674
|
-
line.command,
|
|
4675
|
-
line.argv,
|
|
4676
|
-
{
|
|
4677
|
-
cwd: opts.cwd,
|
|
4678
|
-
windowsHide: true,
|
|
4679
|
-
windowsVerbatimArguments: line.verbatim,
|
|
4680
|
-
maxBuffer: MAX_BUFFER,
|
|
4681
|
-
encoding: "utf8"
|
|
4682
|
-
},
|
|
4683
|
-
(error, stdout, stderr) => {
|
|
4684
|
-
if (!error) {
|
|
4685
|
-
settle({ code: 0, stdout, stderr });
|
|
4686
|
-
return;
|
|
4687
|
-
}
|
|
4688
|
-
if (typeof error.code === "number") {
|
|
4689
|
-
settle({ code: error.code, stdout, stderr });
|
|
4690
|
-
return;
|
|
4691
|
-
}
|
|
4692
|
-
if (error.code == null && typeof error.signal === "string") {
|
|
4693
|
-
settle({ code: null, signal: error.signal, stdout, stderr });
|
|
4694
|
-
return;
|
|
4695
|
-
}
|
|
4696
|
-
const failure = error;
|
|
4697
|
-
reject(failure);
|
|
4698
|
-
}
|
|
4699
|
-
);
|
|
4700
|
-
});
|
|
4701
|
-
function isEnoent(err) {
|
|
4702
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
4703
|
-
}
|
|
4704
|
-
function isMapping(value) {
|
|
4705
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4706
|
-
}
|
|
4707
|
-
async function isFile(path) {
|
|
4708
|
-
try {
|
|
4709
|
-
return (await stat(path)).isFile();
|
|
4710
|
-
} catch {
|
|
4711
|
-
return false;
|
|
4712
|
-
}
|
|
4713
|
-
}
|
|
4714
|
-
async function isOnPath(name) {
|
|
4715
|
-
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
4716
|
-
if (dir === "") continue;
|
|
4717
|
-
if (await isFile(join2(dir, name))) return true;
|
|
4718
|
-
}
|
|
4719
|
-
return false;
|
|
4720
|
-
}
|
|
4721
|
-
function foldDitaOtSeverity(level) {
|
|
4722
|
-
switch (level.toUpperCase()) {
|
|
4723
|
-
case "FATAL":
|
|
4724
|
-
case "ERROR":
|
|
4725
|
-
return "error";
|
|
4726
|
-
case "WARN":
|
|
4727
|
-
return "warning";
|
|
4728
|
-
case "INFO":
|
|
4729
|
-
return "notice";
|
|
4730
|
-
default:
|
|
4731
|
-
return null;
|
|
4732
|
-
}
|
|
4733
|
-
}
|
|
4734
|
-
function pathFromFileUri(location) {
|
|
4735
|
-
let url;
|
|
4736
|
-
try {
|
|
4737
|
-
url = new URL(location);
|
|
4738
|
-
} catch {
|
|
4739
|
-
return location;
|
|
4740
|
-
}
|
|
4741
|
-
if (url.protocol !== "file:") return location;
|
|
4742
|
-
try {
|
|
4743
|
-
return fileURLToPath(url);
|
|
4744
|
-
} catch {
|
|
4745
|
-
return decodeURIComponent(url.pathname);
|
|
4746
|
-
}
|
|
4747
|
-
}
|
|
4748
|
-
function readMessage(raw) {
|
|
4749
|
-
if (!isMapping(raw)) return null;
|
|
4750
|
-
const code = raw["code"];
|
|
4751
|
-
if (typeof code !== "string" || code === "") return null;
|
|
4752
|
-
const level = raw["level"];
|
|
4753
|
-
const severity = typeof level === "string" ? foldDitaOtSeverity(level) : null;
|
|
4754
|
-
if (severity === null) return null;
|
|
4755
|
-
const msg = raw["msg"];
|
|
4756
|
-
const location = raw["location"];
|
|
4757
|
-
const line = raw["line"];
|
|
4758
|
-
const row = raw["row"];
|
|
4759
|
-
return {
|
|
4760
|
-
code,
|
|
4761
|
-
severity,
|
|
4762
|
-
// A coded message with no text is still a finding; the code says what it is.
|
|
4763
|
-
message: typeof msg === "string" && msg !== "" ? msg : code,
|
|
4764
|
-
...typeof location === "string" && location !== "" ? { file: pathFromFileUri(location) } : {},
|
|
4765
|
-
...typeof line === "number" ? { line } : {},
|
|
4766
|
-
...typeof row === "number" ? { column: row } : {}
|
|
4767
|
-
};
|
|
4768
|
-
}
|
|
4769
|
-
function parseDitaOtLog(text) {
|
|
4770
|
-
let parsed;
|
|
4771
|
-
try {
|
|
4772
|
-
parsed = JSON.parse(text);
|
|
4773
|
-
} catch {
|
|
4774
|
-
return null;
|
|
4775
|
-
}
|
|
4776
|
-
if (!Array.isArray(parsed)) return null;
|
|
4777
|
-
const messages = [];
|
|
4778
|
-
for (const raw of parsed) {
|
|
4779
|
-
const message = readMessage(raw);
|
|
4780
|
-
if (message !== null) messages.push(message);
|
|
4781
|
-
}
|
|
4782
|
-
return messages;
|
|
4783
|
-
}
|
|
4784
|
-
function parseDitaOtVersion(output) {
|
|
4785
|
-
const match = /\b(\d+(?:\.\d+)+(?:[-.][0-9A-Za-z]+)*)/.exec(output);
|
|
4786
|
-
return match?.[1] ?? null;
|
|
4787
|
-
}
|
|
4788
|
-
function failureText(result) {
|
|
4789
|
-
const stderr = result.stderr.trim();
|
|
4790
|
-
if (stderr !== "") return stderr;
|
|
4791
|
-
if (result.signal != null) return `killed by ${result.signal}`;
|
|
4792
|
-
return `exit code ${String(result.code)}`;
|
|
4793
|
-
}
|
|
4794
|
-
async function readLog(path) {
|
|
4795
|
-
let text;
|
|
4796
|
-
try {
|
|
4797
|
-
text = await readFile2(path, "utf8");
|
|
4798
|
-
} catch {
|
|
4799
|
-
return null;
|
|
4800
|
-
}
|
|
4801
|
-
return parseDitaOtLog(text);
|
|
4802
|
-
}
|
|
4803
|
-
async function runDitaOtValidate(opts) {
|
|
4804
|
-
const spawn = opts.spawn ?? spawnDitaOt;
|
|
4805
|
-
const launcher = ditaOtLauncher(opts.home);
|
|
4806
|
-
if (opts.home !== void 0 && !await isFile(launcher)) {
|
|
4807
|
-
throw new LintError(noLauncher(opts.home));
|
|
4808
|
-
}
|
|
4809
|
-
const scratch = await mkdtemp(join2(tmpdir(), "manni-dita-ot-"));
|
|
4810
|
-
try {
|
|
4811
|
-
const results = [];
|
|
4812
|
-
for (const [index, target] of opts.targets.entries()) {
|
|
4813
|
-
const logfile = join2(scratch, `${String(index)}.json`);
|
|
4814
|
-
const args = [
|
|
4815
|
-
`--input=${target}`,
|
|
4816
|
-
"--format=dita",
|
|
4817
|
-
`--output=${join2(scratch, `out-${String(index)}`)}`,
|
|
4818
|
-
`--temp=${join2(scratch, `temp-${String(index)}`)}`,
|
|
4819
|
-
"--logger=json",
|
|
4820
|
-
`--logfile=${logfile}`,
|
|
4821
|
-
"--verbose"
|
|
4822
|
-
];
|
|
4823
|
-
let result;
|
|
4824
|
-
try {
|
|
4825
|
-
result = await spawn(launcher, args, { cwd: opts.cwd });
|
|
4826
|
-
} catch (err) {
|
|
4827
|
-
if (isEnoent(err)) {
|
|
4828
|
-
throw new LintError(
|
|
4829
|
-
opts.home === void 0 ? NOT_ON_PATH : noLauncher(opts.home)
|
|
4830
|
-
);
|
|
4831
|
-
}
|
|
4832
|
-
throw err;
|
|
4833
|
-
}
|
|
4834
|
-
const messages = await readLog(logfile);
|
|
4835
|
-
const explained = messages !== null && messages.some((m) => m.severity === "error");
|
|
4836
|
-
if (result.code !== 0 && !explained) {
|
|
4837
|
-
if (opts.home === void 0 && !await isOnPath(launcher)) {
|
|
4838
|
-
throw new LintError(NOT_ON_PATH);
|
|
4839
|
-
}
|
|
4840
|
-
throw new LintError(
|
|
4841
|
-
`DITA Open Toolkit failed on ${target}: ${failureText(result)}. Run it directly on that file to see why.`
|
|
4842
|
-
);
|
|
4843
|
-
}
|
|
4844
|
-
results.push({ target, messages: messages ?? [] });
|
|
4845
|
-
}
|
|
4846
|
-
return results;
|
|
4847
|
-
} finally {
|
|
4848
|
-
await rm(scratch, { recursive: true, force: true });
|
|
4849
|
-
}
|
|
4850
|
-
}
|
|
4851
|
-
async function probeDitaOt(ctx, spawn = spawnDitaOt) {
|
|
4852
|
-
const home = ctx.tools === void 0 ? void 0 : ditaOtHome(ctx.tools, ctx.configDir ?? ctx.cwd);
|
|
4853
|
-
const launcher = ditaOtLauncher(home);
|
|
4854
|
-
if (home !== void 0 && !await isFile(launcher)) {
|
|
4855
|
-
return { available: false, version: null };
|
|
4856
|
-
}
|
|
4857
|
-
let result;
|
|
4858
|
-
try {
|
|
4859
|
-
result = await spawn(launcher, ["--version"], { cwd: ctx.cwd });
|
|
4860
|
-
} catch {
|
|
4861
|
-
return { available: false, version: null };
|
|
4862
|
-
}
|
|
4863
|
-
if (result.code !== 0) return { available: false, version: null };
|
|
4864
|
-
return {
|
|
4865
|
-
available: true,
|
|
4866
|
-
// Both channels, because which one carries the banner is not fixed: the
|
|
4867
|
-
// shell launcher prints it on stdout, and the Windows `.bat` run through
|
|
4868
|
-
// `cmd.exe` has been seen to put it on stderr. Reading one would report a
|
|
4869
|
-
// toolkit that is plainly there as versionless on the other platform.
|
|
4870
|
-
version: parseDitaOtVersion(`${result.stdout}
|
|
4871
|
-
${result.stderr}`)
|
|
4872
|
-
};
|
|
4873
|
-
}
|
|
4874
|
-
var DITA_FORMAT = {
|
|
4875
|
-
name: "dita",
|
|
4876
|
-
label: "DITA",
|
|
4877
|
-
extensions: [".ditamap", ".dita", ".xml"],
|
|
4878
|
-
kinds: []
|
|
4879
|
-
};
|
|
4880
|
-
var ditaOt = {
|
|
4881
|
-
name: "dita-ot",
|
|
4882
|
-
label: "DITA Open Toolkit",
|
|
4883
|
-
formats: () => [{ ...DITA_FORMAT }],
|
|
4884
|
-
// Maps only. DITA-OT takes one `--input` per invocation, so walking
|
|
4885
|
-
// every topic in a docset would pay a JVM start per topic. Naming a `.dita`
|
|
4886
|
-
// explicitly still works: a named file bypasses the extension filter.
|
|
4887
|
-
walkExtensions: () => [".ditamap"],
|
|
4888
|
-
probe: (ctx) => probeDitaOt(ctx),
|
|
4889
|
-
// None. Every option manni owns is refused under this tool, stdin included.
|
|
4890
|
-
ownedOptions: []
|
|
4891
|
-
};
|
|
4892
|
-
|
|
4893
|
-
// src/lint/tools/index.ts
|
|
4894
|
-
var manni = {
|
|
4895
|
-
name: "manni",
|
|
4896
|
-
label: "manni's own structure engine",
|
|
4897
|
-
formats: () => listFormats(),
|
|
4898
|
-
walkExtensions: () => supportedExtensions(),
|
|
4899
|
-
// Nothing to look for: the engine is this package, so it is available
|
|
4900
|
-
// wherever the CLI is. A tool that shells out answers this by looking.
|
|
4901
|
-
probe: () => Promise.resolve({ available: true, version: package_default.version }),
|
|
4902
|
-
// `-` is stdin, and it is in this list so the derivation in `lint.ts` has
|
|
4903
|
-
// one place to learn that manni owns it. The refusal itself is not shaped
|
|
4904
|
-
// like the others, because "option" is the wrong word for a positional:
|
|
4905
|
-
// `assertOptionsOwned` special-cases it to say "cannot read stdin" instead.
|
|
4906
|
-
// A tool that copies this list without reading that guard gets the right
|
|
4907
|
-
// ownership and the wrong sentence.
|
|
4908
|
-
ownedOptions: ["--template", "--templates", "--explain", "--as", "-"]
|
|
4909
|
-
};
|
|
4910
|
-
var descriptors = /* @__PURE__ */ new Map([
|
|
4911
|
-
[manni.name, manni],
|
|
4912
|
-
[ditaOt.name, ditaOt]
|
|
4913
|
-
]);
|
|
4914
|
-
function structureTools() {
|
|
4915
|
-
return [...descriptors.values()];
|
|
4916
|
-
}
|
|
4917
|
-
function resolveStructureTool(name) {
|
|
4918
|
-
const descriptor = descriptors.get(name);
|
|
4919
|
-
if (descriptor) return descriptor;
|
|
4920
|
-
throw new LintError(
|
|
4921
|
-
`Unknown --tool "${name}" for structure. Use ${TOOLS_BY_JOB.structure.join(", ")}.`
|
|
4922
|
-
);
|
|
4923
|
-
}
|
|
4924
|
-
|
|
4925
|
-
// src/lint/commands/lint.ts
|
|
4926
|
-
var STDIN_LABEL = "<stdin>";
|
|
4927
|
-
var TEMPLATE_FILE_EXTENSIONS = [".yaml", ".yml", ".json"];
|
|
4928
|
-
async function asLintError(run) {
|
|
4929
|
-
try {
|
|
4930
|
-
return await run();
|
|
4931
|
-
} catch (err) {
|
|
4932
|
-
if (err instanceof LintError) throw err;
|
|
4933
|
-
if (err instanceof ToolError) throw new LintError(err.message);
|
|
4934
|
-
throw err;
|
|
4935
|
-
}
|
|
4936
|
-
}
|
|
4937
|
-
function templateFiles(value) {
|
|
4938
|
-
if (value == null) return [];
|
|
4939
|
-
return Array.isArray(value) ? value : [value];
|
|
4940
|
-
}
|
|
4941
|
-
var TOOL_OPTIONS = [
|
|
4942
|
-
{ spelling: "--template", given: (o) => o.template !== void 0 },
|
|
4943
|
-
{
|
|
4944
|
-
spelling: "--templates",
|
|
4945
|
-
given: (o) => templateFiles(o.templates).length > 0
|
|
4946
|
-
},
|
|
4947
|
-
{ spelling: "--explain", given: (o) => o.explain === true },
|
|
4948
|
-
{ spelling: "--as", given: (o) => o.as !== void 0 }
|
|
4949
|
-
];
|
|
4950
|
-
function foreignOptions(tool) {
|
|
4951
|
-
const foreign = /* @__PURE__ */ new Map();
|
|
4952
|
-
for (const other of structureTools()) {
|
|
4953
|
-
if (other === tool) continue;
|
|
4954
|
-
for (const spelling of other.ownedOptions) {
|
|
4955
|
-
if (tool.ownedOptions.includes(spelling)) continue;
|
|
4956
|
-
if (!foreign.has(spelling)) foreign.set(spelling, other);
|
|
4957
|
-
}
|
|
4958
|
-
}
|
|
4959
|
-
return foreign;
|
|
4960
|
-
}
|
|
4961
|
-
function assertOptionsOwned(opts, tool, usingStdin) {
|
|
4962
|
-
const foreign = foreignOptions(tool);
|
|
4963
|
-
if (foreign.size === 0) return;
|
|
4964
|
-
for (const { spelling, given } of TOOL_OPTIONS) {
|
|
4965
|
-
const owner = foreign.get(spelling);
|
|
4966
|
-
if (owner === void 0 || !given(opts)) continue;
|
|
4967
|
-
throw new LintError(
|
|
4968
|
-
`${spelling} is an option of the ${owner.name} tool; the structure job's tool is ${tool.name}.`
|
|
4969
|
-
);
|
|
4970
|
-
}
|
|
4971
|
-
if (usingStdin && foreign.has(STDIN_TOKEN)) {
|
|
4972
|
-
throw new LintError(`${tool.name} cannot read stdin. Name files or a map.`);
|
|
4973
|
-
}
|
|
4974
|
-
}
|
|
4975
|
-
function origin3() {
|
|
4976
|
-
return {
|
|
4977
|
-
start: { line: 1, column: 1, offset: 0 },
|
|
4978
|
-
end: { line: 1, column: 1, offset: 0 }
|
|
4979
|
-
};
|
|
4980
|
-
}
|
|
4981
|
-
function skip(file, reason, cause = "unsupported-format") {
|
|
4982
|
-
return {
|
|
4983
|
-
file,
|
|
4984
|
-
// `success` means "linted and produced no findings". A file that was never
|
|
4985
|
-
// linted is neither passing nor failing, and calling it a pass would hide
|
|
4986
|
-
// the gap from anyone reading CI output.
|
|
4987
|
-
success: false,
|
|
4988
|
-
findings: [],
|
|
4989
|
-
template: null,
|
|
4990
|
-
skipped: cause,
|
|
4991
|
-
reason
|
|
4992
|
-
};
|
|
4993
|
-
}
|
|
4994
|
-
function parseFinding(message) {
|
|
4995
|
-
return {
|
|
4996
|
-
type: "parse_error",
|
|
4997
|
-
heading: null,
|
|
4998
|
-
message,
|
|
4999
|
-
position: origin3(),
|
|
5000
|
-
severity: "error"
|
|
5001
|
-
};
|
|
5002
|
-
}
|
|
5003
|
-
function templateLoader() {
|
|
5004
|
-
const cache = /* @__PURE__ */ new Map();
|
|
5005
|
-
return (ref) => {
|
|
5006
|
-
let pending = cache.get(ref);
|
|
5007
|
-
if (!pending) {
|
|
5008
|
-
pending = loadResolvedTemplate(ref);
|
|
5009
|
-
cache.set(ref, pending);
|
|
5010
|
-
}
|
|
5011
|
-
return pending;
|
|
5012
|
-
};
|
|
5013
|
-
}
|
|
5014
|
-
function cliTemplateRef(template, templatePaths) {
|
|
5015
|
-
if (!template) return void 0;
|
|
5016
|
-
if (template.includes("#")) return template;
|
|
5017
|
-
if (classifyRef(template).kind !== "file") return template;
|
|
5018
|
-
if (template.includes("/") || template.includes("\\")) return template;
|
|
5019
|
-
if (TEMPLATE_FILE_EXTENSIONS.some((e) => template.toLowerCase().endsWith(e))) {
|
|
5020
|
-
return template;
|
|
5021
|
-
}
|
|
5022
|
-
if (templatePaths.length === 0) return template;
|
|
5023
|
-
return `${templatePaths[templatePaths.length - 1] ?? ""}#${template}`;
|
|
5024
|
-
}
|
|
5025
|
-
function templateIdentity(ref) {
|
|
5026
|
-
const hash = ref.lastIndexOf("#");
|
|
5027
|
-
if (hash === -1) return { source: ref, template: ref };
|
|
5028
|
-
return { source: ref.slice(0, hash), template: ref.slice(hash + 1) };
|
|
5029
|
-
}
|
|
5030
|
-
function unknownTypeFinding(resolution, typeIndex, position) {
|
|
5031
|
-
const suggestions = resolution.suggestions ?? [];
|
|
5032
|
-
const known = resolution.knownTypes ?? knownTypes(typeIndex);
|
|
5033
|
-
const hint = suggestions.length ? ` Did you mean ${suggestions.map((s) => `"${s}"`).join(", ")}?` : ` Known doctypes: ${known.join(", ") || "(none)"}.`;
|
|
5034
|
-
const declared = resolution.unknownType ?? "(none recorded)";
|
|
5035
|
-
return {
|
|
5036
|
-
type: "unknown_type",
|
|
5037
|
-
heading: null,
|
|
5038
|
-
message: `No template serves type "${declared}".${hint} Declare it on a template with "types:", then pass that file with --templates.`,
|
|
5039
|
-
position,
|
|
5040
|
-
severity: "error"
|
|
5041
|
-
};
|
|
5042
|
-
}
|
|
5043
|
-
async function lintOne(label, content, parser, ctx) {
|
|
5044
|
-
let tree;
|
|
5045
|
-
try {
|
|
5046
|
-
tree = parser.parse(content, label);
|
|
5047
|
-
} catch (err) {
|
|
5048
|
-
return {
|
|
5049
|
-
file: label,
|
|
5050
|
-
success: false,
|
|
5051
|
-
findings: [parseFinding(errorMessage(err))],
|
|
5052
|
-
template: null
|
|
5053
|
-
};
|
|
5054
|
-
}
|
|
5055
|
-
const resolution = resolveTemplateRef({
|
|
5056
|
-
filePath: label,
|
|
5057
|
-
absolutePath: ctx.absoluteOf?.(label),
|
|
5058
|
-
frontmatter: tree.frontmatter,
|
|
5059
|
-
cliTemplate: ctx.cliTemplate,
|
|
5060
|
-
overrides: ctx.overrides,
|
|
5061
|
-
typeIndex: ctx.typeIndex,
|
|
5062
|
-
defaultTemplate: ctx.defaultTemplate
|
|
5063
|
-
});
|
|
5064
|
-
const withResolution = (result) => ctx.explain ? { ...result, resolution } : result;
|
|
5065
|
-
const metaPosition = tree.frontmatterPosition ?? origin3();
|
|
5066
|
-
if (resolution.ref === null) {
|
|
5067
|
-
if (resolution.cause === "unknown-type") {
|
|
5068
|
-
return withResolution({
|
|
5069
|
-
file: label,
|
|
5070
|
-
success: false,
|
|
5071
|
-
findings: [unknownTypeFinding(resolution, ctx.typeIndex, metaPosition)],
|
|
5072
|
-
template: null
|
|
5073
|
-
});
|
|
5074
|
-
}
|
|
5075
|
-
return withResolution(
|
|
5076
|
-
skip(
|
|
5077
|
-
label,
|
|
5078
|
-
"no type in frontmatter and no template resolved",
|
|
5079
|
-
"no-template"
|
|
5080
|
-
)
|
|
5081
|
-
);
|
|
5082
|
-
}
|
|
5083
|
-
const brokenTemplate = (err) => withResolution({
|
|
5084
|
-
file: label,
|
|
5085
|
-
success: false,
|
|
5086
|
-
findings: [
|
|
5087
|
-
{
|
|
5088
|
-
type: "template_error",
|
|
5089
|
-
heading: null,
|
|
5090
|
-
message: errorMessage(err),
|
|
5091
|
-
position: metaPosition,
|
|
5092
|
-
severity: "error"
|
|
5093
|
-
}
|
|
5094
|
-
],
|
|
5095
|
-
template: resolution.ref
|
|
5096
|
-
});
|
|
5097
|
-
if (resolution.stage === "frontmatter-template" && classifyRef(resolution.ref).kind === "url") {
|
|
5098
|
-
return brokenTemplate(
|
|
5099
|
-
new LintError(
|
|
5100
|
-
`${FILE_TEMPLATE_KEY} may not name a URL ("${resolution.ref}"): a document must not choose what the linter fetches. Declare it in manni.config.yaml under "templates:" or "types:", or pass it with --template.`
|
|
5101
|
-
)
|
|
5102
|
-
);
|
|
5103
|
-
}
|
|
5104
|
-
const ref = resolution.stage === "frontmatter-template" ? refRelativeTo(ctx.absoluteOf?.(label) ?? label, resolution.ref) : resolution.ref;
|
|
5105
|
-
let template;
|
|
5106
|
-
try {
|
|
5107
|
-
template = await ctx.getTemplate(ref);
|
|
5108
|
-
} catch (err) {
|
|
5109
|
-
return brokenTemplate(err);
|
|
5110
|
-
}
|
|
5111
|
-
if (ctx.explain) {
|
|
5112
|
-
return withResolution({
|
|
5113
|
-
file: label,
|
|
5114
|
-
success: true,
|
|
5115
|
-
findings: [],
|
|
5116
|
-
template: resolution.ref,
|
|
5117
|
-
// The tree and the loaded template, for the alignment block. Both are
|
|
5118
|
-
// already in hand here, and neither survives this function otherwise.
|
|
5119
|
-
alignment: { tree, template }
|
|
5120
|
-
});
|
|
5121
|
-
}
|
|
5122
|
-
let findings;
|
|
5123
|
-
try {
|
|
5124
|
-
findings = validateDocument(tree, template, {
|
|
5125
|
-
...templateIdentity(ref),
|
|
5126
|
-
kinds: parser.kinds
|
|
5127
|
-
});
|
|
5128
|
-
} catch (err) {
|
|
5129
|
-
if (err instanceof LintError) return brokenTemplate(err);
|
|
5130
|
-
throw err;
|
|
5131
|
-
}
|
|
5132
|
-
return withResolution({
|
|
5133
|
-
file: label,
|
|
5134
|
-
// "Linted and found nothing wrong", which is not the same as "produced no
|
|
5135
|
-
// findings". A `warning` - today, a rule this file's format cannot answer -
|
|
5136
|
-
// says something about the run rather than about the document, and the
|
|
5137
|
-
// exit code follows `success`, so counting it as a failure would turn a
|
|
5138
|
-
// docset of mixed formats permanently red. The family's rule, and the one
|
|
5139
|
-
// meta and cite already apply: `ok` is "no error-severity finding".
|
|
5140
|
-
success: !findings.some(isErrorSeverity),
|
|
5141
|
-
findings,
|
|
5142
|
-
template: resolution.ref
|
|
5143
|
-
});
|
|
5144
|
-
}
|
|
5145
|
-
async function lintWithManni(run) {
|
|
5146
|
-
const { opts, config, cwd, files, forcedParser, usingStdin } = run;
|
|
5147
|
-
const templatePaths = templateFiles(opts.templates ?? config.templates);
|
|
5148
|
-
const getTemplate = templateLoader();
|
|
5149
|
-
const userFiles = [];
|
|
5150
|
-
for (const ref of templatePaths) {
|
|
5151
|
-
const file = await loadTemplateFile(ref);
|
|
5152
|
-
const templates = {};
|
|
5153
|
-
for (const name of Object.keys(file.templates ?? {})) {
|
|
5154
|
-
templates[name] = await getTemplate(`${ref}#${name}`);
|
|
5155
|
-
}
|
|
5156
|
-
userFiles.push({ ref, file: { ...file, templates } });
|
|
5157
|
-
}
|
|
5158
|
-
const typeIndex = buildTypeIndex({
|
|
5159
|
-
builtins: listBuiltins(),
|
|
5160
|
-
userFiles,
|
|
5161
|
-
explicitTypes: opts.types ?? config.types
|
|
5162
|
-
});
|
|
5163
|
-
const ctx = {
|
|
5164
|
-
getTemplate,
|
|
5165
|
-
absoluteOf: (file) => resolve2(cwd, file).replace(/\\/g, "/"),
|
|
5166
|
-
typeIndex,
|
|
5167
|
-
cliTemplate: cliTemplateRef(opts.template, templatePaths),
|
|
5168
|
-
overrides: opts.overrides ?? config.overrides,
|
|
5169
|
-
// `template:` in config is the default for a page that declares no type -
|
|
5170
|
-
// the bottom of the chain, not the top. `--template` is the top.
|
|
5171
|
-
defaultTemplate: opts.defaultTemplate ?? config.template,
|
|
5172
|
-
explain: opts.explain === true
|
|
5173
|
-
};
|
|
5174
|
-
if (ctx.cliTemplate != null) await getTemplate(ctx.cliTemplate);
|
|
5175
|
-
const results = [];
|
|
5176
|
-
if (usingStdin && forcedParser) {
|
|
5177
|
-
results.push(
|
|
5178
|
-
await lintOne(STDIN_LABEL, opts.stdinContent ?? "", forcedParser, ctx)
|
|
5179
|
-
);
|
|
5180
|
-
}
|
|
5181
|
-
for (const file of files) {
|
|
5182
|
-
const ext = extname(file);
|
|
5183
|
-
const parser = forcedParser ?? parserForExtension(ext);
|
|
5184
|
-
if (!parser) {
|
|
5185
|
-
results.push(
|
|
5186
|
-
skip(
|
|
5187
|
-
file,
|
|
5188
|
-
`no parser is registered for "${ext || file}". Supported extensions: ${supportedExtensions().join(", ")}. Use --as to override.`
|
|
5189
|
-
)
|
|
5190
|
-
);
|
|
5191
|
-
continue;
|
|
5192
|
-
}
|
|
5193
|
-
let content;
|
|
5194
|
-
try {
|
|
5195
|
-
content = await readFile3(resolve2(cwd, file), "utf8");
|
|
5196
|
-
} catch (err) {
|
|
5197
|
-
results.push(
|
|
5198
|
-
skip(file, `could not be read: ${errorMessage(err)}`, "unreadable")
|
|
5199
|
-
);
|
|
5200
|
-
continue;
|
|
5201
|
-
}
|
|
5202
|
-
results.push(await lintOne(file, content, parser, ctx));
|
|
5203
|
-
}
|
|
5204
|
-
return results;
|
|
5205
|
-
}
|
|
5206
|
-
function ditaOtFinding(message) {
|
|
5207
|
-
const point = message.line === void 0 ? null : {
|
|
5208
|
-
line: message.line,
|
|
5209
|
-
// The log calls the column `row`; see `readMessage` in the seam.
|
|
5210
|
-
column: message.column ?? 1,
|
|
5211
|
-
// DITA-OT reports no byte offset, and nothing downstream needs one.
|
|
5212
|
-
offset: 0
|
|
5213
|
-
};
|
|
5214
|
-
return {
|
|
5215
|
-
type: message.code,
|
|
5216
|
-
heading: null,
|
|
5217
|
-
message: message.message,
|
|
5218
|
-
position: point === null ? origin3() : { start: point, end: point },
|
|
5219
|
-
severity: message.severity
|
|
5220
|
-
};
|
|
5221
|
-
}
|
|
5222
|
-
async function lintWithDitaOt(run) {
|
|
5223
|
-
const { cwd, files, onNotice } = run;
|
|
5224
|
-
const readable = ditaOt.formats().flatMap((format) => format.extensions);
|
|
5225
|
-
const reads = new Set(readable.map((ext) => ext.toLowerCase()));
|
|
5226
|
-
const targets = [];
|
|
5227
|
-
const unsupported = [];
|
|
5228
|
-
for (const file of files) {
|
|
5229
|
-
if (reads.has(extname(file).toLowerCase())) targets.push(file);
|
|
5230
|
-
else {
|
|
5231
|
-
unsupported.push(
|
|
5232
|
-
skip(
|
|
5233
|
-
file,
|
|
5234
|
-
`dita-ot does not read "${extname(file) || file}". It reads ${readable.join(", ")}.`
|
|
5235
|
-
)
|
|
5236
|
-
);
|
|
5237
|
-
}
|
|
5238
|
-
}
|
|
5239
|
-
const labels = /* @__PURE__ */ new Map();
|
|
5240
|
-
for (const file of targets) {
|
|
5241
|
-
const absolute = resolve2(cwd, file);
|
|
5242
|
-
if (!labels.has(absolute)) labels.set(absolute, file);
|
|
5243
|
-
}
|
|
5244
|
-
const unique = [...labels.values()];
|
|
5245
|
-
const labelOf = (absolute) => {
|
|
5246
|
-
const known = labels.get(absolute);
|
|
5247
|
-
if (known !== void 0) return known;
|
|
5248
|
-
const near = relative(cwd, absolute).replace(/\\/g, "/");
|
|
5249
|
-
return near === "" ? absolute : near;
|
|
5250
|
-
};
|
|
5251
|
-
for (const file of unique) {
|
|
5252
|
-
if (extname(file).toLowerCase() !== ".ditamap") {
|
|
5253
|
-
onNotice?.(
|
|
5254
|
-
`checked ${file} without a map; references outside it are not resolved.`
|
|
5255
|
-
);
|
|
5256
|
-
}
|
|
5257
|
-
}
|
|
5258
|
-
if (unique.length === 0) return unsupported;
|
|
5259
|
-
const results = await run.validate({
|
|
5260
|
-
targets: [...labels.keys()],
|
|
5261
|
-
cwd,
|
|
5262
|
-
...run.home === void 0 ? {} : { home: run.home }
|
|
5263
|
-
});
|
|
5264
|
-
const byFile = new Map(unique.map((file) => [file, []]));
|
|
5265
|
-
const findingsFor = (file) => {
|
|
5266
|
-
let list = byFile.get(file);
|
|
5267
|
-
if (list === void 0) {
|
|
5268
|
-
list = [];
|
|
5269
|
-
byFile.set(file, list);
|
|
5270
|
-
}
|
|
5271
|
-
return list;
|
|
5272
|
-
};
|
|
5273
|
-
for (const { target, messages } of results) {
|
|
5274
|
-
const invoked = labelOf(target);
|
|
5275
|
-
for (const message of messages) {
|
|
5276
|
-
const file = message.file === void 0 ? invoked : labelOf(resolve2(cwd, message.file));
|
|
5277
|
-
findingsFor(file).push(ditaOtFinding(message));
|
|
5278
|
-
}
|
|
5279
|
-
}
|
|
5280
|
-
return [
|
|
5281
|
-
...unsupported,
|
|
5282
|
-
...[...byFile].map(([file, findings]) => ({
|
|
5283
|
-
file,
|
|
5284
|
-
// The family's rule, as manni's branch applies it: `ok` is "no
|
|
5285
|
-
// error-severity finding", so a warning-only file stays green.
|
|
5286
|
-
success: !findings.some(isErrorSeverity),
|
|
5287
|
-
findings,
|
|
5288
|
-
template: null
|
|
5289
|
-
}))
|
|
5290
|
-
];
|
|
5291
|
-
}
|
|
5292
|
-
function assertSomethingWasChecked(results, explain, tool) {
|
|
5293
|
-
const skipped = results.filter((r) => r.skipped != null).length;
|
|
5294
|
-
const checked = results.length - skipped;
|
|
5295
|
-
if (explain || checked > 0 || skipped === 0) return;
|
|
5296
|
-
const unrouted = results.filter((r) => r.skipped === "no-template").length;
|
|
5297
|
-
const unsupported = results.filter(
|
|
5298
|
-
(r) => r.skipped === "unsupported-format"
|
|
5299
|
-
).length;
|
|
5300
|
-
const unreadable = results.filter((r) => r.skipped === "unreadable").length;
|
|
5301
|
-
const advice = [
|
|
5302
|
-
unrouted > 0 ? `${unrouted} had no template: give a page a "type:" that a template serves, pass -t/--template <ref>, or set "lint.template" as a default.` : null,
|
|
5303
|
-
unsupported > 0 ? (
|
|
5304
|
-
// `--as` belongs to manni's engine, so offering it to someone running
|
|
5305
|
-
// another tool is advice that answers with a usage error. The formats
|
|
5306
|
-
// line is true for every tool, because every tool declares one.
|
|
5307
|
-
`${unsupported} ${unsupported === 1 ? "is" : "are"} in a format ${tool.name} does not read: ` + (tool.ownedOptions.includes("--as") ? `pass --as <format> to force one, or target files in a format ` : `target files in a format `) + `"manni lint tools" lists.`
|
|
5308
|
-
) : null,
|
|
5309
|
-
unreadable > 0 ? `${unreadable} could not be read: check the permissions on those paths, or drop them from the run with --exclude <glob>.` : null
|
|
5310
|
-
].filter((line) => line !== null).join(" ");
|
|
5311
|
-
throw new LintError(
|
|
5312
|
-
`Nothing was checked: all ${skipped} file(s) were skipped. ${advice} Run "manni lint structure <paths> --explain" to see how each file resolved.`
|
|
5313
|
-
);
|
|
5314
|
-
}
|
|
5315
|
-
async function runLint(opts) {
|
|
5316
|
-
const run = await resolveLintRun({
|
|
5317
|
-
cwd: opts.cwd ?? process.cwd(),
|
|
5318
|
-
configPath: opts.configPath,
|
|
5319
|
-
noConfig: opts.noConfig,
|
|
5320
|
-
inputs: opts.inputs,
|
|
5321
|
-
collection: opts.collection,
|
|
5322
|
-
onConfigLoaded: opts.onConfigLoaded
|
|
5323
|
-
});
|
|
5324
|
-
const config = run.config;
|
|
5325
|
-
const cwd = run.base;
|
|
5326
|
-
if (run.inputs.length === 0) {
|
|
5327
|
-
throw new LintError(
|
|
5328
|
-
"No files to check. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
|
|
5329
|
-
);
|
|
5330
|
-
}
|
|
5331
|
-
const tool = resolveStructureTool(
|
|
5332
|
-
opts.tool ?? config.structure?.tool ?? "manni"
|
|
5333
|
-
);
|
|
5334
|
-
const usingStdin = run.inputs.includes(STDIN_TOKEN);
|
|
5335
|
-
assertOptionsOwned(opts, tool, usingStdin);
|
|
5336
|
-
const forcedParser = opts.as != null ? parserByName(opts.as) : void 0;
|
|
5337
|
-
if (opts.as != null && !forcedParser) {
|
|
5338
|
-
throw new LintError(
|
|
5339
|
-
`Unknown format "${opts.as}". Run "manni lint tools" to see the formats manni lint reads.`
|
|
5340
|
-
);
|
|
5341
|
-
}
|
|
5342
|
-
if (usingStdin && !forcedParser) {
|
|
5343
|
-
throw new LintError(
|
|
5344
|
-
"Reading from stdin (-) requires --as <format> to choose a parser."
|
|
5345
|
-
);
|
|
5346
|
-
}
|
|
5347
|
-
const fileInputs = run.inputs.filter((input) => input !== STDIN_TOKEN);
|
|
5348
|
-
const exts = opts.exts ?? forcedParser?.extensions ?? tool.walkExtensions();
|
|
5349
|
-
const allowEmpty = opts.allowEmpty ?? config.allowEmpty;
|
|
5350
|
-
const exclude = [
|
|
5351
|
-
.../* @__PURE__ */ new Set([
|
|
5352
|
-
...opts.exclude ?? [],
|
|
5353
|
-
...run.fromCollections ? run.collections.flatMap((c) => c.exclude) : []
|
|
5354
|
-
])
|
|
5355
|
-
];
|
|
5356
|
-
const { files, gitignoreSkipped } = await asLintError(
|
|
5357
|
-
() => resolveTargetSet({
|
|
5358
|
-
inputs: fileInputs,
|
|
5359
|
-
exts,
|
|
5360
|
-
exclude,
|
|
5361
|
-
cwd,
|
|
5362
|
-
allowEmpty,
|
|
5363
|
-
...gitignoreOptions({
|
|
5364
|
-
flag: opts.respectGitignore,
|
|
5365
|
-
onNotice: opts.onNotice
|
|
5366
|
-
})
|
|
5367
|
-
})
|
|
5368
|
-
);
|
|
5369
|
-
await asLintError(() => {
|
|
5370
|
-
assertNonEmpty({
|
|
5371
|
-
files,
|
|
5372
|
-
inputs: fileInputs,
|
|
5373
|
-
usingStdin,
|
|
5374
|
-
allowEmpty,
|
|
5375
|
-
exclude,
|
|
5376
|
-
exts,
|
|
5377
|
-
gitignoreSkipped,
|
|
5378
|
-
action: "linted"
|
|
5379
|
-
});
|
|
5380
|
-
});
|
|
5381
|
-
const name = tool.name;
|
|
5382
|
-
let results;
|
|
5383
|
-
if (name === "manni") {
|
|
5384
|
-
results = await lintWithManni({
|
|
5385
|
-
opts,
|
|
5386
|
-
config,
|
|
5387
|
-
cwd,
|
|
5388
|
-
files,
|
|
5389
|
-
forcedParser,
|
|
5390
|
-
usingStdin
|
|
5391
|
-
});
|
|
5392
|
-
} else if (name === "dita-ot") {
|
|
5393
|
-
const home = ditaOtHome(run.tools, run.configDir ?? cwd);
|
|
5394
|
-
results = await lintWithDitaOt({
|
|
5395
|
-
cwd,
|
|
5396
|
-
files,
|
|
5397
|
-
...home === void 0 ? {} : { home },
|
|
5398
|
-
...opts.onNotice === void 0 ? {} : { onNotice: opts.onNotice },
|
|
5399
|
-
validate: opts.runDitaOt ?? runDitaOtValidate
|
|
5400
|
-
});
|
|
5401
|
-
} else {
|
|
5402
|
-
throw new LintError(`No implementation is registered for the ${name} tool.`);
|
|
5403
|
-
}
|
|
5404
|
-
assertSomethingWasChecked(results, opts.explain === true, tool);
|
|
5405
|
-
const skipped = results.filter((r) => r.skipped != null).length;
|
|
5406
|
-
const failed = results.filter((r) => r.skipped == null && !r.success).length;
|
|
5407
|
-
const checked = results.length - skipped;
|
|
5408
|
-
return {
|
|
5409
|
-
results,
|
|
5410
|
-
summary: { checked, passed: checked - failed, failed, skipped }
|
|
5411
|
-
};
|
|
5412
|
-
}
|
|
5413
|
-
|
|
5414
3243
|
export {
|
|
5415
3244
|
parserForExtension,
|
|
5416
3245
|
parserByName,
|
|
5417
3246
|
supportedExtensions,
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
classifyRef,
|
|
5421
|
-
clearTemplateCaches,
|
|
5422
|
-
loadTemplateFile,
|
|
5423
|
-
loadTemplate,
|
|
5424
|
-
resolveExtends,
|
|
5425
|
-
clearMatcherCache,
|
|
5426
|
-
validateSections,
|
|
5427
|
-
validateDocument,
|
|
5428
|
-
LINT_TOOLS,
|
|
5429
|
-
LINT_JOBS,
|
|
5430
|
-
TOOLS_BY_JOB,
|
|
5431
|
-
isLintTool,
|
|
5432
|
-
parseConfig,
|
|
5433
|
-
loadConfig,
|
|
5434
|
-
rebaseConfig,
|
|
5435
|
-
resolveLintRun,
|
|
5436
|
-
resolveStructureTool,
|
|
5437
|
-
STDIN_LABEL,
|
|
5438
|
-
runLint
|
|
3247
|
+
unsupportedFormatMessage,
|
|
3248
|
+
listFormats
|
|
5439
3249
|
};
|
|
5440
|
-
//# sourceMappingURL=chunk-
|
|
3250
|
+
//# sourceMappingURL=chunk-ELZ2IIOF.js.map
|