@tsdoctor/pages 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/Blocks.js +358 -0
- package/Build.js +525 -0
- package/Examples.js +237 -0
- package/LICENSE +21 -0
- package/Llms.js +282 -0
- package/Markdown.js +155 -0
- package/Nav.js +148 -0
- package/Page.js +80 -0
- package/README.md +32 -0
- package/Scope.js +56 -0
- package/TwoslashDirectives.js +63 -0
- package/WorkItems.js +162 -0
- package/index.d.ts +1193 -0
- package/index.js +12 -0
- package/package.json +54 -0
- package/tsdoc-metadata.json +11 -0
package/Build.js
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { AvailableFrom, BaseClass, CodeText, EnumMemberRow, EnumMemberTable, Example, ExampleGroup, Member, MemberGroup, MemberIndex, MemberIndexEntry, ParameterRow, ParameterTable, ProseBlock, SeeAlso, Signature as Signature$1, SourceLink, Title } from "./Blocks.js";
|
|
2
|
+
import { codeText, formatExampleCode, prepareExampleCode, prependHiddenImports } from "./Examples.js";
|
|
3
|
+
import { NavEntry } from "./Nav.js";
|
|
4
|
+
import { Page } from "./Page.js";
|
|
5
|
+
import { Markdown, Paragraph, Text } from "@effected/markdown";
|
|
6
|
+
import { Effect, Option, Result, Schema } from "effect";
|
|
7
|
+
import { ApiItemKind } from "@microsoft/api-extractor-model";
|
|
8
|
+
import { ApiItems, Routes, Signature, Tsdoc, TypeReferenceExtractor } from "@tsdoctor/model";
|
|
9
|
+
|
|
10
|
+
//#region src/Build.ts
|
|
11
|
+
/**
|
|
12
|
+
* The description a page carries when its item has no summary.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
const NO_DESCRIPTION = "No description available.";
|
|
17
|
+
/**
|
|
18
|
+
* The API landing page: frontmatter facts only, no blocks.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
var IndexPage = class extends Schema.Class("IndexPage")({
|
|
23
|
+
/** The page route (`{baseRoute}/index`). */
|
|
24
|
+
route: Schema.String,
|
|
25
|
+
/** The page title. */
|
|
26
|
+
title: Schema.String,
|
|
27
|
+
/** The page description. */
|
|
28
|
+
description: Schema.String
|
|
29
|
+
}) {};
|
|
30
|
+
/**
|
|
31
|
+
* The label of the index page.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
const INDEX_PAGE_TITLE = "API Reference";
|
|
36
|
+
/**
|
|
37
|
+
* Build the API landing page.
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
function buildIndexPage(input) {
|
|
42
|
+
return IndexPage.make({
|
|
43
|
+
route: `${input.baseRoute}/index`,
|
|
44
|
+
title: INDEX_PAGE_TITLE,
|
|
45
|
+
description: `Auto-generated API documentation for ${input.packageName}`
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const PARSE_OPTIONS = { dialect: "commonmark" };
|
|
49
|
+
/**
|
|
50
|
+
* Parse a one-line prose string as phrasing content. Total: a parse failure
|
|
51
|
+
* (a hardening-guard trip on a pathological string) degrades to the raw
|
|
52
|
+
* text.
|
|
53
|
+
*/
|
|
54
|
+
function phrasing(text) {
|
|
55
|
+
const parsed = Markdown.parsePhrasingResult(text, PARSE_OPTIONS);
|
|
56
|
+
return Result.isSuccess(parsed) ? parsed.success : [Text.make({ value: text })];
|
|
57
|
+
}
|
|
58
|
+
/** Cross-link a prose string, then parse it as phrasing content. */
|
|
59
|
+
function linked(linker, text) {
|
|
60
|
+
return phrasing(linker.link(text));
|
|
61
|
+
}
|
|
62
|
+
/** Parse a prose string as flow content. Total, on the same terms as {@link phrasing}. */
|
|
63
|
+
function flow(text) {
|
|
64
|
+
const parsed = Markdown.parseResult(text, PARSE_OPTIONS);
|
|
65
|
+
if (Result.isSuccess(parsed)) {
|
|
66
|
+
const nodes = parsed.success.children.filter((node) => node.type !== "frontmatter" && node.type !== "mdxjsEsm");
|
|
67
|
+
if (nodes.length > 0) return nodes;
|
|
68
|
+
}
|
|
69
|
+
return [Paragraph.make({ children: [Text.make({ value: text })] })];
|
|
70
|
+
}
|
|
71
|
+
/** The formatted declaration excerpt, or `""` when the item has none. */
|
|
72
|
+
function excerptOf(item) {
|
|
73
|
+
const declared = item;
|
|
74
|
+
return declared.excerpt?.text ? Signature.format(declared.excerpt).trim() : "";
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Prepend the hidden `import type` lines a code block needs for the external
|
|
78
|
+
* types `scope` references, resolved against `owner`'s package.
|
|
79
|
+
*/
|
|
80
|
+
function withHiddenImports(code, owner, scope, packageName) {
|
|
81
|
+
const apiPackage = owner.getAssociatedPackage?.();
|
|
82
|
+
if (!apiPackage) return code;
|
|
83
|
+
const imports = new TypeReferenceExtractor(apiPackage, packageName).extractImportsForApiItem(scope);
|
|
84
|
+
return prependHiddenImports(code, imports);
|
|
85
|
+
}
|
|
86
|
+
/** The `class X extends … implements … {` / `interface X<T> extends … {` opening line. */
|
|
87
|
+
function containerDeclaration(item) {
|
|
88
|
+
const inheritance = ApiItems.inheritance(item);
|
|
89
|
+
let declaration = item.kind === ApiItemKind.Class ? `class ${item.displayName}` : `interface ${item.displayName}`;
|
|
90
|
+
if (item.kind === ApiItemKind.Interface) {
|
|
91
|
+
const typeParameters = item.typeParameters;
|
|
92
|
+
if (typeParameters && typeParameters.length > 0) declaration += `<${typeParameters.map((parameter) => parameter.name).join(", ")}>`;
|
|
93
|
+
}
|
|
94
|
+
if (inheritance.extends && inheritance.extends.length > 0) declaration += ` extends ${inheritance.extends.join(", ")}`;
|
|
95
|
+
if (item.kind === ApiItemKind.Class && inheritance.implements && inheritance.implements.length > 0) declaration += ` implements ${inheritance.implements.join(", ")}`;
|
|
96
|
+
return `${declaration} {`;
|
|
97
|
+
}
|
|
98
|
+
/** The full skeleton: the opening line, one indented line per member, the closing brace. */
|
|
99
|
+
function containerSkeleton(item, members) {
|
|
100
|
+
const lines = [containerDeclaration(item)];
|
|
101
|
+
for (const member of members) {
|
|
102
|
+
const signature = excerptOf(member);
|
|
103
|
+
if (signature) lines.push(` ${signature}`);
|
|
104
|
+
}
|
|
105
|
+
lines.push("}");
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
/** The three-line member context the hide-cut transformer trims: opening, member, closing. */
|
|
109
|
+
function memberContext(item, member, packageName) {
|
|
110
|
+
return withHiddenImports(`${containerDeclaration(item)}\n${excerptOf(member)}\n}`, item, member, packageName);
|
|
111
|
+
}
|
|
112
|
+
const isStatic = (member) => member.isStatic === true;
|
|
113
|
+
const isAccessorName = (member) => member.displayName.startsWith("get ") || member.displayName.startsWith("set ");
|
|
114
|
+
/** Group a class's members the way the class page lists and skeletons them. */
|
|
115
|
+
function classMembers(apiClass) {
|
|
116
|
+
const properties = apiClass.members.filter((m) => m.kind === "Property" || m.kind === "PropertySignature");
|
|
117
|
+
const methods = apiClass.members.filter((m) => m.kind === "Method" || m.kind === "MethodSignature");
|
|
118
|
+
return {
|
|
119
|
+
constructors: apiClass.members.filter((m) => m.kind === "Constructor"),
|
|
120
|
+
staticProperties: properties.filter(isStatic),
|
|
121
|
+
staticMethods: methods.filter((m) => !(m.kind === "Method" && isAccessorName(m)) && isStatic(m)),
|
|
122
|
+
instanceProperties: properties.filter((m) => !isStatic(m) && !isAccessorName(m)),
|
|
123
|
+
getters: methods.filter((m) => m.kind === "Method" && isAccessorName(m)),
|
|
124
|
+
instanceMethods: methods.filter((m) => !(m.kind === "Method" && isAccessorName(m)) && !isStatic(m))
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** The anchor a member's page element carries — data from the work item, or the model's own algorithm. */
|
|
128
|
+
function anchorOf(ctx, member) {
|
|
129
|
+
return ctx.anchors.get(member.canonicalReference?.toString() ?? member.displayName) ?? Routes.memberAnchor(member.displayName);
|
|
130
|
+
}
|
|
131
|
+
function parameterRows(linker, item) {
|
|
132
|
+
return Tsdoc.params(item).map((parameter) => ParameterRow.make({
|
|
133
|
+
name: parameter.name,
|
|
134
|
+
...parameter.type !== void 0 ? { type: parameter.type } : {},
|
|
135
|
+
description: linked(linker, parameter.description)
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* One member block, or none when the member has no declaration excerpt —
|
|
140
|
+
* the generators rendered nothing for such a member.
|
|
141
|
+
*/
|
|
142
|
+
function buildMember(ctx, member, role, name, anchor, options) {
|
|
143
|
+
const signature = excerptOf(member);
|
|
144
|
+
if (!signature) return Option.none();
|
|
145
|
+
const summary = Tsdoc.summary(member);
|
|
146
|
+
const parameters = options.parameters ? parameterRows(ctx.linker, member) : [];
|
|
147
|
+
const returns = options.returns ? Tsdoc.returns(member) : null;
|
|
148
|
+
return Option.some(Member.make({
|
|
149
|
+
role,
|
|
150
|
+
name,
|
|
151
|
+
anchor,
|
|
152
|
+
code: CodeText.make({
|
|
153
|
+
display: signature,
|
|
154
|
+
source: memberContext(ctx.owner, member, ctx.packageName)
|
|
155
|
+
}),
|
|
156
|
+
...summary ? { summary: linked(ctx.linker, summary) } : {},
|
|
157
|
+
...parameters.length > 0 ? { parameters } : {},
|
|
158
|
+
...returns ? { returns: linked(ctx.linker, returns.description) } : {}
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
const fixedMember = (name, anchorName) => ({
|
|
162
|
+
name,
|
|
163
|
+
anchor: Routes.memberAnchor(anchorName)
|
|
164
|
+
});
|
|
165
|
+
/** A heading group, or none when no member of the list renders. */
|
|
166
|
+
function memberGroup(ctx, title, members, role, options, fixed) {
|
|
167
|
+
if (members.length === 0) return Option.none();
|
|
168
|
+
const built = [];
|
|
169
|
+
for (const member of members) {
|
|
170
|
+
const anchor = fixed ? fixed.anchor : anchorOf(ctx, member);
|
|
171
|
+
const result = buildMember(ctx, member, role, fixed ? fixed.name : member.displayName, anchor, options);
|
|
172
|
+
if (Option.isSome(result)) built.push(result.value);
|
|
173
|
+
}
|
|
174
|
+
return Option.some(MemberGroup.make({
|
|
175
|
+
title,
|
|
176
|
+
members: built
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
function titleBlock(item, linker) {
|
|
180
|
+
const deprecation = Tsdoc.deprecation(item);
|
|
181
|
+
return Title.make({
|
|
182
|
+
name: item.displayName,
|
|
183
|
+
releaseTag: Tsdoc.releaseTag(item),
|
|
184
|
+
...deprecation ? { deprecation: linked(linker, deprecation.message) } : {}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function headBlocks(input, summary) {
|
|
188
|
+
const blocks = [titleBlock(input.item, input.linker), ProseBlock.make({
|
|
189
|
+
role: "summary",
|
|
190
|
+
content: flow(summary)
|
|
191
|
+
})];
|
|
192
|
+
if (input.availableFrom && input.availableFrom.length > 1) blocks.push(AvailableFrom.make({
|
|
193
|
+
packageName: input.packageName,
|
|
194
|
+
entryPoints: input.availableFrom
|
|
195
|
+
}));
|
|
196
|
+
const href = ApiItems.sourceLink(input.item, input.source);
|
|
197
|
+
if (href) blocks.push(SourceLink.make({ href }));
|
|
198
|
+
return blocks;
|
|
199
|
+
}
|
|
200
|
+
const buildExampleWithFallback = (input, example) => Effect.gen(function* () {
|
|
201
|
+
const prepared = prepareExampleCode(example, input.item.displayName, input.packageName, input.suppressExampleErrors);
|
|
202
|
+
const formatted = yield* formatExampleCode(prepared.code, prepared.language).pipe(Effect.catchTag("ExampleFormatError", (error) => (input.onExampleFormatError ? input.onExampleFormatError(error) : Effect.void).pipe(Effect.as(prepared.code))));
|
|
203
|
+
return Example.make({
|
|
204
|
+
language: prepared.language,
|
|
205
|
+
code: prepared.isTypeScript ? codeText(formatted) : CodeText.make({
|
|
206
|
+
display: formatted,
|
|
207
|
+
source: formatted
|
|
208
|
+
}),
|
|
209
|
+
typeChecked: prepared.isTypeScript
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
const tailBlocks = (input) => Effect.gen(function* () {
|
|
213
|
+
const blocks = [];
|
|
214
|
+
const examples = Tsdoc.examples(input.item);
|
|
215
|
+
if (examples.length > 0) {
|
|
216
|
+
const items = yield* Effect.forEach(examples, (example) => buildExampleWithFallback(input, example));
|
|
217
|
+
blocks.push(ExampleGroup.make({ items }));
|
|
218
|
+
}
|
|
219
|
+
const references = Tsdoc.seeReferences(input.item);
|
|
220
|
+
if (references.length > 0) blocks.push(SeeAlso.make({ references: references.map((reference) => linked(input.linker, reference.text)) }));
|
|
221
|
+
return blocks;
|
|
222
|
+
});
|
|
223
|
+
function classBody(input, apiClass) {
|
|
224
|
+
const { packageName, linker } = input;
|
|
225
|
+
const members = classMembers(apiClass);
|
|
226
|
+
const ctx = {
|
|
227
|
+
owner: apiClass,
|
|
228
|
+
packageName,
|
|
229
|
+
linker,
|
|
230
|
+
anchors: input.memberAnchors ?? ApiItems.memberAnchors(apiClass)
|
|
231
|
+
};
|
|
232
|
+
const blocks = [];
|
|
233
|
+
const skeleton = containerSkeleton(apiClass, [
|
|
234
|
+
...members.constructors,
|
|
235
|
+
...members.staticProperties,
|
|
236
|
+
...members.staticMethods,
|
|
237
|
+
...members.instanceProperties,
|
|
238
|
+
...members.getters,
|
|
239
|
+
...members.instanceMethods
|
|
240
|
+
]);
|
|
241
|
+
blocks.push(Signature$1.make({ code: codeText(withHiddenImports(skeleton, apiClass, apiClass, packageName)) }));
|
|
242
|
+
const base = input.syntheticBase;
|
|
243
|
+
if (base?.excerpt?.text) {
|
|
244
|
+
const signature = Signature.format(base.excerpt).trim();
|
|
245
|
+
blocks.push(BaseClass.make({
|
|
246
|
+
className: apiClass.displayName,
|
|
247
|
+
baseName: base.displayName,
|
|
248
|
+
packageName,
|
|
249
|
+
code: codeText(withHiddenImports(signature, apiClass, base, packageName))
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
const withParameters = {
|
|
253
|
+
parameters: true,
|
|
254
|
+
returns: false
|
|
255
|
+
};
|
|
256
|
+
const methodOptions = {
|
|
257
|
+
parameters: true,
|
|
258
|
+
returns: true
|
|
259
|
+
};
|
|
260
|
+
const propertyOptions = {
|
|
261
|
+
parameters: false,
|
|
262
|
+
returns: false
|
|
263
|
+
};
|
|
264
|
+
const groups = [
|
|
265
|
+
memberGroup(ctx, "Constructors", members.constructors, "constructor", withParameters, fixedMember("constructor", "constructor")),
|
|
266
|
+
memberGroup(ctx, "Static Properties", members.staticProperties, "property", propertyOptions),
|
|
267
|
+
memberGroup(ctx, "Static Methods", members.staticMethods, "method", methodOptions),
|
|
268
|
+
memberGroup(ctx, "Properties", members.instanceProperties, "property", propertyOptions),
|
|
269
|
+
memberGroup(ctx, "Getters & Setters", members.getters, "getter", methodOptions),
|
|
270
|
+
memberGroup(ctx, "Methods", members.instanceMethods, "method", methodOptions)
|
|
271
|
+
];
|
|
272
|
+
for (const group of groups) if (Option.isSome(group)) blocks.push(group.value);
|
|
273
|
+
return blocks;
|
|
274
|
+
}
|
|
275
|
+
function interfaceBody(input, apiInterface) {
|
|
276
|
+
const { packageName, linker } = input;
|
|
277
|
+
const ctx = {
|
|
278
|
+
owner: apiInterface,
|
|
279
|
+
packageName,
|
|
280
|
+
linker,
|
|
281
|
+
anchors: input.memberAnchors ?? ApiItems.memberAnchors(apiInterface)
|
|
282
|
+
};
|
|
283
|
+
const ofKind = (kind) => apiInterface.members.filter((m) => m.kind === kind);
|
|
284
|
+
const callSignatures = ofKind("CallSignature");
|
|
285
|
+
const constructSignatures = ofKind("ConstructSignature");
|
|
286
|
+
const indexSignatures = ofKind("IndexSignature");
|
|
287
|
+
const properties = ofKind("PropertySignature");
|
|
288
|
+
const methods = ofKind("MethodSignature");
|
|
289
|
+
const blocks = [];
|
|
290
|
+
const skeleton = containerSkeleton(apiInterface, [
|
|
291
|
+
...callSignatures,
|
|
292
|
+
...constructSignatures,
|
|
293
|
+
...indexSignatures,
|
|
294
|
+
...properties,
|
|
295
|
+
...methods
|
|
296
|
+
]);
|
|
297
|
+
blocks.push(Signature$1.make({ code: codeText(withHiddenImports(skeleton, apiInterface, apiInterface, packageName)) }));
|
|
298
|
+
const plain = {
|
|
299
|
+
parameters: false,
|
|
300
|
+
returns: false
|
|
301
|
+
};
|
|
302
|
+
const groups = [
|
|
303
|
+
memberGroup(ctx, "Call Signatures", callSignatures, "call-signature", plain, fixedMember("Call Signature", "call-signature")),
|
|
304
|
+
memberGroup(ctx, "Construct Signatures", constructSignatures, "construct-signature", plain, fixedMember("Construct Signature", "construct-signature")),
|
|
305
|
+
memberGroup(ctx, "Index Signature", indexSignatures, "index-signature", plain, fixedMember("Index Signature", "index-signature")),
|
|
306
|
+
memberGroup(ctx, "Properties", properties, "property", plain),
|
|
307
|
+
memberGroup(ctx, "Methods", methods, "method", {
|
|
308
|
+
parameters: true,
|
|
309
|
+
returns: true
|
|
310
|
+
})
|
|
311
|
+
];
|
|
312
|
+
for (const group of groups) if (Option.isSome(group)) blocks.push(group.value);
|
|
313
|
+
return blocks;
|
|
314
|
+
}
|
|
315
|
+
function functionBody(input) {
|
|
316
|
+
const { item, packageName, linker } = input;
|
|
317
|
+
const blocks = [];
|
|
318
|
+
const rows = parameterRows(linker, item);
|
|
319
|
+
const signature = excerptOf(item);
|
|
320
|
+
if (signature) blocks.push(Signature$1.make({
|
|
321
|
+
code: codeText(withHiddenImports(signature, item, item, packageName)),
|
|
322
|
+
hasParameters: rows.length > 0
|
|
323
|
+
}));
|
|
324
|
+
if (rows.length > 0) blocks.push(ParameterTable.make({ rows }));
|
|
325
|
+
const returns = Tsdoc.returns(item);
|
|
326
|
+
if (returns) blocks.push(ProseBlock.make({
|
|
327
|
+
role: "returns",
|
|
328
|
+
content: [Paragraph.make({ children: [...linked(linker, returns.description)] })]
|
|
329
|
+
}));
|
|
330
|
+
return blocks;
|
|
331
|
+
}
|
|
332
|
+
function declarationBody(input) {
|
|
333
|
+
const { item, packageName } = input;
|
|
334
|
+
const signature = excerptOf(item);
|
|
335
|
+
if (!signature) return [];
|
|
336
|
+
return [Signature$1.make({ code: codeText(withHiddenImports(signature, item, item, packageName)) })];
|
|
337
|
+
}
|
|
338
|
+
/** The initializer after `=` in an enum member's excerpt, without a trailing comma. */
|
|
339
|
+
function enumMemberValue(member) {
|
|
340
|
+
const text = member.excerpt?.text?.trim();
|
|
341
|
+
if (!text) return void 0;
|
|
342
|
+
const equals = text.indexOf("=");
|
|
343
|
+
if (equals === -1) return void 0;
|
|
344
|
+
return text.substring(equals + 1).trim().replace(/,\s*$/, "");
|
|
345
|
+
}
|
|
346
|
+
function enumBody(input, apiEnum) {
|
|
347
|
+
const { linker } = input;
|
|
348
|
+
const members = apiEnum.members;
|
|
349
|
+
const lines = [`enum ${apiEnum.displayName} {`];
|
|
350
|
+
members.forEach((member, index) => {
|
|
351
|
+
const value = enumMemberValue(member);
|
|
352
|
+
const line = ` ${member.displayName}${value !== void 0 ? ` = ${value}` : ""}`;
|
|
353
|
+
lines.push(index < members.length - 1 ? `${line},` : line);
|
|
354
|
+
});
|
|
355
|
+
lines.push("}");
|
|
356
|
+
const hasMembers = members.length > 0;
|
|
357
|
+
const blocks = [Signature$1.make({
|
|
358
|
+
code: codeText(lines.join("\n")),
|
|
359
|
+
hasMembers
|
|
360
|
+
})];
|
|
361
|
+
if (hasMembers) blocks.push(EnumMemberTable.make({ rows: members.map((member) => {
|
|
362
|
+
const value = enumMemberValue(member);
|
|
363
|
+
return EnumMemberRow.make({
|
|
364
|
+
name: member.displayName,
|
|
365
|
+
...value !== void 0 ? { value } : {},
|
|
366
|
+
description: linked(linker, Tsdoc.summary(member) || "")
|
|
367
|
+
});
|
|
368
|
+
}) }));
|
|
369
|
+
return blocks;
|
|
370
|
+
}
|
|
371
|
+
/** The namespace member index sections, in the order the namespace page lists them. */
|
|
372
|
+
const NAMESPACE_SECTIONS = [
|
|
373
|
+
[
|
|
374
|
+
"Classes",
|
|
375
|
+
ApiItemKind.Class,
|
|
376
|
+
"class"
|
|
377
|
+
],
|
|
378
|
+
[
|
|
379
|
+
"Interfaces",
|
|
380
|
+
ApiItemKind.Interface,
|
|
381
|
+
"interface"
|
|
382
|
+
],
|
|
383
|
+
[
|
|
384
|
+
"Functions",
|
|
385
|
+
ApiItemKind.Function,
|
|
386
|
+
"function"
|
|
387
|
+
],
|
|
388
|
+
[
|
|
389
|
+
"Variables",
|
|
390
|
+
ApiItemKind.Variable,
|
|
391
|
+
"variable"
|
|
392
|
+
],
|
|
393
|
+
[
|
|
394
|
+
"Types",
|
|
395
|
+
ApiItemKind.TypeAlias,
|
|
396
|
+
"type"
|
|
397
|
+
],
|
|
398
|
+
[
|
|
399
|
+
"Enums",
|
|
400
|
+
ApiItemKind.Enum,
|
|
401
|
+
"enum"
|
|
402
|
+
],
|
|
403
|
+
[
|
|
404
|
+
"Namespaces",
|
|
405
|
+
ApiItemKind.Namespace,
|
|
406
|
+
"namespace"
|
|
407
|
+
]
|
|
408
|
+
];
|
|
409
|
+
/** A declaration abbreviated to its header — everything before the opening brace. */
|
|
410
|
+
function abbreviate(signature) {
|
|
411
|
+
const brace = signature.indexOf("{");
|
|
412
|
+
return brace === -1 ? signature : signature.substring(0, brace).trim();
|
|
413
|
+
}
|
|
414
|
+
function namespaceBody(input, apiNamespace) {
|
|
415
|
+
const { packageName, linker, baseRoute } = input;
|
|
416
|
+
const name = apiNamespace.displayName;
|
|
417
|
+
const lines = [`namespace ${name} {`];
|
|
418
|
+
const bodied = /* @__PURE__ */ new Set([
|
|
419
|
+
ApiItemKind.Class,
|
|
420
|
+
ApiItemKind.Interface,
|
|
421
|
+
ApiItemKind.Enum,
|
|
422
|
+
ApiItemKind.Namespace
|
|
423
|
+
]);
|
|
424
|
+
for (const [, kind] of NAMESPACE_SECTIONS) for (const member of apiNamespace.members) {
|
|
425
|
+
if (member.kind !== kind) continue;
|
|
426
|
+
const signature = excerptOf(member);
|
|
427
|
+
if (!signature) continue;
|
|
428
|
+
lines.push(bodied.has(kind) ? ` ${abbreviate(signature)} { }` : ` ${signature}`);
|
|
429
|
+
}
|
|
430
|
+
lines.push("}");
|
|
431
|
+
const blocks = [Signature$1.make({ code: codeText(withHiddenImports(lines.join("\n"), apiNamespace, apiNamespace, packageName)) })];
|
|
432
|
+
for (const [title, kind, folder] of NAMESPACE_SECTIONS) {
|
|
433
|
+
const members = apiNamespace.members.filter((member) => member.kind === kind);
|
|
434
|
+
if (members.length === 0) continue;
|
|
435
|
+
blocks.push(MemberIndex.make({
|
|
436
|
+
title,
|
|
437
|
+
entries: members.map((member) => {
|
|
438
|
+
const summary = Tsdoc.summary(member);
|
|
439
|
+
return MemberIndexEntry.make({
|
|
440
|
+
name: member.displayName,
|
|
441
|
+
route: `${baseRoute}/${folder}/${`${name}.${member.displayName}`.toLowerCase()}`,
|
|
442
|
+
...summary ? { summary: linked(linker, summary) } : {}
|
|
443
|
+
});
|
|
444
|
+
})
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
return blocks;
|
|
448
|
+
}
|
|
449
|
+
/** The page kind for a supported item kind, or none for anything else. */
|
|
450
|
+
function pageKindOf(kind) {
|
|
451
|
+
switch (kind) {
|
|
452
|
+
case ApiItemKind.Class: return Option.some("class");
|
|
453
|
+
case ApiItemKind.Interface: return Option.some("interface");
|
|
454
|
+
case ApiItemKind.Function: return Option.some("function");
|
|
455
|
+
case ApiItemKind.TypeAlias: return Option.some("type-alias");
|
|
456
|
+
case ApiItemKind.Enum: return Option.some("enum");
|
|
457
|
+
case ApiItemKind.Variable: return Option.some("variable");
|
|
458
|
+
case ApiItemKind.Namespace: return Option.some("namespace");
|
|
459
|
+
default: return Option.none();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Whether {@link buildPage} produces a page for an item of this kind.
|
|
464
|
+
*
|
|
465
|
+
* @public
|
|
466
|
+
*/
|
|
467
|
+
function isPageKind(kind) {
|
|
468
|
+
return Option.isSome(pageKindOf(kind));
|
|
469
|
+
}
|
|
470
|
+
function bodyFor(input, kind) {
|
|
471
|
+
switch (kind) {
|
|
472
|
+
case "class": return classBody(input, input.item);
|
|
473
|
+
case "interface": return interfaceBody(input, input.item);
|
|
474
|
+
case "function": return functionBody(input);
|
|
475
|
+
case "enum": return enumBody(input, input.item);
|
|
476
|
+
case "namespace": return namespaceBody(input, input.item);
|
|
477
|
+
case "type-alias":
|
|
478
|
+
case "variable": return declarationBody(input);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Build the page for one item, or none for an item kind that gets no page.
|
|
483
|
+
*
|
|
484
|
+
* @remarks
|
|
485
|
+
* The route is `{baseRoute}/{folderName}/{name}` with the name lowercased;
|
|
486
|
+
* a namespace member's last segment is its lowercased qualified name, which
|
|
487
|
+
* is also its sidebar label. Only Prettier can fail here, and that failure
|
|
488
|
+
* degrades through {@link BuildPageInput.onExampleFormatError}, so the
|
|
489
|
+
* error channel is `never`.
|
|
490
|
+
*
|
|
491
|
+
* @public
|
|
492
|
+
*/
|
|
493
|
+
const buildPage = Effect.fn("Build.buildPage")(function* (input) {
|
|
494
|
+
const kind = pageKindOf(input.item.kind);
|
|
495
|
+
if (Option.isNone(kind)) return Option.none();
|
|
496
|
+
const { item } = input;
|
|
497
|
+
const summary = Tsdoc.summary(item) || "No description available.";
|
|
498
|
+
const label = input.namespaceMember ? input.namespaceMember.qualifiedName : item.displayName;
|
|
499
|
+
const name = label.toLowerCase();
|
|
500
|
+
const route = `${input.baseRoute}/${input.folderName}/${name}`;
|
|
501
|
+
const blocks = [
|
|
502
|
+
...headBlocks(input, summary),
|
|
503
|
+
...bodyFor(input, kind.value),
|
|
504
|
+
...yield* tailBlocks(input)
|
|
505
|
+
];
|
|
506
|
+
return Option.some(Page.make({
|
|
507
|
+
kind: kind.value,
|
|
508
|
+
entityName: item.displayName,
|
|
509
|
+
singularName: input.singularName,
|
|
510
|
+
...input.apiName !== void 0 ? { apiName: input.apiName } : {},
|
|
511
|
+
description: summary,
|
|
512
|
+
route,
|
|
513
|
+
headTags: input.headTags ?? [],
|
|
514
|
+
blocks,
|
|
515
|
+
nav: NavEntry.make({
|
|
516
|
+
categoryKey: input.categoryKey,
|
|
517
|
+
label,
|
|
518
|
+
name,
|
|
519
|
+
route
|
|
520
|
+
})
|
|
521
|
+
}));
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
//#endregion
|
|
525
|
+
export { INDEX_PAGE_TITLE, IndexPage, NO_DESCRIPTION, buildIndexPage, buildPage, isPageKind };
|