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.
@@ -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 };
@@ -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 };