@zudojs/docs 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +32 -9
- package/dist/docsTypes/index.d.ts +1 -1
- package/dist/document/documentBuilder.core.d.ts +15 -5
- package/dist/document/documentBuilder.core.js +27 -15
- package/dist/document/index.d.ts +1 -1
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.js +18 -0
- package/dist/examples/examples.core.d.ts +4 -0
- package/dist/examples/examples.core.js +16 -7
- package/dist/frontmatter/frontmatter.core.d.ts +1 -1
- package/dist/frontmatter/frontmatter.parser.d.ts +15 -2
- package/dist/frontmatter/frontmatter.parser.js +202 -39
- package/dist/frontmatter/frontmatter.serializer.d.ts +13 -1
- package/dist/frontmatter/frontmatter.serializer.js +89 -4
- package/dist/frontmatter/frontmatter.types.d.ts +10 -1
- package/dist/frontmatter/index.d.ts +1 -1
- package/dist/generator/generator.types.d.ts +18 -0
- package/dist/generator/generatorJson.core.d.ts +8 -3
- package/dist/generator/generatorJson.core.js +16 -4
- package/dist/generator/generatorMarkdown.core.d.ts +4 -0
- package/dist/generator/generatorMarkdown.core.js +32 -13
- package/dist/generator/generatorMarkdownNodes.d.ts +20 -0
- package/dist/generator/generatorMarkdownNodes.js +87 -17
- package/dist/generator/index.d.ts +2 -2
- package/dist/generator/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/navigation.core.d.ts +25 -6
- package/dist/navigation/navigation.core.js +93 -25
- package/dist/registry/index.d.ts +2 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.core.d.ts +29 -3
- package/dist/registry/registry.core.js +32 -6
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/utils.freeze.d.ts +16 -0
- package/dist/utils/utils.freeze.js +53 -0
- package/dist/utils/utils.helper.d.ts +27 -4
- package/dist/utils/utils.helper.js +108 -21
- package/dist/validator/index.d.ts +4 -1
- package/dist/validator/index.js +2 -1
- package/dist/validator/validator.types.d.ts +8 -0
- package/dist/validator/validator.types.js +10 -1
- package/dist/validator/validatorAll/validatorAll.core.d.ts +11 -2
- package/dist/validator/validatorAll/validatorAll.core.js +19 -7
- package/dist/validator/validatorAll/validatorNavigation.core.d.ts +8 -1
- package/dist/validator/validatorAll/validatorNavigation.core.js +64 -10
- package/dist/validator/validatorDocument.core.d.ts +3 -2
- package/dist/validator/validatorDocument.core.js +197 -15
- package/dist/validator/validatorDuplicates.core.d.ts +1 -1
- package/dist/validator/validatorDuplicates.core.js +4 -6
- package/dist/validator/validatorLinks.core.d.ts +22 -4
- package/dist/validator/validatorLinks.core.js +80 -44
- package/package.json +24 -15
- package/dist/docsTypes/documentationContent.d.ts.map +0 -1
- package/dist/docsTypes/documentationContent.js.map +0 -1
- package/dist/docsTypes/documentationDocument.d.ts.map +0 -1
- package/dist/docsTypes/documentationDocument.js.map +0 -1
- package/dist/docsTypes/documentationMetadata.d.ts.map +0 -1
- package/dist/docsTypes/documentationMetadata.js.map +0 -1
- package/dist/docsTypes/documentationNavigation.d.ts.map +0 -1
- package/dist/docsTypes/documentationNavigation.js.map +0 -1
- package/dist/docsTypes/documentationProvider.d.ts.map +0 -1
- package/dist/docsTypes/documentationProvider.js.map +0 -1
- package/dist/docsTypes/index.d.ts.map +0 -1
- package/dist/docsTypes/index.js.map +0 -1
- package/dist/document/documentBuilder.core.d.ts.map +0 -1
- package/dist/document/documentBuilder.core.js.map +0 -1
- package/dist/document/index.d.ts.map +0 -1
- package/dist/document/index.js.map +0 -1
- package/dist/examples/examples.core.d.ts.map +0 -1
- package/dist/examples/examples.core.js.map +0 -1
- package/dist/examples/index.d.ts.map +0 -1
- package/dist/examples/index.js.map +0 -1
- package/dist/frontmatter/frontmatter.core.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.core.js.map +0 -1
- package/dist/frontmatter/frontmatter.parser.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.parser.js.map +0 -1
- package/dist/frontmatter/frontmatter.serializer.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.serializer.js.map +0 -1
- package/dist/frontmatter/frontmatter.types.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.types.js.map +0 -1
- package/dist/frontmatter/index.d.ts.map +0 -1
- package/dist/frontmatter/index.js.map +0 -1
- package/dist/generator/generator.types.d.ts.map +0 -1
- package/dist/generator/generator.types.js.map +0 -1
- package/dist/generator/generatorJson.core.d.ts.map +0 -1
- package/dist/generator/generatorJson.core.js.map +0 -1
- package/dist/generator/generatorMarkdown.core.d.ts.map +0 -1
- package/dist/generator/generatorMarkdown.core.js.map +0 -1
- package/dist/generator/generatorMarkdownNodes.d.ts.map +0 -1
- package/dist/generator/generatorMarkdownNodes.js.map +0 -1
- package/dist/generator/index.d.ts.map +0 -1
- package/dist/generator/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/navigation/index.d.ts.map +0 -1
- package/dist/navigation/index.js.map +0 -1
- package/dist/navigation/navigation.core.d.ts.map +0 -1
- package/dist/navigation/navigation.core.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/registry.core.d.ts.map +0 -1
- package/dist/registry/registry.core.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/utils.helper.d.ts.map +0 -1
- package/dist/utils/utils.helper.js.map +0 -1
- package/dist/validator/index.d.ts.map +0 -1
- package/dist/validator/index.js.map +0 -1
- package/dist/validator/validator.types.d.ts.map +0 -1
- package/dist/validator/validator.types.js.map +0 -1
- package/dist/validator/validatorAll/index.d.ts.map +0 -1
- package/dist/validator/validatorAll/index.js.map +0 -1
- package/dist/validator/validatorAll/validatorAll.core.d.ts.map +0 -1
- package/dist/validator/validatorAll/validatorAll.core.js.map +0 -1
- package/dist/validator/validatorAll/validatorNavigation.core.d.ts.map +0 -1
- package/dist/validator/validatorAll/validatorNavigation.core.js.map +0 -1
- package/dist/validator/validatorDocument.core.d.ts.map +0 -1
- package/dist/validator/validatorDocument.core.js.map +0 -1
- package/dist/validator/validatorDuplicates.core.d.ts.map +0 -1
- package/dist/validator/validatorDuplicates.core.js.map +0 -1
- package/dist/validator/validatorLinks.core.d.ts.map +0 -1
- package/dist/validator/validatorLinks.core.js.map +0 -1
|
@@ -1,34 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility helpers for the documentation package.
|
|
3
3
|
*/
|
|
4
|
+
/** Characters allowed in one segment of a document ID. */
|
|
5
|
+
const ID_SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
6
|
+
/**
|
|
7
|
+
* Returns true when `id` is a stable dot-separated identifier such as
|
|
8
|
+
* `guides.http.routing`: non-empty segments of letters, digits, `_`
|
|
9
|
+
* and `-`, with no whitespace or empty segments.
|
|
10
|
+
*/
|
|
11
|
+
export function isValidDocumentId(id) {
|
|
12
|
+
if (typeof id !== "string" || id.length === 0)
|
|
13
|
+
return false;
|
|
14
|
+
return id.split(".").every((segment) => ID_SEGMENT.test(segment));
|
|
15
|
+
}
|
|
4
16
|
/**
|
|
5
17
|
* Normalizes a document ID to a consistent format.
|
|
6
|
-
*
|
|
18
|
+
* Trims whitespace, collapses consecutive dots and strips leading/trailing dots.
|
|
7
19
|
*/
|
|
8
20
|
export function normalizeDocumentId(id) {
|
|
9
|
-
return id
|
|
21
|
+
return (id
|
|
22
|
+
.trim()
|
|
10
23
|
.replace(/\.+/g, ".")
|
|
11
|
-
|
|
12
|
-
.
|
|
24
|
+
// Runs are already collapsed, so at most one dot can sit at each end.
|
|
25
|
+
// Matching a single dot keeps this linear; `\.+$` would backtrack.
|
|
26
|
+
.replace(/^\.|\.$/g, ""));
|
|
13
27
|
}
|
|
14
28
|
/**
|
|
15
29
|
* Generates a document ID from a file path.
|
|
16
|
-
* Converts path separators to dots and removes extension
|
|
30
|
+
* Converts path separators to dots and removes the extension of the
|
|
31
|
+
* final segment only (`guides/v1.2/routing.md` → `guides.v1.2.routing`).
|
|
32
|
+
* Returns an empty string when the path has no usable segment
|
|
33
|
+
* (e.g. `.gitignore`).
|
|
17
34
|
*/
|
|
18
35
|
export function documentIdFromPath(path) {
|
|
19
|
-
|
|
36
|
+
const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
37
|
+
const segments = normalizedPath.split("/").filter((s) => s.length > 0);
|
|
38
|
+
if (segments.length === 0)
|
|
39
|
+
return "";
|
|
40
|
+
const last = segments[segments.length - 1] ?? "";
|
|
41
|
+
const stem = last.replace(/\.[^.]+$/, "");
|
|
42
|
+
if (stem.length === 0)
|
|
43
|
+
return "";
|
|
44
|
+
segments[segments.length - 1] = stem;
|
|
45
|
+
return normalizeDocumentId(segments.join("."));
|
|
20
46
|
}
|
|
21
47
|
/**
|
|
22
48
|
* Resolves a relative document link against a base ID.
|
|
23
49
|
* "guides.http" + "./routing" → "guides.http.routing"
|
|
50
|
+
*
|
|
51
|
+
* Fragments (`#section`), `.md`/`.mdx` extensions and Windows
|
|
52
|
+
* separators are stripped before resolving.
|
|
24
53
|
*/
|
|
25
54
|
export function resolveDocumentLink(baseId, link) {
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
const cleaned = stripLinkDecorations(link);
|
|
56
|
+
if (cleaned.startsWith("/")) {
|
|
57
|
+
return normalizeDocumentId(cleaned.slice(1).replace(/\//g, "."));
|
|
28
58
|
}
|
|
29
|
-
const baseParts = baseId.split(".");
|
|
59
|
+
const baseParts = baseId.split(".").filter((p) => p.length > 0);
|
|
30
60
|
baseParts.pop();
|
|
31
|
-
const linkParts =
|
|
61
|
+
const linkParts = cleaned.split("/");
|
|
32
62
|
for (const part of linkParts) {
|
|
33
63
|
if (part === "..") {
|
|
34
64
|
baseParts.pop();
|
|
@@ -40,43 +70,100 @@ export function resolveDocumentLink(baseId, link) {
|
|
|
40
70
|
return normalizeDocumentId(baseParts.join("."));
|
|
41
71
|
}
|
|
42
72
|
/**
|
|
43
|
-
*
|
|
73
|
+
* Removes a `#fragment`, a trailing `.md`/`.mdx` extension and
|
|
74
|
+
* converts `\` to `/` in a link target.
|
|
75
|
+
*/
|
|
76
|
+
export function stripLinkDecorations(link) {
|
|
77
|
+
const withoutFragment = link.split("#")[0] ?? "";
|
|
78
|
+
return withoutFragment.replace(/\\/g, "/").replace(/\.mdx?$/i, "");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Removes fenced code blocks (``` or ~~~) from markdown so that
|
|
82
|
+
* headings and links inside them are not interpreted.
|
|
83
|
+
*/
|
|
84
|
+
export function stripFencedCodeBlocks(markdown) {
|
|
85
|
+
return markdown.replace(/^(`{3,}|~{3,})[^\n]*\n[\s\S]*?^\1[^\n]*$/gm, "");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Extracts the title from markdown content (first level-1 heading
|
|
89
|
+
* outside fenced code blocks).
|
|
44
90
|
*/
|
|
45
91
|
export function extractTitleFromMarkdown(markdown) {
|
|
46
|
-
const match = markdown.match(/^# ([^\n]+)$/m);
|
|
47
|
-
return match?.[1]
|
|
92
|
+
const match = stripFencedCodeBlocks(markdown).match(/^# ([^\n]+)$/m);
|
|
93
|
+
return match?.[1] ? cleanHeadingText(match[1]) : undefined;
|
|
48
94
|
}
|
|
49
95
|
/**
|
|
50
|
-
* Extracts headings from markdown content.
|
|
96
|
+
* Extracts headings from markdown content (outside fenced code blocks).
|
|
51
97
|
*/
|
|
52
98
|
export function extractHeadings(markdown) {
|
|
53
99
|
const results = [];
|
|
54
100
|
const pattern = /^(#{1,6}) ([^\n]+)$/gm;
|
|
101
|
+
const source = stripFencedCodeBlocks(markdown);
|
|
55
102
|
let match;
|
|
56
|
-
while ((match = pattern.exec(
|
|
103
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
57
104
|
const hashes = match[1];
|
|
58
105
|
const headingText = match[2];
|
|
59
106
|
if (hashes && headingText) {
|
|
60
107
|
results.push({
|
|
61
108
|
level: hashes.length,
|
|
62
|
-
text: headingText
|
|
109
|
+
text: cleanHeadingText(headingText),
|
|
63
110
|
});
|
|
64
111
|
}
|
|
65
112
|
}
|
|
66
113
|
return Object.freeze(results);
|
|
67
114
|
}
|
|
115
|
+
/** Trims a heading and removes ATX closing hashes (`# Title #`). */
|
|
116
|
+
function cleanHeadingText(text) {
|
|
117
|
+
return text.replace(/\s+#+\s*$/, "").trim();
|
|
118
|
+
}
|
|
119
|
+
const HTML_TAG = /<\/?[a-zA-Z][^>\n]*>/g;
|
|
120
|
+
/**
|
|
121
|
+
* Removes HTML tags until the result stops changing. A single pass is not
|
|
122
|
+
* enough: stripping the inner tag of `<<b>b>x` re-forms `<b>x`, so a lone pass
|
|
123
|
+
* can leave a tag behind in text that is later rendered as HTML.
|
|
124
|
+
*/
|
|
125
|
+
function stripHtmlTags(text) {
|
|
126
|
+
let current = text;
|
|
127
|
+
let previous;
|
|
128
|
+
do {
|
|
129
|
+
previous = current;
|
|
130
|
+
current = current.replace(HTML_TAG, "");
|
|
131
|
+
} while (current !== previous);
|
|
132
|
+
return current;
|
|
133
|
+
}
|
|
68
134
|
/**
|
|
69
135
|
* Strips markdown formatting to plain text.
|
|
70
136
|
*/
|
|
71
137
|
export function stripMarkdown(markdown) {
|
|
72
|
-
return
|
|
138
|
+
return (stripHtmlTags(
|
|
139
|
+
// fenced code blocks: keep the code, drop the fences
|
|
140
|
+
markdown.replace(/^(`{3,}|~{3,})[^\n]*\n([\s\S]*?)^\1[^\n]*$/gm, "$2"))
|
|
141
|
+
// images before links
|
|
142
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
143
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
144
|
+
// headings
|
|
73
145
|
.replace(/^#{1,6}\s+/gm, "")
|
|
74
|
-
.replace(
|
|
75
|
-
|
|
146
|
+
.replace(/\s+#+\s*$/gm, "")
|
|
147
|
+
// emphasis (bold before italic, non-greedy)
|
|
148
|
+
.replace(/\*\*(.+?)\*\*/g, "$1")
|
|
149
|
+
.replace(/__(.+?)__/g, "$1")
|
|
150
|
+
.replace(/\*(.+?)\*/g, "$1")
|
|
151
|
+
.replace(/(^|[^\w])_(.+?)_(?=[^\w]|$)/g, "$1$2")
|
|
152
|
+
.replace(/~~(.+?)~~/g, "$1")
|
|
153
|
+
// inline code
|
|
76
154
|
.replace(/`([^`]+)`/g, "$1")
|
|
77
|
-
|
|
155
|
+
// blockquotes, list markers
|
|
156
|
+
.replace(/^\s*>\s?/gm, "")
|
|
78
157
|
.replace(/^\s*[-*+]\s+/gm, "")
|
|
79
158
|
.replace(/^\s*\d+\.\s+/gm, "")
|
|
80
|
-
|
|
159
|
+
// tables: drop separator rows, unpipe cells
|
|
160
|
+
.replace(/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/gm, "")
|
|
161
|
+
.replace(/^\s*\|/gm, "")
|
|
162
|
+
.replace(/\|\s*$/gm, "")
|
|
163
|
+
.replace(/\s*\|\s*/g, " ")
|
|
164
|
+
// horizontal rules
|
|
165
|
+
.replace(/^\s*([-*_]\s*){3,}$/gm, "")
|
|
166
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
167
|
+
.trim());
|
|
81
168
|
}
|
|
82
169
|
//# sourceMappingURL=utils.helper.js.map
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { validateDocument } from "./validatorDocument.core.js";
|
|
7
7
|
export { validateNoDuplicateIds } from "./validatorDuplicates.core.js";
|
|
8
|
-
export { validateLinks } from "./validatorLinks.core.js";
|
|
8
|
+
export { validateLinks, DEFAULT_MAX_LINK_SCAN_LENGTH, } from "./validatorLinks.core.js";
|
|
9
9
|
export { validateAll, validateNavigation } from "./validatorAll/index.js";
|
|
10
|
+
export { toValidationResult } from "./validator.types.js";
|
|
10
11
|
export type { ValidationIssue, ValidationResult } from "./validator.types.js";
|
|
12
|
+
export type { ValidateLinksOptions } from "./validatorLinks.core.js";
|
|
13
|
+
export type { ValidateAllOptions } from "./validatorAll/validatorAll.core.js";
|
|
11
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/validator/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { validateDocument } from "./validatorDocument.core.js";
|
|
7
7
|
export { validateNoDuplicateIds } from "./validatorDuplicates.core.js";
|
|
8
|
-
export { validateLinks } from "./validatorLinks.core.js";
|
|
8
|
+
export { validateLinks, DEFAULT_MAX_LINK_SCAN_LENGTH, } from "./validatorLinks.core.js";
|
|
9
9
|
export { validateAll, validateNavigation } from "./validatorAll/index.js";
|
|
10
|
+
export { toValidationResult } from "./validator.types.js";
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -12,9 +12,17 @@ export interface ValidationIssue {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Result of validating documentation.
|
|
15
|
+
*
|
|
16
|
+
* `valid` is `false` only when at least one issue has severity
|
|
17
|
+
* `"error"`; warnings never make a result invalid.
|
|
15
18
|
*/
|
|
16
19
|
export interface ValidationResult {
|
|
17
20
|
readonly valid: boolean;
|
|
18
21
|
readonly issues: readonly ValidationIssue[];
|
|
19
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds a `ValidationResult` from a list of issues using the single
|
|
25
|
+
* rule shared by every validator in this package.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toValidationResult(issues: readonly ValidationIssue[]): ValidationResult;
|
|
20
28
|
//# sourceMappingURL=validator.types.d.ts.map
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for document validation.
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Builds a `ValidationResult` from a list of issues using the single
|
|
6
|
+
* rule shared by every validator in this package.
|
|
7
|
+
*/
|
|
8
|
+
export function toValidationResult(issues) {
|
|
9
|
+
return {
|
|
10
|
+
valid: !issues.some((issue) => issue.severity === "error"),
|
|
11
|
+
issues,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
5
14
|
//# sourceMappingURL=validator.types.js.map
|
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
* Validates an entire documentation set.
|
|
3
3
|
*/
|
|
4
4
|
import type { DocumentationDocument, DocumentationNavigationItem } from "../../docsTypes/index.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type ValidationResult } from "../validator.types.js";
|
|
6
|
+
import { type ValidateLinksOptions } from "../validatorLinks.core.js";
|
|
7
|
+
/** Options for `validateAll`. */
|
|
8
|
+
export interface ValidateAllOptions extends ValidateLinksOptions {
|
|
9
|
+
/**
|
|
10
|
+
* When a navigation tree is supplied, report documents that do not
|
|
11
|
+
* appear in it as `NAVIGATION_ORPHAN_DOCUMENT` warnings. Default true.
|
|
12
|
+
*/
|
|
13
|
+
readonly reportOrphans?: boolean;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* Validates all documents and optionally a navigation tree.
|
|
8
17
|
*/
|
|
9
|
-
export declare function validateAll(documents: readonly DocumentationDocument[], navigation?: readonly DocumentationNavigationItem[]): ValidationResult;
|
|
18
|
+
export declare function validateAll(documents: readonly DocumentationDocument[], navigation?: readonly DocumentationNavigationItem[], options?: ValidateAllOptions): ValidationResult;
|
|
10
19
|
//# sourceMappingURL=validatorAll.core.d.ts.map
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validates an entire documentation set.
|
|
3
3
|
*/
|
|
4
|
+
import { flattenNavigation } from "../../navigation/navigation.core.js";
|
|
5
|
+
import { toValidationResult, } from "../validator.types.js";
|
|
4
6
|
import { validateDocument } from "../validatorDocument.core.js";
|
|
5
7
|
import { validateNoDuplicateIds } from "../validatorDuplicates.core.js";
|
|
6
|
-
import { validateLinks } from "../validatorLinks.core.js";
|
|
8
|
+
import { validateLinks, } from "../validatorLinks.core.js";
|
|
7
9
|
import { validateNavigation } from "./validatorNavigation.core.js";
|
|
8
10
|
/**
|
|
9
11
|
* Validates all documents and optionally a navigation tree.
|
|
10
12
|
*/
|
|
11
|
-
export function validateAll(documents, navigation) {
|
|
13
|
+
export function validateAll(documents, navigation, options = {}) {
|
|
12
14
|
const allIssues = [];
|
|
13
15
|
const idResult = validateNoDuplicateIds(documents);
|
|
14
16
|
allIssues.push(...idResult.issues);
|
|
@@ -16,16 +18,26 @@ export function validateAll(documents, navigation) {
|
|
|
16
18
|
for (const doc of documents) {
|
|
17
19
|
const docResult = validateDocument(doc);
|
|
18
20
|
allIssues.push(...docResult.issues);
|
|
19
|
-
const linkResult = validateLinks(doc, registeredIds);
|
|
21
|
+
const linkResult = validateLinks(doc, registeredIds, options);
|
|
20
22
|
allIssues.push(...linkResult.issues);
|
|
21
23
|
}
|
|
22
24
|
if (navigation) {
|
|
23
25
|
const navResult = validateNavigation(navigation, registeredIds);
|
|
24
26
|
allIssues.push(...navResult.issues);
|
|
27
|
+
if (options.reportOrphans ?? true) {
|
|
28
|
+
const inNavigation = new Set(flattenNavigation(navigation));
|
|
29
|
+
for (const id of registeredIds) {
|
|
30
|
+
if (!inNavigation.has(id)) {
|
|
31
|
+
allIssues.push({
|
|
32
|
+
severity: "warning",
|
|
33
|
+
code: "NAVIGATION_ORPHAN_DOCUMENT",
|
|
34
|
+
message: `Document "${id}" is not reachable from the navigation.`,
|
|
35
|
+
documentId: id,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
|
-
return
|
|
27
|
-
valid: allIssues.filter((i) => i.severity === "error").length === 0,
|
|
28
|
-
issues: allIssues,
|
|
29
|
-
};
|
|
41
|
+
return toValidationResult(allIssues);
|
|
30
42
|
}
|
|
31
43
|
//# sourceMappingURL=validatorAll.core.js.map
|
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* Validates navigation tree references against registered documents.
|
|
3
3
|
*/
|
|
4
4
|
import type { DocumentationNavigationItem } from "../../docsTypes/index.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type ValidationResult } from "../validator.types.js";
|
|
6
6
|
/**
|
|
7
7
|
* Validates navigation tree references.
|
|
8
|
+
*
|
|
9
|
+
* Reports `NAVIGATION_UNKNOWN_DOCUMENT` (error) for references to
|
|
10
|
+
* unregistered documents, `NAVIGATION_CYCLE` (error) when a node is
|
|
11
|
+
* its own ancestor or the tree is deeper than `MAX_NAVIGATION_DEPTH`,
|
|
12
|
+
* `NAVIGATION_DUPLICATE_DOCUMENT` (warning) when a document is
|
|
13
|
+
* referenced more than once, and `NAVIGATION_EMPTY_ITEM` (warning)
|
|
14
|
+
* for items with neither `documentId` nor `children`.
|
|
8
15
|
*/
|
|
9
16
|
export declare function validateNavigation(items: readonly DocumentationNavigationItem[], registeredIds: ReadonlySet<string>): ValidationResult;
|
|
10
17
|
//# sourceMappingURL=validatorNavigation.core.d.ts.map
|
|
@@ -1,29 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validates navigation tree references against registered documents.
|
|
3
3
|
*/
|
|
4
|
+
import { MAX_NAVIGATION_DEPTH } from "../../navigation/navigation.core.js";
|
|
5
|
+
import { toValidationResult, } from "../validator.types.js";
|
|
4
6
|
/**
|
|
5
7
|
* Validates navigation tree references.
|
|
8
|
+
*
|
|
9
|
+
* Reports `NAVIGATION_UNKNOWN_DOCUMENT` (error) for references to
|
|
10
|
+
* unregistered documents, `NAVIGATION_CYCLE` (error) when a node is
|
|
11
|
+
* its own ancestor or the tree is deeper than `MAX_NAVIGATION_DEPTH`,
|
|
12
|
+
* `NAVIGATION_DUPLICATE_DOCUMENT` (warning) when a document is
|
|
13
|
+
* referenced more than once, and `NAVIGATION_EMPTY_ITEM` (warning)
|
|
14
|
+
* for items with neither `documentId` nor `children`.
|
|
6
15
|
*/
|
|
7
16
|
export function validateNavigation(items, registeredIds) {
|
|
8
17
|
const issues = [];
|
|
9
|
-
|
|
18
|
+
const seenIds = new Set();
|
|
19
|
+
const visited = new WeakSet();
|
|
20
|
+
function walk(nodes, ancestors) {
|
|
21
|
+
if (ancestors.length > MAX_NAVIGATION_DEPTH) {
|
|
22
|
+
issues.push({
|
|
23
|
+
severity: "error",
|
|
24
|
+
code: "NAVIGATION_CYCLE",
|
|
25
|
+
message: `Navigation tree exceeds the maximum depth of ${MAX_NAVIGATION_DEPTH}.`,
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
10
29
|
for (const node of nodes) {
|
|
11
|
-
if (node
|
|
30
|
+
if (!node || typeof node !== "object") {
|
|
31
|
+
issues.push({
|
|
32
|
+
severity: "error",
|
|
33
|
+
code: "NAVIGATION_EMPTY_ITEM",
|
|
34
|
+
message: "Navigation item is not an object.",
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ancestors.includes(node)) {
|
|
12
39
|
issues.push({
|
|
13
40
|
severity: "error",
|
|
14
|
-
code: "
|
|
15
|
-
message: `Navigation item "${node.title}"
|
|
41
|
+
code: "NAVIGATION_CYCLE",
|
|
42
|
+
message: `Navigation item "${node.title}" is its own ancestor.`,
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (visited.has(node))
|
|
47
|
+
continue;
|
|
48
|
+
visited.add(node);
|
|
49
|
+
if (node.documentId) {
|
|
50
|
+
if (!registeredIds.has(node.documentId)) {
|
|
51
|
+
issues.push({
|
|
52
|
+
severity: "error",
|
|
53
|
+
code: "NAVIGATION_UNKNOWN_DOCUMENT",
|
|
54
|
+
message: `Navigation item "${node.title}" references unknown document "${node.documentId}".`,
|
|
55
|
+
documentId: node.documentId,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (seenIds.has(node.documentId)) {
|
|
59
|
+
issues.push({
|
|
60
|
+
severity: "warning",
|
|
61
|
+
code: "NAVIGATION_DUPLICATE_DOCUMENT",
|
|
62
|
+
message: `Document "${node.documentId}" is referenced more than once in the navigation.`,
|
|
63
|
+
documentId: node.documentId,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
seenIds.add(node.documentId);
|
|
67
|
+
}
|
|
68
|
+
else if (!node.children || node.children.length === 0) {
|
|
69
|
+
issues.push({
|
|
70
|
+
severity: "warning",
|
|
71
|
+
code: "NAVIGATION_EMPTY_ITEM",
|
|
72
|
+
message: `Navigation item "${node.title}" has neither a documentId nor children.`,
|
|
16
73
|
});
|
|
17
74
|
}
|
|
18
75
|
if (node.children) {
|
|
19
|
-
walk(node.children);
|
|
76
|
+
walk(node.children, [...ancestors, node]);
|
|
20
77
|
}
|
|
21
78
|
}
|
|
22
79
|
}
|
|
23
|
-
walk(items);
|
|
24
|
-
return
|
|
25
|
-
valid: issues.length === 0,
|
|
26
|
-
issues,
|
|
27
|
-
};
|
|
80
|
+
walk(items, []);
|
|
81
|
+
return toValidationResult(issues);
|
|
28
82
|
}
|
|
29
83
|
//# sourceMappingURL=validatorNavigation.core.js.map
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Validates a single document for structural correctness.
|
|
3
3
|
*/
|
|
4
4
|
import type { DocumentationDocument } from "../docsTypes/index.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type ValidationResult } from "./validator.types.js";
|
|
6
6
|
/**
|
|
7
|
-
* Validates a single document
|
|
7
|
+
* Validates a single document: ID syntax, required fields, content
|
|
8
|
+
* shape (including structured nodes), and the metadata enums.
|
|
8
9
|
*/
|
|
9
10
|
export declare function validateDocument(document: DocumentationDocument): ValidationResult;
|
|
10
11
|
//# sourceMappingURL=validatorDocument.core.d.ts.map
|