sourcey 3.6.0 → 3.6.2
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/README.md +5 -2
- package/dist/adapters/index.d.ts +3 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +35 -3
- package/dist/adapters/types.d.ts +8 -2
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cli.js +28 -34
- package/dist/client/search.js +181 -119
- package/dist/client/tabs.js +23 -0
- package/dist/components/layout/Head.d.ts.map +1 -1
- package/dist/components/layout/Head.js +7 -6
- package/dist/components/openapi/Introduction.d.ts.map +1 -1
- package/dist/components/openapi/Introduction.js +16 -1
- package/dist/components/openapi/Security.d.ts.map +1 -1
- package/dist/components/openapi/Security.js +18 -2
- package/dist/config.d.ts +70 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -7
- package/dist/core/api-rendering.d.ts +47 -0
- package/dist/core/api-rendering.d.ts.map +1 -0
- package/dist/core/api-rendering.js +76 -0
- package/dist/core/doxygen-loader.d.ts +1 -1
- package/dist/core/doxygen-loader.d.ts.map +1 -1
- package/dist/core/doxygen-loader.js +37 -19
- package/dist/core/godoc-loader.d.ts.map +1 -1
- package/dist/core/godoc-loader.js +17 -6
- package/dist/core/markdown-loader.d.ts.map +1 -1
- package/dist/core/markdown-loader.js +8 -5
- package/dist/core/mcp-normalizer.d.ts.map +1 -1
- package/dist/core/mcp-normalizer.js +48 -34
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +75 -4
- package/dist/core/rustdoc-introspector.d.ts +29 -0
- package/dist/core/rustdoc-introspector.d.ts.map +1 -0
- package/dist/core/rustdoc-introspector.js +236 -0
- package/dist/core/rustdoc-loader.d.ts +25 -0
- package/dist/core/rustdoc-loader.d.ts.map +1 -0
- package/dist/core/rustdoc-loader.js +472 -0
- package/dist/core/rustdoc-render.d.ts +82 -0
- package/dist/core/rustdoc-render.d.ts.map +1 -0
- package/dist/core/rustdoc-render.js +808 -0
- package/dist/core/rustdoc-types.d.ts +314 -0
- package/dist/core/rustdoc-types.d.ts.map +1 -0
- package/dist/core/rustdoc-types.js +18 -0
- package/dist/core/search-indexer.js +1 -1
- package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
- package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
- package/dist/core/sourcey-rustdoc/README.md +59 -0
- package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
- package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
- package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
- package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
- package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
- package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
- package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
- package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
- package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +50 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -13
- package/dist/init.js +2 -2
- package/dist/renderer/changelog-feed.d.ts.map +1 -1
- package/dist/renderer/changelog-feed.js +27 -19
- package/dist/renderer/context.d.ts +1 -0
- package/dist/renderer/context.d.ts.map +1 -1
- package/dist/renderer/html-builder.d.ts.map +1 -1
- package/dist/renderer/html-builder.js +4 -3
- package/dist/renderer/llms.js +57 -2
- package/dist/site-assembly.d.ts +5 -0
- package/dist/site-assembly.d.ts.map +1 -1
- package/dist/site-assembly.js +57 -2
- package/dist/themes/default/sourcey.css +300 -0
- package/dist/utils/code-samples.d.ts +1 -0
- package/dist/utils/code-samples.d.ts.map +1 -1
- package/dist/utils/code-samples.js +30 -22
- package/dist/utils/html.d.ts +10 -0
- package/dist/utils/html.d.ts.map +1 -0
- package/dist/utils/html.js +152 -0
- package/dist/utils/http.d.ts +0 -2
- package/dist/utils/http.d.ts.map +1 -1
- package/dist/utils/http.js +19 -34
- package/dist/utils/markdown.d.ts.map +1 -1
- package/dist/utils/markdown.js +102 -37
- package/package.json +10 -5
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rustdoc-grade rendering for the sourcey rustdoc adapter.
|
|
3
|
+
*
|
|
4
|
+
* Renders sourcey-native output (the same look as the godoc/doxygen/openapi
|
|
5
|
+
* adapters), not a copy of rustdoc's own DOM. Items render inline as sourcey
|
|
6
|
+
* sections; the only collapsible is the trait-implementor list. Code blocks go
|
|
7
|
+
* through sourcey's shared Shiki pipeline. Anchors still follow rustdoc's
|
|
8
|
+
* scheme (#method.<name>, #impl-Trait-for-Type%3CK%3E) so inbound deep-links
|
|
9
|
+
* from a rustdoc URL keep working.
|
|
10
|
+
*/
|
|
11
|
+
import { renderMarkdown, renderCodeBlock } from "../utils/markdown.js";
|
|
12
|
+
import { apiImplToggle, apiItemInfoRow, apiSectionAnchor, apiStabilityCallout, apiSymbolLink, escapeAttr, escapeHtml, } from "./api-rendering.js";
|
|
13
|
+
import { RUSTDOC_DIAGNOSTIC_CODES } from "./rustdoc-types.js";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Anchor encoding
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Encode a parametric impl anchor in rustdoc's URL-encoded form, e.g.
|
|
19
|
+
* impl-Clone-for-HashMap%3CK,+V,+S,+A%3E
|
|
20
|
+
*/
|
|
21
|
+
export function encodeImplAnchor(traitName, forType) {
|
|
22
|
+
return `impl-${encodeGenericSegment(traitName)}-for-${encodeGenericSegment(forType)}`;
|
|
23
|
+
}
|
|
24
|
+
/** Encode a single segment for anchor-ID use: <,>,space and special chars escaped rustdoc-style. */
|
|
25
|
+
export function encodeGenericSegment(value) {
|
|
26
|
+
return value.replace(/</g, "%3C").replace(/>/g, "%3E").replace(/, /g, ",+").replace(/ /g, "+");
|
|
27
|
+
}
|
|
28
|
+
export function itemAnchor(item) {
|
|
29
|
+
switch (item.inner.kind) {
|
|
30
|
+
case "function":
|
|
31
|
+
return `fn.${slug(item.name)}`;
|
|
32
|
+
case "struct":
|
|
33
|
+
return `struct.${slug(item.name)}`;
|
|
34
|
+
case "enum":
|
|
35
|
+
return `enum.${slug(item.name)}`;
|
|
36
|
+
case "variant":
|
|
37
|
+
return `variant.${slug(item.name)}`;
|
|
38
|
+
case "union":
|
|
39
|
+
return `union.${slug(item.name)}`;
|
|
40
|
+
case "trait":
|
|
41
|
+
return `trait.${slug(item.name)}`;
|
|
42
|
+
case "trait_alias":
|
|
43
|
+
return `traitalias.${slug(item.name)}`;
|
|
44
|
+
case "type_alias":
|
|
45
|
+
return `type.${slug(item.name)}`;
|
|
46
|
+
case "constant":
|
|
47
|
+
return `constant.${slug(item.name)}`;
|
|
48
|
+
case "static":
|
|
49
|
+
return `static.${slug(item.name)}`;
|
|
50
|
+
case "macro":
|
|
51
|
+
return `macro.${slug(item.name)}`;
|
|
52
|
+
case "proc_macro":
|
|
53
|
+
return `macro.${slug(item.name)}`;
|
|
54
|
+
case "assoc_type":
|
|
55
|
+
return `associatedtype.${slug(item.name)}`;
|
|
56
|
+
case "assoc_const":
|
|
57
|
+
return `associatedconstant.${slug(item.name)}`;
|
|
58
|
+
case "struct_field":
|
|
59
|
+
return `structfield.${slug(item.name)}`;
|
|
60
|
+
case "use":
|
|
61
|
+
return `reexport.${slug(useBaseName(item.inner))}`;
|
|
62
|
+
case "module":
|
|
63
|
+
return `mod.${slug(item.name)}`;
|
|
64
|
+
case "impl":
|
|
65
|
+
return implAnchor(item);
|
|
66
|
+
case "primitive":
|
|
67
|
+
return `primitive.${slug(item.name)}`;
|
|
68
|
+
case "extern_type":
|
|
69
|
+
return `externtype.${slug(item.name)}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function implAnchor(item) {
|
|
73
|
+
if (item.inner.kind !== "impl")
|
|
74
|
+
return `impl-${slug(item.name)}`;
|
|
75
|
+
const traitDisplay = item.inner.trait_path?.display ?? "";
|
|
76
|
+
const forDisplay = item.inner.for_type.display;
|
|
77
|
+
if (traitDisplay)
|
|
78
|
+
return encodeImplAnchor(traitDisplay, forDisplay);
|
|
79
|
+
return `impl-${encodeGenericSegment(forDisplay)}`;
|
|
80
|
+
}
|
|
81
|
+
function slug(name) {
|
|
82
|
+
if (!name)
|
|
83
|
+
return "";
|
|
84
|
+
return name;
|
|
85
|
+
}
|
|
86
|
+
/** Base name of a re-export: the imported name, or the last path segment. */
|
|
87
|
+
function useBaseName(inner) {
|
|
88
|
+
return inner.name || (inner.source.split("::").filter(Boolean).pop() ?? inner.source);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Heading/anchor display name for an item. Re-exports (`use`) carry their name
|
|
92
|
+
* on `inner`, not the top-level `item.name` which rustdoc leaves null, so we
|
|
93
|
+
* derive it from the imported name or the re-exported path (with `::*` for globs).
|
|
94
|
+
*/
|
|
95
|
+
export function itemDisplayName(item) {
|
|
96
|
+
if (item.inner.kind === "use") {
|
|
97
|
+
const base = useBaseName(item.inner);
|
|
98
|
+
return item.inner.is_glob ? `${base}::*` : base;
|
|
99
|
+
}
|
|
100
|
+
return item.name ?? "";
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Signature formatter
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
const SIGNATURE_INLINE_LIMIT = 100;
|
|
106
|
+
/** Multi-line signature renderer with rustdoc break rules. */
|
|
107
|
+
export function renderSignature(signature, ctx) {
|
|
108
|
+
const inline = renderInlineSignature(signature, ctx);
|
|
109
|
+
if (signature.display.length <= SIGNATURE_INLINE_LIMIT) {
|
|
110
|
+
return `<span class="code-header rust-signature">${inline}</span>`;
|
|
111
|
+
}
|
|
112
|
+
return `<span class="code-header rust-signature">${renderMultilineSignature(signature, ctx)}</span>`;
|
|
113
|
+
}
|
|
114
|
+
function renderInlineSignature(signature, ctx) {
|
|
115
|
+
const parts = [];
|
|
116
|
+
for (const token of signature.tokens) {
|
|
117
|
+
parts.push(renderToken(token, ctx));
|
|
118
|
+
}
|
|
119
|
+
return parts.join("");
|
|
120
|
+
}
|
|
121
|
+
function renderMultilineSignature(signature, ctx) {
|
|
122
|
+
// Heuristic: break each input onto its own line; place `where` if present on
|
|
123
|
+
// its own line. Keeps in sync with rustdoc's visual cadence without
|
|
124
|
+
// requiring an AST.
|
|
125
|
+
let head = "";
|
|
126
|
+
const inputs = [];
|
|
127
|
+
let output = "";
|
|
128
|
+
let seenOpenParen = false;
|
|
129
|
+
let seenCloseParen = false;
|
|
130
|
+
let currentInput = "";
|
|
131
|
+
for (const token of signature.tokens) {
|
|
132
|
+
const rendered = renderToken(token, ctx);
|
|
133
|
+
if (!seenOpenParen) {
|
|
134
|
+
head += rendered;
|
|
135
|
+
if (token.kind === "punct" && token.text === "(")
|
|
136
|
+
seenOpenParen = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!seenCloseParen) {
|
|
140
|
+
if (token.kind === "punct" && token.text === ")") {
|
|
141
|
+
if (currentInput.trim().length > 0)
|
|
142
|
+
inputs.push(currentInput);
|
|
143
|
+
currentInput = "";
|
|
144
|
+
seenCloseParen = true;
|
|
145
|
+
output = rendered;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (token.kind === "punct" && token.text === "," && currentInput.trim().length > 0) {
|
|
149
|
+
inputs.push(currentInput.trimEnd());
|
|
150
|
+
currentInput = "";
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
currentInput += rendered;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
output += rendered;
|
|
157
|
+
}
|
|
158
|
+
const inputsBlock = inputs
|
|
159
|
+
.map((line, idx) => ` ${line.trimStart()}${idx === inputs.length - 1 ? "" : ","}`)
|
|
160
|
+
.join("\n");
|
|
161
|
+
return `${head}\n${inputsBlock}\n${output}`;
|
|
162
|
+
}
|
|
163
|
+
function renderToken(token, ctx) {
|
|
164
|
+
switch (token.kind) {
|
|
165
|
+
case "keyword":
|
|
166
|
+
return `<span class="kw">${escapeHtml(token.text)}</span>`;
|
|
167
|
+
case "punct":
|
|
168
|
+
return escapeHtml(token.text);
|
|
169
|
+
case "generic":
|
|
170
|
+
return `<span class="generic">${escapeHtml(token.text)}</span>`;
|
|
171
|
+
case "lifetime":
|
|
172
|
+
return `<span class="lifetime">'${escapeHtml(token.text)}</span>`;
|
|
173
|
+
case "type": {
|
|
174
|
+
const target = token.target;
|
|
175
|
+
if (target) {
|
|
176
|
+
const href = typePathHref(target, ctx);
|
|
177
|
+
if (href) {
|
|
178
|
+
return apiSymbolLink({
|
|
179
|
+
kind: typeLinkClass(target),
|
|
180
|
+
href,
|
|
181
|
+
text: token.text,
|
|
182
|
+
title: target.path.join("::"),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return `<span class="ident">${escapeHtml(token.text)}</span>`;
|
|
187
|
+
}
|
|
188
|
+
case "whitespace":
|
|
189
|
+
return " ";
|
|
190
|
+
case "newline":
|
|
191
|
+
return "\n";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function typeLinkClass(target) {
|
|
195
|
+
return target.external ? "rust-extern" : "rust-symbol";
|
|
196
|
+
}
|
|
197
|
+
function typePathHref(target, ctx) {
|
|
198
|
+
if (!target.external) {
|
|
199
|
+
if (target.path.length === 0)
|
|
200
|
+
return null;
|
|
201
|
+
const name = target.path[target.path.length - 1];
|
|
202
|
+
// Find the actual item in the snapshot so we can use ITS path (which
|
|
203
|
+
// tells us which module page it lives on), not the inbound target.path
|
|
204
|
+
// which may be a re-export shortcut.
|
|
205
|
+
const candidate = [...ctx.itemsById.values()].find((i) => i.name === name && i.path.join("::") === target.path.join("::")) ?? [...ctx.itemsById.values()].find((i) => i.name === name);
|
|
206
|
+
if (candidate) {
|
|
207
|
+
const crateName = candidate.path[0] ?? ctx.crate.name;
|
|
208
|
+
const modulePath = candidate.path.slice(1, -1);
|
|
209
|
+
const anchorFragment = itemAnchor(candidate);
|
|
210
|
+
return tabHref(ctx.tabSlug, pageSlugFor(crateName, modulePath)) + `#${anchorFragment}`;
|
|
211
|
+
}
|
|
212
|
+
// Last resort: guess from the inbound path.
|
|
213
|
+
const crateName = target.path[0] === ctx.crate.name ? ctx.crate.name : target.path[0];
|
|
214
|
+
const modulePath = target.path.slice(1, -1);
|
|
215
|
+
return (tabHref(ctx.tabSlug, pageSlugFor(crateName, modulePath)) + `#${encodeURIComponent(name)}`);
|
|
216
|
+
}
|
|
217
|
+
if (target.html_root_url) {
|
|
218
|
+
return joinUrl(target.html_root_url, target.path.join("/"));
|
|
219
|
+
}
|
|
220
|
+
return joinUrl("https://docs.rs", `${target.path[0] ?? ""}/latest/${target.path.join("/")}`);
|
|
221
|
+
}
|
|
222
|
+
function tabHref(tabSlug, slug) {
|
|
223
|
+
if (!slug)
|
|
224
|
+
return `${tabSlug}/`;
|
|
225
|
+
return `${tabSlug}/${slug}.html`;
|
|
226
|
+
}
|
|
227
|
+
function pageSlugFor(crateName, modulePath) {
|
|
228
|
+
const base = `pkg-${slugifyId(crateName)}`;
|
|
229
|
+
if (modulePath.length === 0)
|
|
230
|
+
return base;
|
|
231
|
+
return `${base}-${modulePath.map(slugifyId).join("-")}`;
|
|
232
|
+
}
|
|
233
|
+
function slugifyId(value) {
|
|
234
|
+
return value
|
|
235
|
+
.toLowerCase()
|
|
236
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
237
|
+
.replace(/^-+|-+$/g, "");
|
|
238
|
+
}
|
|
239
|
+
function joinUrl(base, suffix) {
|
|
240
|
+
const trimmedBase = base.replace(/\/+$/, "");
|
|
241
|
+
const trimmedSuffix = suffix.replace(/^\/+/, "");
|
|
242
|
+
return `${trimmedBase}/${trimmedSuffix}`;
|
|
243
|
+
}
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Intra-doc link resolver
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
/**
|
|
248
|
+
* Rewrite markdown intra-doc links using the rustdoc `Item.links` map.
|
|
249
|
+
*
|
|
250
|
+
* `Item.links` is a label→item-id map. We look up each link target in the
|
|
251
|
+
* markdown, substitute either an internal sourcey URL (for items in this
|
|
252
|
+
* crate) or a docs.rs / doc.rust-lang.org URL (for cross-crate items),
|
|
253
|
+
* leaving plain prose untouched. Unresolved labels render as plain text
|
|
254
|
+
* with `title="unresolved intra-doc link"`.
|
|
255
|
+
*/
|
|
256
|
+
export function resolveIntraDocLinks(markdown, links, ctx) {
|
|
257
|
+
const unresolved = [];
|
|
258
|
+
// Match both `[label]` and ``[`label`]`` forms used by rustdoc intra-doc links.
|
|
259
|
+
// Match `[label]` or ``[`label`]`` but NOT `[label](url)` (regular markdown
|
|
260
|
+
// inline links keep their target URL).
|
|
261
|
+
const replaced = markdown.replace(/\[(`?)([^`\]\n]+)\1\](?:\[\])?(?!\()/g, (_match, _tick, label) => {
|
|
262
|
+
const target = lookupLinkTarget(links, label);
|
|
263
|
+
const display = `<code>${escapeHtml(label)}</code>`;
|
|
264
|
+
if (!target) {
|
|
265
|
+
unresolved.push(label);
|
|
266
|
+
return `<code title="unresolved intra-doc link">${escapeHtml(label)}</code>`;
|
|
267
|
+
}
|
|
268
|
+
if (target.kind === "internal") {
|
|
269
|
+
const item = ctx.itemsById.get(target.id);
|
|
270
|
+
if (!item) {
|
|
271
|
+
unresolved.push(label);
|
|
272
|
+
return `<code title="unresolved intra-doc link">${escapeHtml(label)}</code>`;
|
|
273
|
+
}
|
|
274
|
+
const crateName = item.path[0] ?? ctx.crate.name;
|
|
275
|
+
const modulePath = item.path.slice(1, -1);
|
|
276
|
+
const href = tabHref(ctx.tabSlug, pageSlugFor(crateName, modulePath)) + `#${itemAnchor(item)}`;
|
|
277
|
+
return `<a class="rust-symbol" href="${escapeAttr(href)}">${display}</a>`;
|
|
278
|
+
}
|
|
279
|
+
const externalHref = externalLinkHref(target);
|
|
280
|
+
return `<a class="rust-extern" href="${escapeAttr(externalHref)}" rel="noopener" target="_blank">${display}</a>`;
|
|
281
|
+
});
|
|
282
|
+
return { html: replaced, unresolved };
|
|
283
|
+
}
|
|
284
|
+
function lookupLinkTarget(links, label) {
|
|
285
|
+
// rustdoc's Item.links keys can include backticks, full paths, or aliases.
|
|
286
|
+
// Try common variants before giving up.
|
|
287
|
+
return links[label] ?? links[`\`${label}\``] ?? links[label.replace(/^`(.+)`$/, "$1")];
|
|
288
|
+
}
|
|
289
|
+
function externalLinkHref(target) {
|
|
290
|
+
const isStdLike = ["std", "core", "alloc", "proc_macro"].includes(target.crate_name);
|
|
291
|
+
if (isStdLike) {
|
|
292
|
+
return `https://doc.rust-lang.org/stable/${target.path.join("/")}`;
|
|
293
|
+
}
|
|
294
|
+
if (target.html_root_url) {
|
|
295
|
+
const trimmed = target.html_root_url.replace(/\/+$/, "");
|
|
296
|
+
return `${trimmed}/${target.path.join("/")}`;
|
|
297
|
+
}
|
|
298
|
+
return `https://docs.rs/${target.crate_name}/latest/${target.path.join("/")}`;
|
|
299
|
+
}
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// Stability + item-info row helpers
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
export function renderStabilityCallouts(item) {
|
|
304
|
+
const parts = [];
|
|
305
|
+
if (item.deprecation) {
|
|
306
|
+
parts.push(apiStabilityCallout({
|
|
307
|
+
kind: "deprecated",
|
|
308
|
+
since: item.deprecation.since,
|
|
309
|
+
reason: item.deprecation.note,
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
const stability = item.stability;
|
|
313
|
+
if (stability?.level === "unstable") {
|
|
314
|
+
parts.push(apiStabilityCallout({
|
|
315
|
+
kind: "unstable",
|
|
316
|
+
featureName: stability.feature,
|
|
317
|
+
issueId: stability.issue,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
for (const featureName of item.feature_gates) {
|
|
321
|
+
parts.push(apiStabilityCallout({ kind: "portability", featureName }));
|
|
322
|
+
}
|
|
323
|
+
if (hasAttr(item, "non_exhaustive")) {
|
|
324
|
+
parts.push(apiStabilityCallout({ kind: "non_exhaustive" }));
|
|
325
|
+
}
|
|
326
|
+
const mustUseReason = extractMustUseReason(item);
|
|
327
|
+
if (mustUseReason !== null) {
|
|
328
|
+
parts.push(apiStabilityCallout({
|
|
329
|
+
kind: "must_use",
|
|
330
|
+
reason: mustUseReason === "" ? undefined : mustUseReason,
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
return parts.join("\n");
|
|
334
|
+
}
|
|
335
|
+
function hasAttr(item, marker) {
|
|
336
|
+
return item.attrs_structured.some((a) => a.includes(marker));
|
|
337
|
+
}
|
|
338
|
+
function extractMustUseReason(item) {
|
|
339
|
+
for (const attr of item.attrs_structured) {
|
|
340
|
+
if (attr.startsWith("MustUse"))
|
|
341
|
+
return "";
|
|
342
|
+
if (attr.startsWith('#[must_use = "')) {
|
|
343
|
+
const m = attr.match(/^#\[must_use = "([^"]*)"\]/);
|
|
344
|
+
if (m)
|
|
345
|
+
return m[1];
|
|
346
|
+
return "";
|
|
347
|
+
}
|
|
348
|
+
if (attr === "#[must_use]")
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
export function renderItemInfoRow(item, ctx) {
|
|
354
|
+
const since = item.stability?.level === "stable" ? item.stability.since : null;
|
|
355
|
+
const sourceHref = renderSourceHref(item, ctx);
|
|
356
|
+
return apiItemInfoRow({ since: since ?? null, sourceHref });
|
|
357
|
+
}
|
|
358
|
+
export function renderSourceHref(item, ctx) {
|
|
359
|
+
if (!item.source)
|
|
360
|
+
return null;
|
|
361
|
+
const { repo, editBranch, editBasePath } = ctx.sourceLinks;
|
|
362
|
+
if (!repo || !editBranch)
|
|
363
|
+
return null;
|
|
364
|
+
const base = repo.replace(/\/+$/, "");
|
|
365
|
+
const basePath = editBasePath ? `${editBasePath.replace(/^\/+|\/+$/g, "")}/` : "";
|
|
366
|
+
return `${base}/blob/${editBranch}/${basePath}${item.source.file}#L${item.source.line_start}-L${item.source.line_end}`;
|
|
367
|
+
}
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// Item rendering
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
export function renderItemHtml(item, ctx) {
|
|
372
|
+
const anchor = itemAnchor(item);
|
|
373
|
+
const callouts = renderStabilityCallouts(item);
|
|
374
|
+
const infoRow = renderItemInfoRow(item, ctx);
|
|
375
|
+
const signatureHtml = renderItemSignature(item, ctx);
|
|
376
|
+
const docs = renderItemDocs(item, ctx);
|
|
377
|
+
const doctests = item.doctests.map((dt, idx) => renderDoctestBlock(item, dt, idx)).join("\n");
|
|
378
|
+
const impls = renderItemImpls(item, ctx);
|
|
379
|
+
return [
|
|
380
|
+
`<section class="rust-item rust-${item.inner.kind} api-item">`,
|
|
381
|
+
infoRow,
|
|
382
|
+
apiSectionAnchor({
|
|
383
|
+
level: 4,
|
|
384
|
+
id: anchor,
|
|
385
|
+
text: itemDisplayName(item) || anchor,
|
|
386
|
+
className: "code-header rust-item-header",
|
|
387
|
+
}),
|
|
388
|
+
signatureHtml,
|
|
389
|
+
callouts,
|
|
390
|
+
docs,
|
|
391
|
+
doctests,
|
|
392
|
+
impls,
|
|
393
|
+
`</section>`,
|
|
394
|
+
]
|
|
395
|
+
.filter(Boolean)
|
|
396
|
+
.join("\n");
|
|
397
|
+
}
|
|
398
|
+
function renderItemImpls(item, ctx) {
|
|
399
|
+
const traitOwnMembers = renderTraitOwnMembers(item, ctx);
|
|
400
|
+
const implIds = collectImplIds(item);
|
|
401
|
+
if (implIds.length === 0 && !traitOwnMembers)
|
|
402
|
+
return "";
|
|
403
|
+
// Inherent impls render inline (they carry the type's own API). Trait impls
|
|
404
|
+
// can be numerous and are mostly boilerplate, so they collapse behind one
|
|
405
|
+
// styled toggle each, default closed.
|
|
406
|
+
const inherent = [];
|
|
407
|
+
const traitImpls = [];
|
|
408
|
+
const traitRows = [];
|
|
409
|
+
for (const implId of implIds) {
|
|
410
|
+
const impl = ctx.itemsById.get(implId);
|
|
411
|
+
if (!impl || impl.inner.kind !== "impl")
|
|
412
|
+
continue;
|
|
413
|
+
const traitName = impl.inner.trait_path?.display;
|
|
414
|
+
const forName = impl.inner.for_type.display;
|
|
415
|
+
const header = traitName ? `impl ${traitName} for ${forName}` : `impl ${forName}`;
|
|
416
|
+
const implAnchorId = itemAnchor(impl);
|
|
417
|
+
const providedMethods = new Set(impl.inner.provided_trait_methods);
|
|
418
|
+
const methodSections = impl.inner.items
|
|
419
|
+
.map((mid) => ctx.itemsById.get(mid))
|
|
420
|
+
.filter((m) => Boolean(m))
|
|
421
|
+
.map((m) => {
|
|
422
|
+
const isRequired = item.inner.kind === "trait" &&
|
|
423
|
+
m.inner.kind === "function" &&
|
|
424
|
+
!providedMethods.has(m.name ?? "");
|
|
425
|
+
return renderImplMember(m, ctx, isRequired);
|
|
426
|
+
})
|
|
427
|
+
.filter(Boolean)
|
|
428
|
+
.join("\n");
|
|
429
|
+
// No <code> child: `.code-header` gives the mono font, and a bare <code>
|
|
430
|
+
// inside the prose container would render with backtick pseudo-elements.
|
|
431
|
+
const implHeader = `<h3 class="code-header rust-impl-header" id="${escapeAttr(implAnchorId)}">${escapeHtml(header)}</h3>`;
|
|
432
|
+
if (traitName) {
|
|
433
|
+
if (methodSections) {
|
|
434
|
+
// Substantive trait impls collapse behind a chevron.
|
|
435
|
+
traitImpls.push(apiImplToggle({ open: false, summary: implHeader, body: methodSections }));
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// Marker / auto / blanket traits (Send, Sync, From, …) carry no methods.
|
|
439
|
+
// Render them as a quiet, non-expandable row rather than an empty
|
|
440
|
+
// collapsible that opens onto nothing.
|
|
441
|
+
traitRows.push(`<div class="rust-impl-row">${implHeader}</div>`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else if (methodSections) {
|
|
445
|
+
inherent.push(implHeader + methodSections);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const parts = [];
|
|
449
|
+
if (traitOwnMembers)
|
|
450
|
+
parts.push(traitOwnMembers);
|
|
451
|
+
if (inherent.length > 0) {
|
|
452
|
+
parts.push(apiSectionAnchor({
|
|
453
|
+
level: 3,
|
|
454
|
+
id: `impls-for-${itemAnchor(item)}`,
|
|
455
|
+
text: "Implementations",
|
|
456
|
+
className: "rust-impls-header",
|
|
457
|
+
}));
|
|
458
|
+
parts.push(inherent.join("\n"));
|
|
459
|
+
}
|
|
460
|
+
if (traitImpls.length > 0 || traitRows.length > 0) {
|
|
461
|
+
parts.push(apiSectionAnchor({
|
|
462
|
+
level: 3,
|
|
463
|
+
id: `trait-impls-for-${itemAnchor(item)}`,
|
|
464
|
+
text: "Trait Implementations",
|
|
465
|
+
className: "rust-impls-header",
|
|
466
|
+
}));
|
|
467
|
+
if (traitImpls.length > 0)
|
|
468
|
+
parts.push(traitImpls.join("\n"));
|
|
469
|
+
if (traitRows.length > 0)
|
|
470
|
+
parts.push(`<div class="rust-impl-rows">${traitRows.join("\n")}</div>`);
|
|
471
|
+
}
|
|
472
|
+
if (parts.length === 0)
|
|
473
|
+
return "";
|
|
474
|
+
return parts.join("\n");
|
|
475
|
+
}
|
|
476
|
+
function renderTraitOwnMembers(item, ctx) {
|
|
477
|
+
if (item.inner.kind !== "trait")
|
|
478
|
+
return "";
|
|
479
|
+
const members = item.inner.items
|
|
480
|
+
.map((mid) => ctx.itemsById.get(mid))
|
|
481
|
+
.filter((m) => Boolean(m));
|
|
482
|
+
if (members.length === 0)
|
|
483
|
+
return "";
|
|
484
|
+
const required = [];
|
|
485
|
+
const provided = [];
|
|
486
|
+
const assocTypes = [];
|
|
487
|
+
const assocConsts = [];
|
|
488
|
+
for (const m of members) {
|
|
489
|
+
if (m.inner.kind === "function") {
|
|
490
|
+
// Functions without bodies on a trait are required; with bodies are provided.
|
|
491
|
+
(m.inner.has_body ? provided : required).push(m);
|
|
492
|
+
}
|
|
493
|
+
else if (m.inner.kind === "assoc_type") {
|
|
494
|
+
assocTypes.push(m);
|
|
495
|
+
}
|
|
496
|
+
else if (m.inner.kind === "assoc_const") {
|
|
497
|
+
assocConsts.push(m);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
const parts = [];
|
|
501
|
+
if (assocTypes.length > 0) {
|
|
502
|
+
parts.push(apiSectionAnchor({
|
|
503
|
+
level: 3,
|
|
504
|
+
id: "required-associated-types",
|
|
505
|
+
text: "Required Associated Types",
|
|
506
|
+
}));
|
|
507
|
+
parts.push(assocTypes.map((m) => renderImplMember(m, ctx, false)).join("\n"));
|
|
508
|
+
}
|
|
509
|
+
if (assocConsts.length > 0) {
|
|
510
|
+
parts.push(apiSectionAnchor({
|
|
511
|
+
level: 3,
|
|
512
|
+
id: "required-associated-consts",
|
|
513
|
+
text: "Required Associated Constants",
|
|
514
|
+
}));
|
|
515
|
+
parts.push(assocConsts.map((m) => renderImplMember(m, ctx, false)).join("\n"));
|
|
516
|
+
}
|
|
517
|
+
if (required.length > 0) {
|
|
518
|
+
parts.push(apiSectionAnchor({ level: 3, id: "required-methods", text: "Required Methods" }));
|
|
519
|
+
parts.push(required.map((m) => renderImplMember(m, ctx, true)).join("\n"));
|
|
520
|
+
}
|
|
521
|
+
if (provided.length > 0) {
|
|
522
|
+
parts.push(apiSectionAnchor({ level: 3, id: "provided-methods", text: "Provided Methods" }));
|
|
523
|
+
parts.push(provided.map((m) => renderImplMember(m, ctx, false)).join("\n"));
|
|
524
|
+
}
|
|
525
|
+
return parts.join("\n");
|
|
526
|
+
}
|
|
527
|
+
function implMemberAnchor(member, isRequiredTraitMethod) {
|
|
528
|
+
if (member.inner.kind === "function") {
|
|
529
|
+
const prefix = isRequiredTraitMethod ? "tymethod" : "method";
|
|
530
|
+
return `${prefix}.${member.name ?? ""}`;
|
|
531
|
+
}
|
|
532
|
+
if (member.inner.kind === "assoc_type")
|
|
533
|
+
return `associatedtype.${member.name ?? ""}`;
|
|
534
|
+
if (member.inner.kind === "assoc_const")
|
|
535
|
+
return `associatedconstant.${member.name ?? ""}`;
|
|
536
|
+
return itemAnchor(member);
|
|
537
|
+
}
|
|
538
|
+
function collectImplIds(item) {
|
|
539
|
+
switch (item.inner.kind) {
|
|
540
|
+
case "struct":
|
|
541
|
+
case "enum":
|
|
542
|
+
case "union":
|
|
543
|
+
return item.inner.impls;
|
|
544
|
+
case "trait":
|
|
545
|
+
// Implementors of the trait are rendered separately below. Trait's own
|
|
546
|
+
// members are handled by renderTraitOwnMembers().
|
|
547
|
+
return item.inner.implementations;
|
|
548
|
+
default:
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function renderImplMember(member, ctx, isRequiredTraitMethod = false) {
|
|
553
|
+
const anchor = implMemberAnchor(member, isRequiredTraitMethod);
|
|
554
|
+
const sigHtml = renderItemSignature(member, ctx);
|
|
555
|
+
const callouts = renderStabilityCallouts(member);
|
|
556
|
+
const docs = renderItemDocs(member, ctx);
|
|
557
|
+
const doctests = member.doctests.map((dt, idx) => renderDoctestBlock(member, dt, idx)).join("\n");
|
|
558
|
+
const infoRow = renderItemInfoRow(member, ctx);
|
|
559
|
+
const body = [
|
|
560
|
+
infoRow,
|
|
561
|
+
`<h4 class="code-header rust-method-header" id="${escapeAttr(anchor)}">${escapeHtml(member.name ?? "")}</h4>`,
|
|
562
|
+
sigHtml,
|
|
563
|
+
callouts,
|
|
564
|
+
docs,
|
|
565
|
+
doctests,
|
|
566
|
+
]
|
|
567
|
+
.filter(Boolean)
|
|
568
|
+
.join("\n");
|
|
569
|
+
if (!body)
|
|
570
|
+
return "";
|
|
571
|
+
return `<section class="rust-member api-member">${body}</section>`;
|
|
572
|
+
}
|
|
573
|
+
function renderItemDocs(item, ctx) {
|
|
574
|
+
if (!item.docs_markdown)
|
|
575
|
+
return "";
|
|
576
|
+
const links = item.links ?? {};
|
|
577
|
+
const { html, unresolved } = resolveIntraDocLinks(item.docs_markdown, links, ctx);
|
|
578
|
+
for (const label of unresolved) {
|
|
579
|
+
ctx.diagnostics.push({
|
|
580
|
+
severity: "info",
|
|
581
|
+
code: RUSTDOC_DIAGNOSTIC_CODES.INTRA_DOC_LINK_UNRESOLVED,
|
|
582
|
+
message: `Intra-doc link "${label}" in ${item.path.join("::")} could not be resolved.`,
|
|
583
|
+
crate_name: ctx.crate.name,
|
|
584
|
+
file: item.source?.file ?? null,
|
|
585
|
+
line: item.source?.line_start ?? null,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return `<div class="docblock rust-doc">${renderMarkdown(html)}</div>`;
|
|
589
|
+
}
|
|
590
|
+
function renderItemSignature(item, ctx) {
|
|
591
|
+
if (item.inner.kind === "function") {
|
|
592
|
+
return renderSignature(item.inner.signature, ctx);
|
|
593
|
+
}
|
|
594
|
+
const fallback = fallbackSignatureText(item);
|
|
595
|
+
if (!fallback)
|
|
596
|
+
return "";
|
|
597
|
+
// A <div>, not <pre><code>: inside the page's prose container a bare <pre>
|
|
598
|
+
// picks up the dark fenced-code-block treatment and a <code> gets backtick
|
|
599
|
+
// pseudo-elements. `.code-header` already supplies the mono font + wrapping.
|
|
600
|
+
return `<div class="code-header rust-signature rust-signature-fallback">${escapeHtml(fallback)}</div>`;
|
|
601
|
+
}
|
|
602
|
+
function fallbackSignatureText(item) {
|
|
603
|
+
const path = item.path.join("::");
|
|
604
|
+
switch (item.inner.kind) {
|
|
605
|
+
case "struct":
|
|
606
|
+
return `pub struct ${path} { /* ... */ }`;
|
|
607
|
+
case "enum":
|
|
608
|
+
return `pub enum ${path} { /* ... */ }`;
|
|
609
|
+
case "trait":
|
|
610
|
+
return `pub trait ${path} { /* ... */ }`;
|
|
611
|
+
case "trait_alias":
|
|
612
|
+
return `pub trait ${path};`;
|
|
613
|
+
case "type_alias":
|
|
614
|
+
return `pub type ${path} = ${item.inner.aliased_type.display};`;
|
|
615
|
+
case "constant":
|
|
616
|
+
return `pub const ${path}: ${item.inner.type_display} = ${item.inner.expr};`;
|
|
617
|
+
case "static":
|
|
618
|
+
return `pub${item.inner.is_mutable ? " mut" : ""} static ${path}: ${item.inner.type_display};`;
|
|
619
|
+
case "macro":
|
|
620
|
+
return `macro_rules! ${item.name ?? ""}`;
|
|
621
|
+
case "proc_macro":
|
|
622
|
+
return `#[${item.inner.macro_kind === "derive" ? "derive" : "proc_macro"}] ${item.name ?? ""}`;
|
|
623
|
+
case "use": {
|
|
624
|
+
const { source, name, is_glob } = item.inner;
|
|
625
|
+
if (is_glob)
|
|
626
|
+
return `pub use ${source}::*;`;
|
|
627
|
+
const lastSegment = source.split("::").filter(Boolean).pop();
|
|
628
|
+
return name && name !== lastSegment ? `pub use ${source} as ${name};` : `pub use ${source};`;
|
|
629
|
+
}
|
|
630
|
+
default:
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// ---------------------------------------------------------------------------
|
|
635
|
+
// Doctest blocks
|
|
636
|
+
// ---------------------------------------------------------------------------
|
|
637
|
+
const RUST_EDITION_DEFAULT = "2024";
|
|
638
|
+
export function renderDoctestBlock(parent, dt, idx) {
|
|
639
|
+
const anchor = `doctest-${itemAnchor(parent)}-${idx}`;
|
|
640
|
+
const isRust = dt.lang.toLowerCase() === "rust" || dt.lang === "";
|
|
641
|
+
const lang = isRust ? "rust" : dt.lang || "text";
|
|
642
|
+
const badges = renderDoctestBadges(dt);
|
|
643
|
+
const hasHidden = dt.display_code !== dt.executable_code;
|
|
644
|
+
const edition = pickEdition(dt.fence_attributes);
|
|
645
|
+
const playgroundHref = isRust && !dt.fence_attributes.includes("ignore")
|
|
646
|
+
? `https://play.rust-lang.org/?code=${encodeURIComponent(dt.executable_code)}&edition=${edition}`
|
|
647
|
+
: null;
|
|
648
|
+
const runButton = playgroundHref
|
|
649
|
+
? `<a class="test-arrow rust-doctest-run" href="${escapeAttr(playgroundHref)}" target="_blank" rel="noopener">Run</a>`
|
|
650
|
+
: "";
|
|
651
|
+
const hiddenToggle = hasHidden
|
|
652
|
+
? `<button class="rust-doctest-toggle-hidden" type="button" data-target="#${anchor}-full" data-display="#${anchor}-code">Show hidden lines</button>`
|
|
653
|
+
: "";
|
|
654
|
+
const controls = runButton || hiddenToggle
|
|
655
|
+
? `<div class="rust-doctest-controls">${runButton}${hiddenToggle}</div>`
|
|
656
|
+
: "";
|
|
657
|
+
return [
|
|
658
|
+
`<div class="rust-doctest api-doctest" id="${anchor}">`,
|
|
659
|
+
badges,
|
|
660
|
+
`<div class="rust-doctest-code" id="${anchor}-code">${renderCodeBlock(dt.display_code, lang)}</div>`,
|
|
661
|
+
hasHidden
|
|
662
|
+
? `<div class="rust-doctest-code rust-doctest-full" id="${anchor}-full" hidden>${renderCodeBlock(dt.executable_code, lang)}</div>`
|
|
663
|
+
: "",
|
|
664
|
+
controls,
|
|
665
|
+
`</div>`,
|
|
666
|
+
]
|
|
667
|
+
.filter(Boolean)
|
|
668
|
+
.join("\n");
|
|
669
|
+
}
|
|
670
|
+
export function renderDoctestBadges(dt) {
|
|
671
|
+
if (dt.fence_attributes.length === 0)
|
|
672
|
+
return "";
|
|
673
|
+
const badges = dt.fence_attributes
|
|
674
|
+
.map((a) => `<span class="rust-doctest-badge rust-doctest-badge-${escapeAttr(a.replace(/[^a-zA-Z0-9_-]/g, "_"))}" title="${escapeAttr(badgeTitle(a))}">${escapeHtml(a)}</span>`)
|
|
675
|
+
.join(" ");
|
|
676
|
+
return `<div class="rust-doctest-badges">${badges}</div>`;
|
|
677
|
+
}
|
|
678
|
+
function badgeTitle(attr) {
|
|
679
|
+
switch (attr) {
|
|
680
|
+
case "ignore":
|
|
681
|
+
return "Excluded from testing.";
|
|
682
|
+
case "no_run":
|
|
683
|
+
return "Compiled but not executed.";
|
|
684
|
+
case "should_panic":
|
|
685
|
+
return "Test expected to panic.";
|
|
686
|
+
case "compile_fail":
|
|
687
|
+
return "Test expected to fail to compile.";
|
|
688
|
+
case "edition2018":
|
|
689
|
+
case "edition2021":
|
|
690
|
+
case "edition2024":
|
|
691
|
+
return `Compiled with ${attr.replace("edition", "Rust edition ")}.`;
|
|
692
|
+
case "standalone_crate":
|
|
693
|
+
return "Compiled as a standalone crate.";
|
|
694
|
+
default:
|
|
695
|
+
return attr;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function pickEdition(attrs) {
|
|
699
|
+
for (const a of attrs) {
|
|
700
|
+
if (a === "edition2018")
|
|
701
|
+
return "2018";
|
|
702
|
+
if (a === "edition2021")
|
|
703
|
+
return "2021";
|
|
704
|
+
if (a === "edition2024")
|
|
705
|
+
return "2024";
|
|
706
|
+
}
|
|
707
|
+
return RUST_EDITION_DEFAULT;
|
|
708
|
+
}
|
|
709
|
+
export function renderModulePage(module, items, ctx) {
|
|
710
|
+
const parts = [];
|
|
711
|
+
if (module.docs_markdown) {
|
|
712
|
+
parts.push(`<div class="docblock rust-doc">${renderMarkdown(module.docs_markdown)}</div>`);
|
|
713
|
+
}
|
|
714
|
+
const grouped = groupItemsForRendering(items);
|
|
715
|
+
const sidebar = [];
|
|
716
|
+
for (const [group, members] of grouped) {
|
|
717
|
+
if (members.length === 0)
|
|
718
|
+
continue;
|
|
719
|
+
parts.push(apiSectionAnchor({ level: 2, id: groupAnchor(group), text: group }));
|
|
720
|
+
for (const m of members) {
|
|
721
|
+
const html = renderItemHtml(m, ctx);
|
|
722
|
+
if (!html)
|
|
723
|
+
continue;
|
|
724
|
+
parts.push(html);
|
|
725
|
+
}
|
|
726
|
+
sidebar.push({
|
|
727
|
+
label: group,
|
|
728
|
+
items: members
|
|
729
|
+
.map((m) => ({ text: itemDisplayName(m), anchor: itemAnchor(m) }))
|
|
730
|
+
.filter((entry) => entry.text)
|
|
731
|
+
.sort((a, b) => a.text.localeCompare(b.text)),
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
html: parts.join("\n"),
|
|
736
|
+
sidebarSections: sidebar,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
const GROUP_ORDER = [
|
|
740
|
+
"Modules",
|
|
741
|
+
"Macros",
|
|
742
|
+
"Structs",
|
|
743
|
+
"Enums",
|
|
744
|
+
"Unions",
|
|
745
|
+
"Traits",
|
|
746
|
+
"Trait Aliases",
|
|
747
|
+
"Type Aliases",
|
|
748
|
+
"Constants",
|
|
749
|
+
"Statics",
|
|
750
|
+
"Functions",
|
|
751
|
+
"Re-exports",
|
|
752
|
+
"Implementations",
|
|
753
|
+
];
|
|
754
|
+
function groupItemsForRendering(items) {
|
|
755
|
+
const out = new Map();
|
|
756
|
+
for (const g of GROUP_ORDER)
|
|
757
|
+
out.set(g, []);
|
|
758
|
+
for (const item of items) {
|
|
759
|
+
const group = groupForItem(item);
|
|
760
|
+
if (group)
|
|
761
|
+
out.get(group).push(item);
|
|
762
|
+
}
|
|
763
|
+
return out;
|
|
764
|
+
}
|
|
765
|
+
function groupForItem(item) {
|
|
766
|
+
switch (item.inner.kind) {
|
|
767
|
+
case "module":
|
|
768
|
+
return "Modules";
|
|
769
|
+
case "macro":
|
|
770
|
+
case "proc_macro":
|
|
771
|
+
return "Macros";
|
|
772
|
+
case "struct":
|
|
773
|
+
return "Structs";
|
|
774
|
+
case "enum":
|
|
775
|
+
return "Enums";
|
|
776
|
+
case "union":
|
|
777
|
+
return "Unions";
|
|
778
|
+
case "trait":
|
|
779
|
+
return "Traits";
|
|
780
|
+
case "trait_alias":
|
|
781
|
+
return "Trait Aliases";
|
|
782
|
+
case "type_alias":
|
|
783
|
+
return "Type Aliases";
|
|
784
|
+
case "constant":
|
|
785
|
+
return "Constants";
|
|
786
|
+
case "static":
|
|
787
|
+
return "Statics";
|
|
788
|
+
case "function":
|
|
789
|
+
return "Functions";
|
|
790
|
+
case "use":
|
|
791
|
+
return "Re-exports";
|
|
792
|
+
case "impl":
|
|
793
|
+
return "Implementations";
|
|
794
|
+
default:
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function groupAnchor(group) {
|
|
799
|
+
return group.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
800
|
+
}
|
|
801
|
+
// Re-export for unit tests.
|
|
802
|
+
export const __internals = {
|
|
803
|
+
encodeImplAnchor,
|
|
804
|
+
encodeGenericSegment,
|
|
805
|
+
itemAnchor,
|
|
806
|
+
renderDoctestBadges,
|
|
807
|
+
pickEdition,
|
|
808
|
+
};
|