@doc-kit/core 1.0.1 → 1.2.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/CHANGELOG.md +18 -0
- package/README.md +2 -1
- package/package.json +11 -6
- package/src/generators/index.mjs +3 -0
- package/src/generators/json/README.md +151 -0
- package/src/generators/json/constants.mjs +57 -0
- package/src/generators/json/generate.mjs +65 -0
- package/src/generators/json/generated/schema.d.ts +351 -0
- package/src/generators/json/index.mjs +27 -0
- package/src/generators/json/schema.json +529 -0
- package/src/generators/json/types.d.ts +29 -0
- package/src/generators/json/utils/document.mjs +58 -0
- package/src/generators/json/utils/entry.mjs +87 -0
- package/src/generators/json/utils/lifecycle.mjs +61 -0
- package/src/generators/json/utils/markdown.mjs +57 -0
- package/src/generators/json/utils/node.mjs +153 -0
- package/src/generators/json/utils/schema.mjs +15 -0
- package/src/generators/json/utils/signature.mjs +185 -0
- package/src/generators/json/utils/types.mjs +28 -0
- package/src/generators/json-all/README.md +31 -0
- package/src/generators/json-all/constants.mjs +5 -0
- package/src/generators/json-all/generate.mjs +35 -0
- package/src/generators/json-all/index.mjs +25 -0
- package/src/generators/json-all/schema.json +22 -0
- package/src/generators/json-all/types.d.ts +20 -0
- package/src/generators/json-simple/generate.mjs +2 -7
- package/src/generators/loader.mjs +8 -3
- package/src/generators/metadata/maps/mdn.json +9 -2
- package/src/generators/metadata/types.d.ts +24 -0
- package/src/generators/pipeline.mjs +169 -0
- package/src/generators/types.d.ts +20 -0
- package/src/generators.mjs +19 -12
- package/src/logger/logger.mjs +8 -0
- package/src/parsers/markdown.mjs +5 -6
- package/src/threading/chunk-worker.mjs +7 -0
- package/src/threading/index.mjs +1 -0
- package/src/utils/configuration/constants.mjs +13 -0
- package/src/utils/configuration/index.mjs +7 -2
- package/src/utils/file.mjs +10 -0
- package/src/utils/hierarchy.mjs +50 -0
- package/src/utils/overloads.mjs +54 -0
- package/src/utils/queries/index.mjs +2 -0
- package/src/utils/queries/utils.mjs +15 -0
- package/src/utils/remark-shiki.mjs +29 -0
- package/src/utils/remark.mjs +27 -49
- package/src/utils/signature/constants.mjs +3 -0
- package/src/utils/signature/extractListItem.mjs +140 -0
- package/src/utils/stability.mjs +24 -0
- package/src/utils/type-annotations/hast.mjs +0 -58
- package/src/utils/type-annotations/highlighted.mjs +64 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Heading types that document a callable and therefore may appear several times
|
|
4
|
+
// in a row as overloaded signatures of the same function.
|
|
5
|
+
const OVERLOADABLE_TYPES = new Set(['method', 'ctor', 'classMethod']);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Two headings document the same function (i.e. are overloads of one another)
|
|
9
|
+
* when they sit at the same depth and share the same resolved name and type.
|
|
10
|
+
*
|
|
11
|
+
* @param {import('../generators/metadata/types').HeadingNode} a
|
|
12
|
+
* @param {import('../generators/metadata/types').HeadingNode} b
|
|
13
|
+
*/
|
|
14
|
+
const isSameFunction = (a, b) =>
|
|
15
|
+
a.depth === b.depth &&
|
|
16
|
+
a.data.type === b.data.type &&
|
|
17
|
+
a.data.name === b.data.name;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Flags overloaded function headings so consumers can present a single entry
|
|
21
|
+
* per function.
|
|
22
|
+
*
|
|
23
|
+
* Node.js documents each overload of a function as its own heading (e.g. the
|
|
24
|
+
* five `new Buffer(...)` signatures). This marks the 2nd..nth heading of each
|
|
25
|
+
* such run with `isOverload`, and with `overloadOf` pointing at the slug of
|
|
26
|
+
* the run's first heading. The first (most stable) heading is left as-is.
|
|
27
|
+
*
|
|
28
|
+
* @param {Array<import('../generators/metadata/types').MetadataEntry>} entries - Entries, in document order.
|
|
29
|
+
* @returns {Array<import('../generators/metadata/types').MetadataEntry>} The same entries (mutated).
|
|
30
|
+
*/
|
|
31
|
+
export const annotateOverloads = entries => {
|
|
32
|
+
for (let i = 0; i < entries.length; i++) {
|
|
33
|
+
const { heading } = entries[i];
|
|
34
|
+
|
|
35
|
+
if (!OVERLOADABLE_TYPES.has(heading.data.type)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Flag each following heading that overloads the same function.
|
|
40
|
+
let end = i + 1;
|
|
41
|
+
while (
|
|
42
|
+
end < entries.length &&
|
|
43
|
+
isSameFunction(entries[end].heading, heading)
|
|
44
|
+
) {
|
|
45
|
+
entries[end].heading.data.isOverload = true;
|
|
46
|
+
entries[end].heading.data.overloadOf = heading.data.slug;
|
|
47
|
+
end++;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
i = end - 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return entries;
|
|
54
|
+
};
|
|
@@ -4,6 +4,8 @@ import { transformNodesToString } from '#utils/unist.mjs';
|
|
|
4
4
|
|
|
5
5
|
import { isTypedListItem, isTypedList } from './utils.mjs';
|
|
6
6
|
|
|
7
|
+
export { splitTypedItems } from './utils.mjs';
|
|
8
|
+
|
|
7
9
|
// This defines the actual REGEX Queries
|
|
8
10
|
export const QUERIES = {
|
|
9
11
|
// Fixes the references to Markdown pages into the API documentation
|
|
@@ -67,3 +67,18 @@ export const isTypedList = list => {
|
|
|
67
67
|
|
|
68
68
|
return getTypedConfidence(list.children?.[0]?.children?.[0]?.children?.[0]);
|
|
69
69
|
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Splits a typed list's items into the leading typed ones and the rest:
|
|
73
|
+
* prose bullets that happen to share the list in the source markdown.
|
|
74
|
+
*
|
|
75
|
+
* @param {import('@types/mdast').List} list
|
|
76
|
+
* @returns {{ typed: Array<import('@types/mdast').ListItem>, rest: Array<import('@types/mdast').ListItem> }}
|
|
77
|
+
*/
|
|
78
|
+
export const splitTypedItems = ({ children }) => {
|
|
79
|
+
const end = children.findIndex(item => !isTypedListItem(item));
|
|
80
|
+
|
|
81
|
+
return end === -1
|
|
82
|
+
? { typed: children, rest: [] }
|
|
83
|
+
: { typed: children.slice(0, end), rest: children.slice(end) };
|
|
84
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import rehypeStringify from 'rehype-stringify';
|
|
4
|
+
import remarkParse from 'remark-parse';
|
|
5
|
+
import remarkRehype from 'remark-rehype';
|
|
6
|
+
import { unified } from 'unified';
|
|
7
|
+
|
|
8
|
+
import syntaxHighlighter from './highlighter.mjs';
|
|
9
|
+
import { lazy } from './misc.mjs';
|
|
10
|
+
import { rehypeOptions } from './remark.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves an instance of Remark configured to output stringified HTML code
|
|
14
|
+
* including parsing Code Boxes with syntax highlighting.
|
|
15
|
+
*
|
|
16
|
+
* This lives apart from `./remark.mjs` because importing it loads Shiki (every
|
|
17
|
+
* grammar plus the regex engine, ~250MB per process). Only the generators that
|
|
18
|
+
* highlight code — `legacy-html` here — should pay for that.
|
|
19
|
+
*/
|
|
20
|
+
export const getRemarkRehypeWithShiki = lazy(() =>
|
|
21
|
+
unified()
|
|
22
|
+
.use(remarkParse)
|
|
23
|
+
// legacy-html gets the minimal (unhighlighted) type rendering
|
|
24
|
+
.use(remarkRehype, rehypeOptions)
|
|
25
|
+
// This is a custom ad-hoc within the Shiki Rehype plugin, used to highlight code
|
|
26
|
+
// and transform them into HAST nodes
|
|
27
|
+
.use(syntaxHighlighter)
|
|
28
|
+
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
29
|
+
);
|
package/src/utils/remark.mjs
CHANGED
|
@@ -8,13 +8,13 @@ import remarkRehype from 'remark-rehype';
|
|
|
8
8
|
import remarkStringify from 'remark-stringify';
|
|
9
9
|
import { unified } from 'unified';
|
|
10
10
|
|
|
11
|
-
import syntaxHighlighter from './highlighter.mjs';
|
|
12
11
|
import { lazy } from './misc.mjs';
|
|
13
12
|
import { typeAnnotationToHast } from './type-annotations/hast.mjs';
|
|
14
13
|
import remarkTypeAnnotations from './type-annotations/remark.mjs';
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// Nothing in this module loads Shiki: the `ast` and `metadata` stages (and
|
|
16
|
+
// every worker that runs them) import it, and none of them highlight code.
|
|
17
|
+
// The highlighting pipeline lives in `./remark-shiki.mjs`.
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Renders an MDX JSX element as just its children, so the surrounding prose
|
|
@@ -30,15 +30,28 @@ const mdxElementToChildren = (state, node) => state.all(node);
|
|
|
30
30
|
*/
|
|
31
31
|
const dropNode = () => undefined;
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The `remark-rehype` options shared by the HTML-string pipelines.
|
|
35
|
+
*
|
|
36
|
+
* Existing HTML nodes pass through untouched (they were created during the
|
|
37
|
+
* rehype process), and dangerous HTML is allowed since the Markdown sources
|
|
38
|
+
* are trusted. The MDX node types cannot be rendered to an HTML string (that
|
|
39
|
+
* is the React generators' job): JSX elements degrade to their children so the
|
|
40
|
+
* surrounding prose still renders, and expressions/ESM are dropped.
|
|
41
|
+
*
|
|
42
|
+
* @type {import('remark-rehype').Options}
|
|
43
|
+
*/
|
|
44
|
+
export const rehypeOptions = {
|
|
45
|
+
allowDangerousHtml: true,
|
|
46
|
+
passThrough: ['element'],
|
|
47
|
+
handlers: {
|
|
48
|
+
typeAnnotation: typeAnnotationToHast,
|
|
49
|
+
mdxJsxTextElement: mdxElementToChildren,
|
|
50
|
+
mdxJsxFlowElement: mdxElementToChildren,
|
|
51
|
+
mdxFlowExpression: dropNode,
|
|
52
|
+
mdxTextExpression: dropNode,
|
|
53
|
+
mdxjsEsm: dropNode,
|
|
54
|
+
},
|
|
42
55
|
};
|
|
43
56
|
|
|
44
57
|
/**
|
|
@@ -63,7 +76,7 @@ export const getRemark = lazy(() =>
|
|
|
63
76
|
* `{` for type annotations that MDX would otherwise try to parse.
|
|
64
77
|
*/
|
|
65
78
|
export const getRemarkMdx = lazy(() =>
|
|
66
|
-
unified().use(remarkParse).use(remarkMdx).use(remarkGfm)
|
|
79
|
+
unified().use(remarkParse).use(remarkMdx).use(remarkGfm).use(remarkStringify)
|
|
67
80
|
);
|
|
68
81
|
|
|
69
82
|
/**
|
|
@@ -72,41 +85,6 @@ export const getRemarkMdx = lazy(() =>
|
|
|
72
85
|
export const getRemarkRehype = lazy(() =>
|
|
73
86
|
unified()
|
|
74
87
|
.use(remarkParse)
|
|
75
|
-
|
|
76
|
-
// as these are nodes we manually created during the rehype process
|
|
77
|
-
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
78
|
-
// and we trust the sources of the Markdown files
|
|
79
|
-
.use(remarkRehype, {
|
|
80
|
-
allowDangerousHtml: true,
|
|
81
|
-
passThrough,
|
|
82
|
-
handlers: { typeAnnotation: typeAnnotationToHast, ...mdxToHastHandlers },
|
|
83
|
-
})
|
|
84
|
-
// We allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
85
|
-
// and we trust the sources of the Markdown files
|
|
86
|
-
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Retrieves an instance of Remark configured to output stringified HTML code
|
|
91
|
-
* including parsing Code Boxes with syntax highlighting
|
|
92
|
-
*/
|
|
93
|
-
export const getRemarkRehypeWithShiki = lazy(() =>
|
|
94
|
-
unified()
|
|
95
|
-
.use(remarkParse)
|
|
96
|
-
// We make Rehype ignore existing HTML nodes (just the node itself, not its children)
|
|
97
|
-
// as these are nodes we manually created during the rehype process
|
|
98
|
-
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
99
|
-
// and we trust the sources of the Markdown files
|
|
100
|
-
.use(remarkRehype, {
|
|
101
|
-
allowDangerousHtml: true,
|
|
102
|
-
passThrough,
|
|
103
|
-
// legacy-html gets the minimal (unhighlighted) type rendering
|
|
104
|
-
handlers: { typeAnnotation: typeAnnotationToHast, ...mdxToHastHandlers },
|
|
105
|
-
})
|
|
106
|
-
// This is a custom ad-hoc within the Shiki Rehype plugin, used to highlight code
|
|
107
|
-
// and transform them into HAST nodes
|
|
108
|
-
.use(syntaxHighlighter)
|
|
109
|
-
// We allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
110
|
-
// and we trust the sources of the Markdown files
|
|
88
|
+
.use(remarkRehype, rehypeOptions)
|
|
111
89
|
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
112
90
|
);
|
|
@@ -4,6 +4,9 @@ export const NAME_EXPRESSION = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
|
|
|
4
4
|
// Checks if there's a leading hyphen
|
|
5
5
|
export const LEADING_HYPHEN = /^-\s*/;
|
|
6
6
|
|
|
7
|
+
// The separators between a parameter's type and its description
|
|
8
|
+
export const TRIMMABLE_PADDING_REGEX = /^[\s:-]+/;
|
|
9
|
+
|
|
7
10
|
// Grabs the default value if present
|
|
8
11
|
export const DEFAULT_EXPRESSION = /\s*\*\*Default:\*\*\s*([^]+)$/i;
|
|
9
12
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { QUERIES, UNIST } from '#utils/queries/index.mjs';
|
|
4
|
+
import { transformNodesToString } from '#utils/unist.mjs';
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_EXPRESSION, TRIMMABLE_PADDING_REGEX } from './constants.mjs';
|
|
7
|
+
|
|
8
|
+
// The text of the `**Default:**` marker introducing a default value
|
|
9
|
+
const DEFAULT_MARKER = /^default:$/i;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A typed list item taken apart into its parts, none of them stringified, so
|
|
13
|
+
* each consumer can render the description its own way.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {object} ExtractedListItem
|
|
16
|
+
* @property {string | undefined} name The parameter name (the leading code span), if any
|
|
17
|
+
* @property {'Returns' | 'Extends' | 'Type' | undefined} prefix The special prefix the item starts with, if any
|
|
18
|
+
* @property {import('mdast').Node | undefined} annotation The leading `typeAnnotation` node, if any
|
|
19
|
+
* @property {Array<import('mdast').PhrasingContent>} text The rest of the first paragraph: the description,
|
|
20
|
+
* with leading separators trimmed and any `**Default:**` marker still in place
|
|
21
|
+
* @property {Array<import('mdast').BlockContent>} blocks The item's further block content, excluding its nested typed list
|
|
22
|
+
* @property {string | undefined} default The `**Default:**` value as written (code span included), if any
|
|
23
|
+
* @property {Array<import('mdast').ListItem>} items The items of the nested typed list, if any
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Replaces the first node's text, or drops the node when nothing is left.
|
|
28
|
+
*
|
|
29
|
+
* @param {Array<import('mdast').PhrasingContent>} nodes
|
|
30
|
+
* @param {string} value The new text of the first node
|
|
31
|
+
*/
|
|
32
|
+
const replaceLeadingText = (nodes, value) => {
|
|
33
|
+
if (value.trim()) {
|
|
34
|
+
nodes[0] = { ...nodes[0], value };
|
|
35
|
+
} else {
|
|
36
|
+
nodes.shift();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Takes a typed list item apart: `` `name` {Type} Description. **Default:** `value` ``,
|
|
42
|
+
* or one of the `Returns:`, `Extends:` and `Type:` forms.
|
|
43
|
+
*
|
|
44
|
+
* @param {import('mdast').ListItem} item
|
|
45
|
+
* @returns {ExtractedListItem}
|
|
46
|
+
*/
|
|
47
|
+
export const extractListItem = item => {
|
|
48
|
+
const [paragraph, ...rest] = item.children ?? [];
|
|
49
|
+
const text = [...(paragraph?.children ?? [])];
|
|
50
|
+
|
|
51
|
+
const result = {
|
|
52
|
+
name: undefined,
|
|
53
|
+
prefix: undefined,
|
|
54
|
+
annotation: undefined,
|
|
55
|
+
text,
|
|
56
|
+
blocks: [],
|
|
57
|
+
default: undefined,
|
|
58
|
+
items: [],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const [first] = text;
|
|
62
|
+
|
|
63
|
+
if (first?.type === 'inlineCode') {
|
|
64
|
+
result.name = first.value.trimEnd();
|
|
65
|
+
text.shift();
|
|
66
|
+
} else if (first?.type === 'text') {
|
|
67
|
+
const match = first.value.match(QUERIES.typedListStarters);
|
|
68
|
+
|
|
69
|
+
if (match) {
|
|
70
|
+
result.prefix = match[1];
|
|
71
|
+
replaceLeadingText(text, first.value.slice(match[0].length));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The whitespace between the name and the type
|
|
76
|
+
if (text[0]?.type === 'text' && !text[0].value.trim()) {
|
|
77
|
+
text.shift();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (text[0]?.type === 'typeAnnotation') {
|
|
81
|
+
result.annotation = text.shift();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Leading separators: `- description`, `: description`
|
|
85
|
+
if (text[0]?.type === 'text') {
|
|
86
|
+
replaceLeadingText(
|
|
87
|
+
text,
|
|
88
|
+
text[0].value.replace(TRIMMABLE_PADDING_REGEX, '')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
result.default = DEFAULT_EXPRESSION.exec(transformNodesToString(text))?.[1]
|
|
93
|
+
.trim()
|
|
94
|
+
.replace(/\.$/, '');
|
|
95
|
+
|
|
96
|
+
for (const node of rest) {
|
|
97
|
+
if (result.items.length === 0 && UNIST.isLooselyTypedList(node)) {
|
|
98
|
+
result.items = node.children;
|
|
99
|
+
} else {
|
|
100
|
+
result.blocks.push(node);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Drops the `**Default:**` marker, and everything after it, from an item's
|
|
109
|
+
* description.
|
|
110
|
+
*
|
|
111
|
+
* @param {Array<import('mdast').PhrasingContent>} nodes The item's description
|
|
112
|
+
* @returns {Array<import('mdast').PhrasingContent>} The description without its default value
|
|
113
|
+
*/
|
|
114
|
+
export const removeDefault = nodes => {
|
|
115
|
+
const index = nodes.findIndex(
|
|
116
|
+
node =>
|
|
117
|
+
node.type === 'strong' &&
|
|
118
|
+
DEFAULT_MARKER.test(transformNodesToString(node.children).trim())
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (index === -1) {
|
|
122
|
+
return nodes;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const kept = nodes.slice(0, index);
|
|
126
|
+
const last = kept.at(-1);
|
|
127
|
+
|
|
128
|
+
// The whitespace that separated the description from its default
|
|
129
|
+
if (last?.type === 'text') {
|
|
130
|
+
const value = last.value.trimEnd();
|
|
131
|
+
|
|
132
|
+
kept.pop();
|
|
133
|
+
|
|
134
|
+
if (value) {
|
|
135
|
+
kept.push({ ...last, value });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return kept;
|
|
140
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { slice } from 'mdast-util-slice-markdown';
|
|
4
|
+
import { toString } from 'mdast-util-to-string';
|
|
5
|
+
|
|
6
|
+
import { QUERIES } from './queries/index.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The content of a stability blockquote without its `Stability: N - `
|
|
10
|
+
* prefix, as a copy of the blockquote. The prefix may have been turned into a
|
|
11
|
+
* link by the AST stage; the separator goes with it.
|
|
12
|
+
*
|
|
13
|
+
* @param {import('../generators/metadata/types').StabilityNode} node
|
|
14
|
+
* @returns {import('mdast').Blockquote | null} The copy, or `null` when nothing follows the prefix
|
|
15
|
+
*/
|
|
16
|
+
export const removeStabilityPrefix = node => {
|
|
17
|
+
const text = toString(node.children[0]);
|
|
18
|
+
const match = QUERIES.stabilityIndex.exec(text);
|
|
19
|
+
const start = match ? text.length - match[2].length : 0;
|
|
20
|
+
|
|
21
|
+
return slice(node, start, undefined, {
|
|
22
|
+
textHandling: { boundaries: 'preserve' },
|
|
23
|
+
}).node;
|
|
24
|
+
};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
import { highlighter } from '#utils/highlighter.mjs';
|
|
4
|
-
|
|
5
|
-
const [lightTheme, darkTheme] = highlighter.shiki.getLoadedThemes();
|
|
6
|
-
|
|
7
3
|
/**
|
|
8
4
|
* Slices a type's text by its resolved link ranges into hast children —
|
|
9
5
|
* plain text segments interleaved with `<a class="type-link">` anchors.
|
|
@@ -59,57 +55,3 @@ export const typeAnnotationToHast = (state, node) => {
|
|
|
59
55
|
|
|
60
56
|
return state.applyData(node, result);
|
|
61
57
|
};
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Syntax-highlighted mdast→hast handler for `typeAnnotation` nodes, used by
|
|
65
|
-
* the web (JSX) pipeline. The whole type is highlighted as one inline
|
|
66
|
-
* fragment, and each resolved identifier's exact character range is wrapped
|
|
67
|
-
* in an `<a>` via Shiki decorations. Values that are not TypeScript (display
|
|
68
|
-
* names such as `HTTP/2 Headers Object`) are highlighted as plain text, so
|
|
69
|
-
* their prose is not coloured as operators and numeric literals.
|
|
70
|
-
*
|
|
71
|
-
* Falls back to the minimal handler when the type failed to parse or nothing
|
|
72
|
-
* resolved (no point paying for highlighting then).
|
|
73
|
-
*
|
|
74
|
-
* @param {import('mdast-util-to-hast').State} state
|
|
75
|
-
* @param {import('mdast').Node} node
|
|
76
|
-
* @returns {import('hast').Element}
|
|
77
|
-
*/
|
|
78
|
-
export const typeAnnotationToHighlightedHast = (state, node) => {
|
|
79
|
-
const links = node.data?.links ?? [];
|
|
80
|
-
|
|
81
|
-
if (node.data?.parseError || links.length === 0) {
|
|
82
|
-
return typeAnnotationToHast(state, node);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const root = highlighter.shiki.codeToHast(node.value, {
|
|
86
|
-
lang: node.data?.typescript ? 'typescript' : 'text',
|
|
87
|
-
themes: { light: lightTheme, dark: darkTheme },
|
|
88
|
-
decorations: links.map(({ start, end, href }) => ({
|
|
89
|
-
start,
|
|
90
|
-
end,
|
|
91
|
-
tagName: 'a',
|
|
92
|
-
properties: { href, class: 'type-link' },
|
|
93
|
-
alwaysWrap: true,
|
|
94
|
-
})),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// codeToHast wraps the highlighted line in <pre><code>; re-shape that into
|
|
98
|
-
// a single inline <code> element ("only the outermost type opens/closes
|
|
99
|
-
// the code fragment")
|
|
100
|
-
const [preElement] = root.children;
|
|
101
|
-
const [codeElement] = preElement.children;
|
|
102
|
-
|
|
103
|
-
const result = {
|
|
104
|
-
type: 'element',
|
|
105
|
-
tagName: 'code',
|
|
106
|
-
properties: {
|
|
107
|
-
class: `${preElement.properties.class} type`,
|
|
108
|
-
},
|
|
109
|
-
children: codeElement.children,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
state.patch(node, result);
|
|
113
|
-
|
|
114
|
-
return state.applyData(node, result);
|
|
115
|
-
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { highlighter } from '#utils/highlighter.mjs';
|
|
4
|
+
|
|
5
|
+
import { typeAnnotationToHast } from './hast.mjs';
|
|
6
|
+
|
|
7
|
+
// Kept apart from `./hast.mjs` on purpose: importing this module loads Shiki
|
|
8
|
+
// (every grammar plus the regex engine), which only the pipelines that
|
|
9
|
+
// highlight should pay for. The `ast` and `metadata` stages never do.
|
|
10
|
+
const [lightTheme, darkTheme] = highlighter.shiki.getLoadedThemes();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Syntax-highlighted mdast→hast handler for `typeAnnotation` nodes, used by
|
|
14
|
+
* the web (JSX) pipeline. The whole type is highlighted as one inline
|
|
15
|
+
* fragment, and each resolved identifier's exact character range is wrapped
|
|
16
|
+
* in an `<a>` via Shiki decorations. Values that are not TypeScript (display
|
|
17
|
+
* names such as `HTTP/2 Headers Object`) are highlighted as plain text, so
|
|
18
|
+
* their prose is not coloured as operators and numeric literals.
|
|
19
|
+
*
|
|
20
|
+
* Falls back to the minimal handler when the type failed to parse or nothing
|
|
21
|
+
* resolved (no point paying for highlighting then).
|
|
22
|
+
*
|
|
23
|
+
* @param {import('mdast-util-to-hast').State} state
|
|
24
|
+
* @param {import('mdast').Node} node
|
|
25
|
+
* @returns {import('hast').Element}
|
|
26
|
+
*/
|
|
27
|
+
export const typeAnnotationToHighlightedHast = (state, node) => {
|
|
28
|
+
const links = node.data?.links ?? [];
|
|
29
|
+
|
|
30
|
+
if (node.data?.parseError || links.length === 0) {
|
|
31
|
+
return typeAnnotationToHast(state, node);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const root = highlighter.shiki.codeToHast(node.value, {
|
|
35
|
+
lang: node.data?.typescript ? 'typescript' : 'text',
|
|
36
|
+
themes: { light: lightTheme, dark: darkTheme },
|
|
37
|
+
decorations: links.map(({ start, end, href }) => ({
|
|
38
|
+
start,
|
|
39
|
+
end,
|
|
40
|
+
tagName: 'a',
|
|
41
|
+
properties: { href, class: 'type-link' },
|
|
42
|
+
alwaysWrap: true,
|
|
43
|
+
})),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// codeToHast wraps the highlighted line in <pre><code>; re-shape that into
|
|
47
|
+
// a single inline <code> element ("only the outermost type opens/closes
|
|
48
|
+
// the code fragment")
|
|
49
|
+
const [preElement] = root.children;
|
|
50
|
+
const [codeElement] = preElement.children;
|
|
51
|
+
|
|
52
|
+
const result = {
|
|
53
|
+
type: 'element',
|
|
54
|
+
tagName: 'code',
|
|
55
|
+
properties: {
|
|
56
|
+
class: `${preElement.properties.class} type`,
|
|
57
|
+
},
|
|
58
|
+
children: codeElement.children,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
state.patch(node, result);
|
|
62
|
+
|
|
63
|
+
return state.applyData(node, result);
|
|
64
|
+
};
|