@zudojs/docs 1.0.6 → 1.1.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.
Files changed (92) hide show
  1. package/README.md +4 -1
  2. package/dist/docsTypes/documentationContent.d.ts +0 -1
  3. package/dist/docsTypes/documentationContent.js +0 -1
  4. package/dist/docsTypes/documentationDocument.d.ts +0 -1
  5. package/dist/docsTypes/documentationDocument.js +0 -1
  6. package/dist/docsTypes/documentationMetadata.d.ts +5 -2
  7. package/dist/docsTypes/documentationMetadata.js +0 -1
  8. package/dist/docsTypes/documentationNavigation.d.ts +9 -3
  9. package/dist/docsTypes/documentationNavigation.js +0 -1
  10. package/dist/docsTypes/documentationProvider.d.ts +5 -2
  11. package/dist/docsTypes/documentationProvider.js +0 -1
  12. package/dist/docsTypes/index.d.ts +0 -1
  13. package/dist/docsTypes/index.js +0 -1
  14. package/dist/document/documentBuilder.convenience.d.ts +15 -0
  15. package/dist/document/documentBuilder.convenience.js +28 -0
  16. package/dist/document/documentBuilder.core.d.ts +18 -13
  17. package/dist/document/documentBuilder.core.js +16 -50
  18. package/dist/document/documentBuilder.normalize.d.ts +5 -1
  19. package/dist/document/documentBuilder.normalize.js +12 -1
  20. package/dist/document/documentBuilder.validate.d.ts +16 -0
  21. package/dist/document/documentBuilder.validate.js +36 -0
  22. package/dist/document/index.d.ts +2 -2
  23. package/dist/document/index.js +2 -2
  24. package/dist/errors/index.d.ts +0 -1
  25. package/dist/errors/index.js +0 -1
  26. package/dist/examples/examples.core.d.ts +0 -1
  27. package/dist/examples/examples.core.js +0 -1
  28. package/dist/examples/index.d.ts +0 -1
  29. package/dist/examples/index.js +0 -1
  30. package/dist/frontmatter/frontmatter.core.d.ts +0 -1
  31. package/dist/frontmatter/frontmatter.core.js +0 -1
  32. package/dist/frontmatter/frontmatter.parser.d.ts +0 -1
  33. package/dist/frontmatter/frontmatter.parser.js +0 -1
  34. package/dist/frontmatter/frontmatter.serializer.d.ts +0 -1
  35. package/dist/frontmatter/frontmatter.serializer.js +0 -1
  36. package/dist/frontmatter/frontmatter.types.d.ts +0 -1
  37. package/dist/frontmatter/frontmatter.types.js +0 -1
  38. package/dist/frontmatter/frontmatter.values.d.ts +0 -1
  39. package/dist/frontmatter/frontmatter.values.js +0 -1
  40. package/dist/frontmatter/index.d.ts +0 -1
  41. package/dist/frontmatter/index.js +0 -1
  42. package/dist/generator/generator.types.d.ts +0 -1
  43. package/dist/generator/generator.types.js +0 -1
  44. package/dist/generator/generatorJson.core.d.ts +0 -1
  45. package/dist/generator/generatorJson.core.js +0 -1
  46. package/dist/generator/generatorMarkdown.core.d.ts +0 -1
  47. package/dist/generator/generatorMarkdown.core.js +0 -1
  48. package/dist/generator/generatorMarkdownNodes.d.ts +0 -1
  49. package/dist/generator/generatorMarkdownNodes.js +0 -1
  50. package/dist/generator/index.d.ts +0 -1
  51. package/dist/generator/index.js +0 -1
  52. package/dist/index.d.ts +0 -1
  53. package/dist/index.js +0 -1
  54. package/dist/navigation/index.d.ts +3 -2
  55. package/dist/navigation/index.js +2 -2
  56. package/dist/navigation/navigation.adjacent.d.ts +32 -0
  57. package/dist/navigation/navigation.adjacent.js +60 -0
  58. package/dist/navigation/navigation.core.d.ts +0 -13
  59. package/dist/navigation/navigation.core.js +0 -51
  60. package/dist/registry/index.d.ts +0 -1
  61. package/dist/registry/index.js +0 -1
  62. package/dist/registry/registry.core.d.ts +0 -1
  63. package/dist/registry/registry.core.js +0 -1
  64. package/dist/utils/index.d.ts +0 -1
  65. package/dist/utils/index.js +0 -1
  66. package/dist/utils/utils.freeze.d.ts +0 -1
  67. package/dist/utils/utils.freeze.js +0 -1
  68. package/dist/utils/utils.helper.d.ts +0 -1
  69. package/dist/utils/utils.helper.js +0 -1
  70. package/dist/utils/utils.href.d.ts +0 -1
  71. package/dist/utils/utils.href.js +0 -1
  72. package/dist/utils/utils.markdownText.d.ts +0 -1
  73. package/dist/utils/utils.markdownText.js +0 -1
  74. package/dist/validator/index.d.ts +0 -1
  75. package/dist/validator/index.js +0 -1
  76. package/dist/validator/validator.types.d.ts +0 -1
  77. package/dist/validator/validator.types.js +0 -1
  78. package/dist/validator/validatorAll/index.d.ts +0 -1
  79. package/dist/validator/validatorAll/index.js +0 -1
  80. package/dist/validator/validatorAll/validatorAll.core.d.ts +11 -1
  81. package/dist/validator/validatorAll/validatorAll.core.js +6 -2
  82. package/dist/validator/validatorAll/validatorNavigation.core.d.ts +0 -1
  83. package/dist/validator/validatorAll/validatorNavigation.core.js +0 -1
  84. package/dist/validator/validatorDocument.core.d.ts +0 -1
  85. package/dist/validator/validatorDocument.core.js +0 -1
  86. package/dist/validator/validatorDuplicates.core.d.ts +0 -1
  87. package/dist/validator/validatorDuplicates.core.js +0 -1
  88. package/dist/validator/validatorLinks.core.d.ts +16 -6
  89. package/dist/validator/validatorLinks.core.js +18 -37
  90. package/dist/validator/validatorLinks.extract.d.ts +30 -0
  91. package/dist/validator/validatorLinks.extract.js +118 -0
  92. package/package.json +2 -2
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Navigation tree utilities — breadcrumbs, flattening, sibling resolution.
5
5
  */
