vitepress-plugin-api-extractor 0.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.
- package/ApiExtractor.js +96 -0
- package/Categories.js +49 -0
- package/Generate.js +220 -0
- package/Registry.js +117 -0
- package/Twoslash.js +43 -0
- package/TwoslashCache.js +102 -0
- package/emit/frontmatter.js +58 -0
- package/emit/markdown.js +199 -0
- package/emit/sidebar.js +37 -0
- package/index.d.ts +477 -0
- package/index.js +11 -0
- package/package.json +65 -0
- package/tsdoc-metadata.json +11 -0
package/emit/markdown.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Result } from "effect";
|
|
2
|
+
import { Blockquote, Code, Heading, InlineCode, Link, List, ListItem, Markdown, Paragraph, Root, Strong, Table, TableCell, TableRow, Text } from "@effected/markdown";
|
|
3
|
+
|
|
4
|
+
//#region src/emit/markdown.ts
|
|
5
|
+
/**
|
|
6
|
+
* The fence info string that triggers Twoslash under VitePress's default
|
|
7
|
+
* `explicitTrigger`.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
const TWOSLASH_META = "twoslash";
|
|
12
|
+
const text = (value) => Text.make({ value });
|
|
13
|
+
const paragraph = (children) => Paragraph.make({ children: [...children] });
|
|
14
|
+
const heading = (depth, value) => Heading.make({
|
|
15
|
+
depth,
|
|
16
|
+
children: [text(value)]
|
|
17
|
+
});
|
|
18
|
+
const code = (value) => InlineCode.make({ value });
|
|
19
|
+
const cell = (children) => TableCell.make({ children: [...children] });
|
|
20
|
+
const row = (cells) => TableRow.make({ children: [...cells] });
|
|
21
|
+
/** A type-checked fence: the `source` text under the Twoslash trigger. */
|
|
22
|
+
const twoslashFence = (source) => Code.make({
|
|
23
|
+
value: source,
|
|
24
|
+
lang: "ts",
|
|
25
|
+
meta: TWOSLASH_META
|
|
26
|
+
});
|
|
27
|
+
const NO_ERRORS = "// @noErrors";
|
|
28
|
+
/**
|
|
29
|
+
* A type-checked fence for a DECLARATION — a signature, a member, a base
|
|
30
|
+
* class — with error rendering off.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* A declaration excerpt is not a program: its type parameters and the
|
|
34
|
+
* sibling types it names are out of scope, so Twoslash would annotate every
|
|
35
|
+
* line with "Cannot find name". The RSPress plugin never type-checks these
|
|
36
|
+
* blocks at all; here they keep their hovers (every identifier the package's
|
|
37
|
+
* declarations resolve) and drop the diagnostics, which is what `@noErrors`
|
|
38
|
+
* does. The directive is the emitter's spelling, not the IR's — `source`
|
|
39
|
+
* carries it only when the builder put it there (examples).
|
|
40
|
+
*/
|
|
41
|
+
const declarationFence = (source) => twoslashFence(source.includes(NO_ERRORS) ? source : `${NO_ERRORS}\n${source}`);
|
|
42
|
+
/** A plain fence: the `display` text, no type-checking. */
|
|
43
|
+
const plainFence = (display, lang) => Code.make({
|
|
44
|
+
value: display,
|
|
45
|
+
lang
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* A heading carrying a VitePress custom anchor: `### name {#anchor}`.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* mdast has no node for the attribute suffix, so it rides as trailing text;
|
|
52
|
+
* the kit passes `{` through on a tree with no MDX nodes.
|
|
53
|
+
*/
|
|
54
|
+
const anchoredHeading = (depth, name, anchor) => Heading.make({
|
|
55
|
+
depth,
|
|
56
|
+
children: [text(`${name} {#${anchor}}`)]
|
|
57
|
+
});
|
|
58
|
+
const parametersTable = (rows) => Table.make({ children: [row([
|
|
59
|
+
cell([text("Name")]),
|
|
60
|
+
cell([text("Type")]),
|
|
61
|
+
cell([text("Description")])
|
|
62
|
+
]), ...rows.map((r) => row([
|
|
63
|
+
cell([code(r.name)]),
|
|
64
|
+
cell(r.type === void 0 ? [] : [code(r.type)]),
|
|
65
|
+
cell(r.description)
|
|
66
|
+
]))] });
|
|
67
|
+
const enumMembersTable = (rows) => Table.make({ children: [row([
|
|
68
|
+
cell([text("Name")]),
|
|
69
|
+
cell([text("Value")]),
|
|
70
|
+
cell([text("Description")])
|
|
71
|
+
]), ...rows.map((r) => row([
|
|
72
|
+
cell([code(r.name)]),
|
|
73
|
+
cell(r.value === void 0 ? [] : [code(r.value)]),
|
|
74
|
+
cell(r.description)
|
|
75
|
+
]))] });
|
|
76
|
+
/** The heading text a member role fixes, or the member's own name. */
|
|
77
|
+
function memberHeadingName(member) {
|
|
78
|
+
switch (member.role) {
|
|
79
|
+
case "constructor": return "constructor";
|
|
80
|
+
case "call-signature": return "Call Signature";
|
|
81
|
+
case "construct-signature": return "Construct Signature";
|
|
82
|
+
case "index-signature": return "Index Signature";
|
|
83
|
+
default: return member.name;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const memberNodes = (member) => {
|
|
87
|
+
const nodes = [anchoredHeading(3, memberHeadingName(member), member.anchor), declarationFence(member.code.source)];
|
|
88
|
+
if (member.summary && member.summary.length > 0) nodes.push(paragraph(member.summary));
|
|
89
|
+
if (member.parameters && member.parameters.length > 0) nodes.push(parametersTable(member.parameters));
|
|
90
|
+
if (member.returns && member.returns.length > 0) nodes.push(paragraph([
|
|
91
|
+
Strong.make({ children: [text("Returns:")] }),
|
|
92
|
+
text(" "),
|
|
93
|
+
...member.returns
|
|
94
|
+
]));
|
|
95
|
+
return nodes;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Render one block to flow nodes.
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
function markdownBlockTree(block) {
|
|
103
|
+
switch (block.kind) {
|
|
104
|
+
case "title": {
|
|
105
|
+
const nodes = [heading(1, block.name)];
|
|
106
|
+
if (block.deprecation && block.deprecation.length > 0) nodes.push(Blockquote.make({ children: [paragraph([
|
|
107
|
+
text("⚠️ "),
|
|
108
|
+
Strong.make({ children: [text("Deprecated:")] }),
|
|
109
|
+
text(" "),
|
|
110
|
+
...block.deprecation
|
|
111
|
+
])] }));
|
|
112
|
+
if (block.releaseTag !== "Public") nodes.push(paragraph([code(block.releaseTag)]));
|
|
113
|
+
return nodes;
|
|
114
|
+
}
|
|
115
|
+
case "available-from": {
|
|
116
|
+
const children = [text("Available from: ")];
|
|
117
|
+
block.entryPoints.forEach((entryPoint, index) => {
|
|
118
|
+
if (index > 0) children.push(text(", "));
|
|
119
|
+
children.push(code(entryPoint === "default" ? block.packageName : `${block.packageName}/${entryPoint}`));
|
|
120
|
+
});
|
|
121
|
+
return [paragraph(children)];
|
|
122
|
+
}
|
|
123
|
+
case "prose": return block.role === "summary" ? block.content : [heading(2, block.role === "remarks" ? "Remarks" : "Returns"), ...block.content];
|
|
124
|
+
case "source-link": return [paragraph([Link.make({
|
|
125
|
+
url: block.href,
|
|
126
|
+
children: [text("Source")]
|
|
127
|
+
})])];
|
|
128
|
+
case "signature": return [heading(2, "Signature"), declarationFence(block.code.source)];
|
|
129
|
+
case "base-class": return [
|
|
130
|
+
heading(2, "Base Class"),
|
|
131
|
+
paragraph([
|
|
132
|
+
code(block.className),
|
|
133
|
+
text(" extends "),
|
|
134
|
+
code(block.baseName),
|
|
135
|
+
text(", a compiler-generated declaration that is not exported from "),
|
|
136
|
+
code(block.packageName),
|
|
137
|
+
text(".")
|
|
138
|
+
]),
|
|
139
|
+
declarationFence(block.code.source)
|
|
140
|
+
];
|
|
141
|
+
case "member-group": return [heading(2, block.title), ...block.members.flatMap(memberNodes)];
|
|
142
|
+
case "parameters": return [parametersTable(block.rows)];
|
|
143
|
+
case "enum-members": return [enumMembersTable(block.rows)];
|
|
144
|
+
case "examples": return [heading(2, "Examples"), ...block.items.map((item) => item.typeChecked ? twoslashFence(item.code.source) : plainFence(item.code.display, item.language))];
|
|
145
|
+
case "see-also": return [heading(2, "See Also"), List.make({
|
|
146
|
+
ordered: false,
|
|
147
|
+
spread: false,
|
|
148
|
+
children: block.references.map((reference) => ListItem.make({
|
|
149
|
+
spread: false,
|
|
150
|
+
children: [paragraph(reference)]
|
|
151
|
+
}))
|
|
152
|
+
})];
|
|
153
|
+
case "member-index": return [heading(2, block.title), List.make({
|
|
154
|
+
ordered: false,
|
|
155
|
+
spread: false,
|
|
156
|
+
children: block.entries.map((entry) => {
|
|
157
|
+
const children = [Link.make({
|
|
158
|
+
url: entry.route,
|
|
159
|
+
children: [text(entry.name)]
|
|
160
|
+
})];
|
|
161
|
+
if (entry.summary && entry.summary.length > 0) children.push(text(" - "), ...entry.summary);
|
|
162
|
+
return ListItem.make({
|
|
163
|
+
spread: false,
|
|
164
|
+
children: [paragraph(children)]
|
|
165
|
+
});
|
|
166
|
+
})
|
|
167
|
+
})];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Render a page's body to flow nodes — the pre-serialization form of
|
|
172
|
+
* {@link emitMarkdownBody}.
|
|
173
|
+
*
|
|
174
|
+
* @public
|
|
175
|
+
*/
|
|
176
|
+
function markdownTree(page) {
|
|
177
|
+
return page.blocks.flatMap(markdownBlockTree);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Emit a page's markdown body. No frontmatter — the adapter assembles that
|
|
181
|
+
* from the page facts (see `emit/frontmatter.ts`).
|
|
182
|
+
*
|
|
183
|
+
* @remarks
|
|
184
|
+
* A stringify failure is surfaced rather than thrown: the prose inside a
|
|
185
|
+
* block arrived from a builder and may carry any node the kit admits, and the
|
|
186
|
+
* kit's own error names what it could not serialize.
|
|
187
|
+
*
|
|
188
|
+
* @public
|
|
189
|
+
*/
|
|
190
|
+
function emitMarkdownBody(page) {
|
|
191
|
+
const root = Root.make({ children: [...markdownTree(page)] });
|
|
192
|
+
return Result.map(Markdown.stringifyResult(root), (markdown) => {
|
|
193
|
+
const trimmed = markdown.trim();
|
|
194
|
+
return trimmed ? `${trimmed}\n` : "\n";
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
export { TWOSLASH_META, emitMarkdownBody, markdownBlockTree, markdownTree };
|
package/emit/sidebar.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/emit/sidebar.ts
|
|
2
|
+
/** One category group as a collapsible sidebar section. */
|
|
3
|
+
function sidebarGroup(group) {
|
|
4
|
+
const collapsible = group.category.collapsible ?? true;
|
|
5
|
+
return {
|
|
6
|
+
text: group.category.displayName,
|
|
7
|
+
items: group.pages.map((page) => ({
|
|
8
|
+
text: page.label,
|
|
9
|
+
link: page.route
|
|
10
|
+
})),
|
|
11
|
+
...collapsible ? { collapsed: group.category.collapsed ?? true } : {}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The sidebar items for one API: the index link, then one group per
|
|
16
|
+
* category in the tree's order.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
function sidebarItems(tree) {
|
|
21
|
+
return [{
|
|
22
|
+
text: tree.index.label,
|
|
23
|
+
link: `${tree.baseRoute}/`
|
|
24
|
+
}, ...tree.groups.map(sidebarGroup)];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The `themeConfig.sidebar` entry for one API, keyed by its base route so
|
|
28
|
+
* the sidebar shows only under the API's pages.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
function sidebarFor(tree) {
|
|
33
|
+
return { [`${tree.baseRoute}/`]: sidebarItems(tree) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { sidebarFor, sidebarGroup, sidebarItems };
|