-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.ts
121 lines (102 loc) · 3.75 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import assert from 'node:assert';
import { hide, visitAndReveal } from 'pkgverse/mdast-util-hidden/src/index';
import remarkInlineLinks from 'remark-inline-links';
import remarkReferenceLinks from 'remark-reference-links';
import { removePosition } from 'unist-util-remove-position';
import { SKIP, visit } from 'unist-util-visit';
import type { Definition, Root } from 'mdast';
import type { Plugin } from 'unified';
const scopeSymbol: unique symbol = Symbol('owned-by: remark-renumber-references');
/**
* Options type for the remark-renumber-references plugin.
*/
export type Options = {
/**
* If `true`, alphanumeric definition ids (i.e. any id that cannot be parsed
* into an integer) will be spared during the renumbering. If `false`, _all_
* definition ids will be deleted and recreated starting from `[1]`.
*
* @default true
*/
preserveAlphanumericDefinitions?: boolean;
};
/**
* A remark plugin that takes a Root node as input and returns the same node
* with all reference-style link nodes and their definition nodes renumbered in
* ascending order starting from `[1]`.
*
* Links are assigned a numeric reference id as they are encountered.
*/
const remarkRenumberReferences: Plugin<[options: Options] | void[], Root> = function (
{ preserveAlphanumericDefinitions = true } = {} as Options,
..._ignored
) {
return async (tree) => {
const definitions: Definition[] = [];
if (preserveAlphanumericDefinitions) {
hideAlphanumericReferenceLinks();
}
await convertNonHiddenReferenceLinksToInlineLinks();
if (preserveAlphanumericDefinitions) {
revealHiddenReferenceLinks();
appendRevealedReferenceLinkDefinitionsToDocument();
}
await convertInlineLinksToNumericReferenceLinks();
function hideAlphanumericReferenceLinks() {
visit(
tree,
['imageReference', 'linkReference', 'definition'],
(node, index, parent) => {
assert(
node.type === 'imageReference' ||
node.type === 'linkReference' ||
node.type === 'definition',
`unexpected node type ${node.type}`
);
assert(index !== undefined, 'index is missing');
assert(parent !== undefined, 'parent is missing');
if (isAlphanumeric(node.identifier)) {
if (node.type === 'definition') {
removePosition(node);
definitions.push(node);
} else {
hide({ nodes: [node], index, parent });
parent.children[index].data = {
...parent.children[index].data,
scope: scopeSymbol
};
return [SKIP, index + 1];
}
}
}
);
}
async function convertNonHiddenReferenceLinksToInlineLinks() {
const transformer = remarkInlineLinks();
assert(transformer, 'remark-inline-links did not return a transformer');
await transformer(tree);
}
function revealHiddenReferenceLinks() {
// ? Ensure that we're only revealing the hidden nodes that belong to us
visitAndReveal({
tree,
visitor: (node) =>
(node.data as Record<string, unknown>)?.scope === scopeSymbol
? undefined
: false
});
}
function appendRevealedReferenceLinkDefinitionsToDocument() {
tree.children.push(...definitions);
}
async function convertInlineLinksToNumericReferenceLinks() {
const transformer = remarkReferenceLinks();
assert(transformer, 'remark-reference-links did not return a transformer');
await transformer(tree);
}
function isAlphanumeric(identifier: string) {
return identifier && !/^\p{Decimal_Number}+$/u.test(identifier);
}
};
};
export default remarkRenumberReferences;