6
- export { getBreadcrumbs, flattenNavigation, findNavigationItem, getSiblings, getAdjacent, } from "./navigation.core.js";
7
- //# sourceMappingURL=index.js.map
6
+ export { getBreadcrumbs, flattenNavigation, findNavigationItem, } from "./navigation.core.js";
7
+ export { getSiblings, getAdjacent } from "./navigation.adjacent.js";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Sibling and previous/next resolution over a navigation tree.
3
+ *
4
+ * @module navigation/navigation.adjacent
5
+ */
6
+ import type { DocumentationNavigationItem } from "../docsTypes/index.js";
7
+ type Nodes = readonly DocumentationNavigationItem[];
8
+ /**
9
+ * Gets sibling document IDs for a given document (the other documents
10
+ * at the same navigation level, excluding the document itself).
11
+ */
12
+ export declare function getSiblings(documentId: string, items: Nodes): readonly string[];
13
+ /** Options for `getAdjacent`. */
14
+ export interface GetAdjacentOptions {
15
+ /**
16
+ * `"level"` (default) looks only at the documents that share the
17
+ * target's navigation level, so the first item of a section has no
18
+ * `previous`. `"tree"` walks the whole tree in reading order (the
19
+ * order of `flattenNavigation`), crossing section boundaries.
20
+ */
21
+ readonly scope?: "level" | "tree";
22
+ }
23
+ /**
24
+ * Gets the previous and next document IDs around `documentId`: within its
25
+ * navigation level by default, or across the whole tree in reading order
26
+ * with `{ scope: "tree" }`.
27
+ */
28
+ export declare function getAdjacent(documentId: string, items: Nodes, options?: GetAdjacentOptions): {
29
+ readonly previous?: string;
30
+ readonly next?: string;
31
+ };
32
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Sibling and previous/next resolution over a navigation tree.
3
+ *
4
+ * @module navigation/navigation.adjacent
5
+ */
6
+ import { MAX_NAVIGATION_DEPTH, flattenNavigation } from "./navigation.core.js";
7
+ /**
8
+ * Returns the list of document IDs at the same level as `documentId`,
9
+ * including the document itself, in navigation order.
10
+ */
11
+ function getLevel(documentId, items) {
12
+ const seen = new WeakSet();
13
+ function walk(nodes, depth) {
14
+ if (depth > MAX_NAVIGATION_DEPTH)
15
+ return undefined;
16
+ for (const node of nodes) {
17
+ if (seen.has(node))
18
+ continue;
19
+ seen.add(node);
20
+ if (node.documentId === documentId) {
21
+ return nodes
22
+ .map((n) => n.documentId)
23
+ .filter((id) => typeof id === "string");
24
+ }
25
+ if (node.children) {
26
+ const result = walk(node.children, depth + 1);
27
+ if (result)
28
+ return result;
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ return walk(items, 0) ?? [];
34
+ }
35
+ /**
36
+ * Gets sibling document IDs for a given document (the other documents
37
+ * at the same navigation level, excluding the document itself).
38
+ */
39
+ export function getSiblings(documentId, items) {
40
+ return Object.freeze(getLevel(documentId, items).filter((id) => id !== documentId));
41
+ }
42
+ /**
43
+ * Gets the previous and next document IDs around `documentId`: within its
44
+ * navigation level by default, or across the whole tree in reading order
45
+ * with `{ scope: "tree" }`.
46
+ */
47
+ export function getAdjacent(documentId, items, options = {}) {
48
+ const level = options.scope === "tree"
49
+ ? flattenNavigation(items)
50
+ : getLevel(documentId, items);
51
+ const index = level.indexOf(documentId);
52
+ if (index === -1)
53
+ return Object.freeze({});
54
+ const previous = level[index - 1];
55
+ const next = level[index + 1];
56
+ return Object.freeze({
57
+ ...(previous !== undefined ? { previous } : {}),
58
+ ...(next !== undefined ? { next } : {}),
59
+ });
60
+ }
@@ -28,17 +28,4 @@ export declare function flattenNavigation(items: Nodes): readonly string[];
28
28
  * Finds a navigation item by document ID.
29
29
  */
30
30
  export declare function findNavigationItem(documentId: string, items: Nodes): DocumentationNavigationItem | undefined;
31
- /**
32
- * Gets sibling document IDs for a given document (the other documents
33
- * at the same navigation level, excluding the document itself).
34
- */
35
- export declare function getSiblings(documentId: string, items: Nodes): readonly string[];
36
- /**
37
- * Gets the previous and next document IDs at the same navigation level.
38
- */
39
- export declare function getAdjacent(documentId: string, items: Nodes): {
40
- readonly previous?: string;
41
- readonly next?: string;
42
- };
43
31
  export {};
44
- //# sourceMappingURL=navigation.core.d.ts.map
@@ -100,54 +100,3 @@ export function findNavigationItem(documentId, items) {
100
100
  }
101
101
  return walk(items, 0);
102
102
  }
103
- /**
104
- * Returns the list of document IDs at the same level as `documentId`,
105
- * including the document itself, in navigation order.
106
- */
107
- function getLevel(documentId, items) {
108
- const seen = new WeakSet();
109
- function walk(nodes, depth) {
110
- if (depth > MAX_NAVIGATION_DEPTH)
111
- return undefined;
112
- for (const node of nodes) {
113
- if (seen.has(node))
114
- continue;
115
- seen.add(node);
116
- if (node.documentId === documentId) {
117
- return nodes
118
- .map((n) => n.documentId)
119
- .filter((id) => typeof id === "string");
120
- }
121
- if (node.children) {
122
- const result = walk(node.children, depth + 1);
123
- if (result)
124
- return result;
125
- }
126
- }
127
- return undefined;
128
- }
129
- return walk(items, 0) ?? [];
130
- }
131
- /**
132
- * Gets sibling document IDs for a given document (the other documents
133
- * at the same navigation level, excluding the document itself).
134
- */
135
- export function getSiblings(documentId, items) {
136
- return Object.freeze(getLevel(documentId, items).filter((id) => id !== documentId));
137
- }
138
- /**
139
- * Gets the previous and next document IDs at the same navigation level.
140
- */
141
- export function getAdjacent(documentId, items) {
142
- const level = getLevel(documentId, items);
143
- const index = level.indexOf(documentId);
144
- if (index === -1)
145
- return Object.freeze({});
146
- const previous = level[index - 1];
147
- const next = level[index + 1];
148
- return Object.freeze({
149
- ...(previous !== undefined ? { previous } : {}),
150
- ...(next !== undefined ? { next } : {}),
151
- });
152
- }
153
- //# sourceMappingURL=navigation.core.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { DocumentRegistry, createDocumentRegistry, matchesVisibility, } from "./registry.core.js";
7
7
  export type { DocumentVisibilityFilter, GetAllOptions, } from "./registry.core.js";
8
- //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * Document registry for storing, retrieving, and filtering documentation.
5
5
  */
6
6
  export { DocumentRegistry, createDocumentRegistry, matchesVisibility, } from "./registry.core.js";
7
- //# sourceMappingURL=index.js.map
@@ -90,4 +90,3 @@ export declare class DocumentRegistry implements DocumentationProvider {
90
90
  * Creates a new document registry.
91
91
  */
92
92
  export declare function createDocumentRegistry(): DocumentRegistry;
93
- //# sourceMappingURL=registry.core.d.ts.map
@@ -117,4 +117,3 @@ export class DocumentRegistry {
117
117
  export function createDocumentRegistry() {
118
118
  return new DocumentRegistry();
119
119
  }
120
- //# sourceMappingURL=registry.core.js.map
@@ -6,4 +6,3 @@
6
6
  export { isValidDocumentId, normalizeDocumentId, documentIdFromPath, resolveDocumentLink, stripLinkDecorations, stripFencedCodeBlocks, extractTitleFromMarkdown, extractHeadings, } from "./utils.helper.js";
7
7
  export { stripMarkdown } from "./utils.markdownText.js";
8
8
  export { deepFreeze, deepFreezeClone } from "./utils.freeze.js";
9
- //# sourceMappingURL=index.d.ts.map
@@ -6,4 +6,3 @@
6
6
  export { isValidDocumentId, normalizeDocumentId, documentIdFromPath, resolveDocumentLink, stripLinkDecorations, stripFencedCodeBlocks, extractTitleFromMarkdown, extractHeadings, } from "./utils.helper.js";
7
7
  export { stripMarkdown } from "./utils.markdownText.js";
8
8
  export { deepFreeze, deepFreezeClone } from "./utils.freeze.js";
9
- //# sourceMappingURL=index.js.map
@@ -14,4 +14,3 @@ export declare function deepFreeze<T>(value: T, seen?: WeakSet<object>): T;
14
14
  * functions); those are kept by reference.
15
15
  */
16
16
  export declare function deepFreezeClone<T>(value: T): T;
17
- //# sourceMappingURL=utils.freeze.d.ts.map
@@ -74,4 +74,3 @@ function isPlainObject(value) {
74
74
  const proto = Object.getPrototypeOf(value);
75
75
  return proto === Object.prototype || proto === null;
76
76
  }
77
- //# sourceMappingURL=utils.freeze.js.map
@@ -50,4 +50,3 @@ export declare function extractHeadings(markdown: string): readonly {
50
50
  level: number;
51
51
  text: string;
52
52
  }[];
53
- //# sourceMappingURL=utils.helper.d.ts.map
@@ -121,4 +121,3 @@ function cleanHeadingText(text) {
121
121
  ? withoutHashes.trim()
122
122
  : trimmed;
123
123
  }
124
- //# sourceMappingURL=utils.helper.js.map
@@ -23,4 +23,3 @@ export declare function isSafeLinkHref(href: string): boolean;
23
23
  * as a character reference.
24
24
  */
25
25
  export declare function escapeHtmlText(value: string): string;
26
- //# sourceMappingURL=utils.href.d.ts.map
@@ -42,4 +42,3 @@ export function escapeHtmlText(value) {
42
42
  .replace(/</g, "&lt;")
43
43
  .replace(/>/g, "&gt;");
44
44
  }
45
- //# sourceMappingURL=utils.href.js.map
@@ -19,4 +19,3 @@ export declare function stripHtmlTags(text: string): string;
19
19
  * Strips markdown formatting to plain text.
20
20
  */
21
21
  export declare function stripMarkdown(markdown: string): string;
22
- //# sourceMappingURL=utils.markdownText.d.ts.map
@@ -85,4 +85,3 @@ export function stripMarkdown(markdown) {
85
85
  .replace(/\n{3,}/g, "\n\n")
86
86
  .trim();
87
87
  }
88
- //# sourceMappingURL=utils.markdownText.js.map
@@ -11,4 +11,3 @@ export { toValidationResult } from "./validator.types.js";
11
11
  export type { ValidationIssue, ValidationResult } from "./validator.types.js";
12
12
  export type { ValidateLinksOptions } from "./validatorLinks.core.js";
13
13
  export type { ValidateAllOptions } from "./validatorAll/validatorAll.core.js";
14
- //# sourceMappingURL=index.d.ts.map
@@ -8,4 +8,3 @@ export { validateNoDuplicateIds } from "./validatorDuplicates.core.js";
8
8
  export { validateLinks, DEFAULT_MAX_LINK_SCAN_LENGTH, } from "./validatorLinks.core.js";
9
9
  export { validateAll, validateNavigation } from "./validatorAll/index.js";
10
10
  export { toValidationResult } from "./validator.types.js";
11
- //# sourceMappingURL=index.js.map
@@ -25,4 +25,3 @@ export interface ValidationResult {
25
25
  * rule shared by every validator in this package.
26
26
  */
27
27
  export declare function toValidationResult(issues: readonly ValidationIssue[]): ValidationResult;
28
- //# sourceMappingURL=validator.types.d.ts.map
@@ -11,4 +11,3 @@ export function toValidationResult(issues) {
11
11
  issues,
12
12
  };
13
13
  }
14
- //# sourceMappingURL=validator.types.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { validateAll } from "./validatorAll.core.js";
7
7
  export { validateNavigation } from "./validatorNavigation.core.js";
8
- //# sourceMappingURL=index.d.ts.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { validateAll } from "./validatorAll.core.js";
7
7
  export { validateNavigation } from "./validatorNavigation.core.js";
8
- //# sourceMappingURL=index.js.map
@@ -11,9 +11,19 @@ export interface ValidateAllOptions extends ValidateLinksOptions {
11
11
  * appear in it as `NAVIGATION_ORPHAN_DOCUMENT` warnings. Default true.
12
12
  */
13
13
  readonly reportOrphans?: boolean;
14
+ /**
15
+ * Severity of a `NAVIGATION_ORPHAN_DOCUMENT` issue. Defaults to
16
+ * `"warning"`; pass `"error"` to fail validation when a document is
17
+ * unreachable from the navigation.
18
+ */
19
+ readonly orphanSeverity?: "error" | "warning";
14
20
  }
15
21
  /**
16
22
  * Validates all documents and optionally a navigation tree.
23
+ *
24
+ * `valid` is false only when an issue has severity `"error"`. Broken
25
+ * links and orphan documents are warnings by default, so a set with dead
26
+ * links still validates; set `brokenLinkSeverity` / `orphanSeverity` to
27
+ * `"error"` to fail on them.
17
28
  */
18
29
  export declare function validateAll(documents: readonly DocumentationDocument[], navigation?: readonly DocumentationNavigationItem[], options?: ValidateAllOptions): ValidationResult;
19
- //# sourceMappingURL=validatorAll.core.d.ts.map
@@ -9,6 +9,11 @@ import { validateLinks, } from "../validatorLinks.core.js";
9
9
  import { validateNavigation } from "./validatorNavigation.core.js";
10
10
  /**
11
11
  * Validates all documents and optionally a navigation tree.
12
+ *
13
+ * `valid` is false only when an issue has severity `"error"`. Broken
14
+ * links and orphan documents are warnings by default, so a set with dead
15
+ * links still validates; set `brokenLinkSeverity` / `orphanSeverity` to
16
+ * `"error"` to fail on them.
12
17
  */
13
18
  export function validateAll(documents, navigation, options = {}) {
14
19
  const allIssues = [];
@@ -29,7 +34,7 @@ export function validateAll(documents, navigation, options = {}) {
29
34
  for (const id of registeredIds) {
30
35
  if (!inNavigation.has(id)) {
31
36
  allIssues.push({
32
- severity: "warning",
37
+ severity: options.orphanSeverity ?? "warning",
33
38
  code: "NAVIGATION_ORPHAN_DOCUMENT",
34
39
  message: `Document "${id}" is not reachable from the navigation.`,
35
40
  documentId: id,
@@ -40,4 +45,3 @@ export function validateAll(documents, navigation, options = {}) {
40
45
  }
41
46
  return toValidationResult(allIssues);
42
47
  }
43
- //# sourceMappingURL=validatorAll.core.js.map
@@ -14,4 +14,3 @@ import { type ValidationResult } from "../validator.types.js";
14
14
  * for items with neither `documentId` nor `children`.
15
15
  */
16
16
  export declare function validateNavigation(items: readonly DocumentationNavigationItem[], registeredIds: ReadonlySet<string>): ValidationResult;
17
- //# sourceMappingURL=validatorNavigation.core.d.ts.map
@@ -83,4 +83,3 @@ export function validateNavigation(items, registeredIds) {
83
83
  walk(items, []);
84
84
  return toValidationResult(issues);
85
85
  }
86
- //# sourceMappingURL=validatorNavigation.core.js.map
@@ -8,4 +8,3 @@ import { type ValidationResult } from "./validator.types.js";
8
8
  * shape (including structured nodes), and the metadata enums.
9
9
  */
10
10
  export declare function validateDocument(document: DocumentationDocument): ValidationResult;
11
- //# sourceMappingURL=validatorDocument.core.d.ts.map
@@ -224,4 +224,3 @@ function describeNodeProblem(node) {
224
224
  return undefined;
225
225
  }
226
226
  }
227
- //# sourceMappingURL=validatorDocument.core.js.map
@@ -7,4 +7,3 @@ import { type ValidationResult } from "./validator.types.js";
7
7
  * Validates no duplicate IDs exist in a collection of documents.
8
8
  */
9
9
  export declare function validateNoDuplicateIds(documents: readonly DocumentationDocument[]): ValidationResult;
10
- //# sourceMappingURL=validatorDuplicates.core.d.ts.map
@@ -23,4 +23,3 @@ export function validateNoDuplicateIds(documents) {
23
23
  }
24
24
  return toValidationResult(issues);
25
25
  }
26
- //# sourceMappingURL=validatorDuplicates.core.js.map
@@ -13,18 +13,28 @@ export interface ValidateLinksOptions {
13
13
  * Defaults to {@link DEFAULT_MAX_LINK_SCAN_LENGTH}.
14
14
  */
15
15
  readonly maxContentLength?: number;
16
+ /**
17
+ * Severity of a `BROKEN_LINK` issue (a relative link to an unregistered
18
+ * document). Defaults to `"warning"`, which never makes a result invalid;
19
+ * pass `"error"` to fail validation on dead links.
20
+ */
21
+ readonly brokenLinkSeverity?: "error" | "warning";
16
22
  }
17
23
  /**
18
24
  * Validates internal links in a document's markdown content and in
19
25
  * structured `link` nodes.
20
26
  *
21
- * A target whose scheme is not allowed (http, https, mailto, tel, ftp, ftps).(`javascript:`,
22
- * `data:`, `vbscript:` …) is reported as an `UNSAFE_LINK` error.
23
- * Skipped (never reported): images, anchors (`#…`), other targets with a
24
- * URL scheme or `//` prefix, links inside fenced or inline code.
27
+ * Every markdown link form is scanned: inline `[text](target)` (with
28
+ * balanced parentheses, `<…>` targets and titles), reference definitions
29
+ * `[label]: target`, raw HTML `href="…"` attributes and `<scheme:…>`
30
+ * autolinks. A target whose scheme is not on the allow-list (http, https,
31
+ * mailto, tel, ftp, ftps) — `javascript:`, `data:`, `vbscript:` … — is
32
+ * reported as an `UNSAFE_LINK` error.
33
+ * Skipped (never reported): images, anchors (`#…`), other targets with an
34
+ * allowed URL scheme or `//` prefix, links inside fenced or inline code.
25
35
  * Relative targets (`./x`, `../x`, `/x`) are resolved against the
26
36
  * document ID with `resolveDocumentLink`; bare targets are looked up
27
- * both as-is and resolved.
37
+ * both as-is and resolved. An unregistered target is a `BROKEN_LINK`
38
+ * whose severity is `options.brokenLinkSeverity` (default `"warning"`).
28
39
  */
29
40
  export declare function validateLinks(document: DocumentationDocument, registeredIds: ReadonlySet<string>, options?: ValidateLinksOptions): ValidationResult;
30
- //# sourceMappingURL=validatorLinks.core.d.ts.map
@@ -3,42 +3,37 @@
3
3
  */
4
4
  import { resolveDocumentLink, stripFencedCodeBlocks, stripLinkDecorations, } from "../utils/utils.helper.js";
5
5
  import { isSafeLinkHref } from "../utils/utils.href.js";
6
+ import { extractLinkTargets, stripInlineCode, } from "./validatorLinks.extract.js";
6
7
  import { toValidationResult, } from "./validator.types.js";
7
8
  /** Default maximum markdown length that is scanned for links. */
8
9
  export const DEFAULT_MAX_LINK_SCAN_LENGTH = 100_000;
9
- /**
10
- * Matches `[text](target)` and `![alt](target)` links. Group 1 is the
11
- * optional image bang, group 2 the target (optionally `<…>` wrapped
12
- * and followed by a `"title"`).
13
- *
14
- * Linear by construction: link text cannot contain `[`/`]` and a target
15
- * cannot contain `(`/`)`, so each start scans only to the next opener; the
16
- * target is non-empty, so leading whitespace is never split between two
17
- * quantifiers; and whitespace is `[ \t]` (the old `\s` ran across lines).
18
- * The old pattern took ~30 s on a 99 KB run of `[`.
19
- */
20
- const LINK_PATTERN = /(!?)\[[^[\]\n]*\]\((?:[ \t]*(<[^<>\n]*>|[^()\s<>]+)(?:[ \t]+(?:"[^"\n]*"|'[^'\n]*'))?)?[ \t]*\)/g;
21
10
  /** `scheme:` (mailto:, ftp:, http:) or protocol-relative `//`. */
22
11
  const EXTERNAL_PATTERN = /^([a-zA-Z][a-zA-Z0-9+.-]*:|\/\/)/;
23
12
  /**
24
13
  * Validates internal links in a document's markdown content and in
25
14
  * structured `link` nodes.
26
15
  *
27
- * A target whose scheme is not allowed (http, https, mailto, tel, ftp, ftps).(`javascript:`,
28
- * `data:`, `vbscript:` …) is reported as an `UNSAFE_LINK` error.
29
- * Skipped (never reported): images, anchors (`#…`), other targets with a
30
- * URL scheme or `//` prefix, links inside fenced or inline code.
16
+ * Every markdown link form is scanned: inline `[text](target)` (with
17
+ * balanced parentheses, `<…>` targets and titles), reference definitions
18
+ * `[label]: target`, raw HTML `href="…"` attributes and `<scheme:…>`
19
+ * autolinks. A target whose scheme is not on the allow-list (http, https,
20
+ * mailto, tel, ftp, ftps) — `javascript:`, `data:`, `vbscript:` … — is
21
+ * reported as an `UNSAFE_LINK` error.
22
+ * Skipped (never reported): images, anchors (`#…`), other targets with an
23
+ * allowed URL scheme or `//` prefix, links inside fenced or inline code.
31
24
  * Relative targets (`./x`, `../x`, `/x`) are resolved against the
32
25
  * document ID with `resolveDocumentLink`; bare targets are looked up
33
- * both as-is and resolved.
26
+ * both as-is and resolved. An unregistered target is a `BROKEN_LINK`
27
+ * whose severity is `options.brokenLinkSeverity` (default `"warning"`).
34
28
  */
35
29
  export function validateLinks(document, registeredIds, options = {}) {
36
30
  const issues = [];
37
31
  const content = document.content;
32
+ const brokenSeverity = options.brokenLinkSeverity ?? "warning";
38
33
  if (content.type === "structured") {
39
34
  for (const node of content.nodes) {
40
35
  if (node.type === "link") {
41
- checkTarget(node.href, document, registeredIds, issues);
36
+ checkTarget(node.href, document, registeredIds, issues, brokenSeverity);
42
37
  }
43
38
  }
44
39
  return toValidationResult(issues);
@@ -57,19 +52,14 @@ export function validateLinks(document, registeredIds, options = {}) {
57
52
  return toValidationResult(issues);
58
53
  }
59
54
  const source = stripInlineCode(stripFencedCodeBlocks(content.value));
60
- for (const match of source.matchAll(LINK_PATTERN)) {
61
- const isImage = match[1] === "!";
62
- let target = match[2] ?? "";
63
- if (isImage)
55
+ for (const link of extractLinkTargets(source)) {
56
+ if (link.isImage)
64
57
  continue;
65
- if (target.startsWith("<") && target.endsWith(">")) {
66
- target = target.slice(1, -1);
67
- }
68
- checkTarget(target, document, registeredIds, issues);
58
+ checkTarget(link.target, document, registeredIds, issues, brokenSeverity);
69
59
  }
70
60
  return toValidationResult(issues);
71
61
  }
72
- function checkTarget(rawTarget, document, registeredIds, issues) {
62
+ function checkTarget(rawTarget, document, registeredIds, issues, brokenSeverity) {
73
63
  const target = rawTarget.trim();
74
64
  if (target === "" || target.startsWith("#"))
75
65
  return;
@@ -97,18 +87,9 @@ function checkTarget(rawTarget, document, registeredIds, issues) {
97
87
  return;
98
88
  }
99
89
  issues.push({
100
- severity: "warning",
90
+ severity: brokenSeverity,
101
91
  code: "BROKEN_LINK",
102
92
  message: `Document "${document.id}" links to "${rawTarget}" which is not registered.`,
103
93
  documentId: document.id,
104
94
  });
105
95
  }
106
- /**
107
- * Blanks out inline code spans so links inside them are ignored. The
108
- * lookarounds anchor each attempt at a whole backtick run, so a long run is
109
- * not re-scanned from every position inside it.
110
- */
111
- function stripInlineCode(markdown) {
112
- return markdown.replace(/(?<!`)(`+)(?![`\n])[\s\S]*?(?<!`)\1(?!`)/g, (m) => " ".repeat(m.length));
113
- }
114
- //# sourceMappingURL=validatorLinks.core.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Extracts link targets from markdown in every form a renderer turns into
3
+ * a live link: inline `[text](target)`, reference definitions
4
+ * `[label]: target`, raw HTML `href` attributes and `<scheme:…>` autolinks.
5
+ *
6
+ * Every scanner is linear in the input length, so hostile documents
7
+ * (a 99 KB run of `[`, `(` or `<`) are scanned in milliseconds.
8
+ *
9
+ * @module validator/validatorLinks.extract
10
+ */
11
+ /** A link target found in markdown. */
12
+ export interface ExtractedLink {
13
+ /** The raw target, without `<…>` wrapping. */
14
+ readonly target: string;
15
+ /** True for `![alt](target)` images, which are never validated. */
16
+ readonly isImage: boolean;
17
+ }
18
+ /**
19
+ * Returns every distinct link target in `source`, in order of first
20
+ * appearance. `source` must already have fenced code blocks and inline
21
+ * code spans removed. A target found by two scanners (`[x](<t>)` is both
22
+ * an inline link and an autolink) is returned once.
23
+ */
24
+ export declare function extractLinkTargets(source: string): readonly ExtractedLink[];
25
+ /**
26
+ * Blanks out inline code spans so links inside them are ignored. The
27
+ * lookarounds anchor each attempt at a whole backtick run, so a long run is
28
+ * not re-scanned from every position inside it.
29
+ */
30
+ export declare function stripInlineCode(markdown: string): string;