sourcey 3.5.10 → 3.6.1
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 +24 -97
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +130 -0
- package/dist/adapters/mkdocs.d.ts +5 -0
- package/dist/adapters/mkdocs.d.ts.map +1 -0
- package/dist/adapters/mkdocs.js +367 -0
- package/dist/adapters/shared.d.ts +12 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +134 -0
- package/dist/adapters/types.d.ts +74 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +28 -34
- package/dist/client/search.js +213 -82
- package/dist/client/tabs.js +23 -0
- package/dist/components/layout/Head.js +17 -4
- package/dist/components/layout/TableOfContents.d.ts.map +1 -1
- package/dist/components/layout/TableOfContents.js +4 -2
- 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 +101 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +95 -143
- 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.map +1 -1
- package/dist/core/doxygen-loader.js +36 -17
- 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 +14 -0
- package/dist/core/markdown-loader.d.ts.map +1 -1
- package/dist/core/markdown-loader.js +70 -26
- package/dist/core/mcp-normalizer.d.ts.map +1 -1
- package/dist/core/mcp-normalizer.js +48 -34
- package/dist/core/navigation.d.ts.map +1 -1
- package/dist/core/navigation.js +8 -7
- 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 +469 -0
- package/dist/core/rustdoc-render.d.ts +76 -0
- package/dist/core/rustdoc-render.d.ts.map +1 -0
- package/dist/core/rustdoc-render.js +785 -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.d.ts +10 -0
- package/dist/core/search-indexer.d.ts.map +1 -1
- package/dist/core/search-indexer.js +6 -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 +109 -33
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -14
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +21 -16
- package/dist/renderer/changelog-feed.d.ts.map +1 -1
- package/dist/renderer/changelog-feed.js +27 -19
- 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 +161 -25
- 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 +115 -39
- package/dist/vite-plugin.d.ts +2 -0
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +71 -1
- package/package.json +11 -5
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
2
|
+
|
|
3
|
+
use rustdoc_types::{Crate, Id, Item as RdItem, ItemEnum, StructKind, VariantKind, Visibility as RdVisibility};
|
|
4
|
+
|
|
5
|
+
use crate::diagnostics::{self, codes};
|
|
6
|
+
use crate::doctest::extract_doctests;
|
|
7
|
+
use crate::links::{format_id, LinkContext};
|
|
8
|
+
use crate::signature::{lower_generics, lower_signature, render_type};
|
|
9
|
+
use crate::spec::{
|
|
10
|
+
AssocConstItem, AssocTypeItem, ConstantItem, CrateSpec, Deprecation, Diagnostic, EnumItem,
|
|
11
|
+
FunctionItem, ImplItem, Item, ItemId, ItemInner, LinkTarget, MacroItem, ModuleSpec,
|
|
12
|
+
ProcMacroItem, ProcMacroKindKind, SourceLocation, Stability, StabilityLevel, StaticItem,
|
|
13
|
+
StructFieldItem, StructItem, StructKindKind, TraitAliasItem, TraitItem, TypeAliasItem,
|
|
14
|
+
TypePath, UnionItem, UseItem, VariantItem, VariantKindKind, Visibility,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
pub struct ExtractOptions {
|
|
18
|
+
pub include_private: bool,
|
|
19
|
+
pub include_hidden: bool,
|
|
20
|
+
pub crate_name_hint: String,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub struct ExtractResult {
|
|
24
|
+
pub crate_spec: CrateSpec,
|
|
25
|
+
pub diagnostics: Vec<Diagnostic>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn extract_crate(krate: &Crate, opts: &ExtractOptions) -> ExtractResult {
|
|
29
|
+
let links = LinkContext::new(krate);
|
|
30
|
+
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
|
31
|
+
let mut items: BTreeMap<String, Item> = BTreeMap::new();
|
|
32
|
+
let mut modules: Vec<ModuleSpec> = Vec::new();
|
|
33
|
+
let mut emitted: HashSet<ItemId> = HashSet::new();
|
|
34
|
+
|
|
35
|
+
for (id, rd_item) in &krate.index {
|
|
36
|
+
if !should_emit(rd_item, opts) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
let item_id = ItemId(format_id(id));
|
|
40
|
+
if !emitted.insert(item_id.clone()) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
match &rd_item.inner {
|
|
44
|
+
ItemEnum::Module(m) => {
|
|
45
|
+
modules.push(lower_module(id, rd_item, m, krate));
|
|
46
|
+
// Also surface modules as items so per-module sidebar entries,
|
|
47
|
+
// search categories, and parent-module pages can link to
|
|
48
|
+
// their children.
|
|
49
|
+
if let Some(item) = lower_item(id, rd_item, krate, &links, &mut diagnostics) {
|
|
50
|
+
items.insert(item.id.0.clone(), item);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
_ => {
|
|
54
|
+
if let Some(item) = lower_item(id, rd_item, krate, &links, &mut diagnostics) {
|
|
55
|
+
items.insert(item.id.0.clone(), item);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let root_module_id = ItemId(format_id(&krate.root));
|
|
62
|
+
// Prefer the user-supplied crate name (matches Cargo.toml `[package].name`,
|
|
63
|
+
// hyphens preserved). Fall back to rustdoc's lib target name (underscored)
|
|
64
|
+
// when no hint is set.
|
|
65
|
+
let crate_name = if opts.crate_name_hint.is_empty() {
|
|
66
|
+
krate
|
|
67
|
+
.index
|
|
68
|
+
.get(&krate.root)
|
|
69
|
+
.and_then(|i| i.name.clone())
|
|
70
|
+
.unwrap_or_default()
|
|
71
|
+
} else {
|
|
72
|
+
opts.crate_name_hint.clone()
|
|
73
|
+
};
|
|
74
|
+
let crate_version = krate.crate_version.clone();
|
|
75
|
+
|
|
76
|
+
let crate_spec = CrateSpec {
|
|
77
|
+
name: crate_name,
|
|
78
|
+
version: crate_version,
|
|
79
|
+
root_module_id,
|
|
80
|
+
modules,
|
|
81
|
+
items,
|
|
82
|
+
external_crates: links.external_crates(),
|
|
83
|
+
diagnostics: diagnostics.clone(),
|
|
84
|
+
};
|
|
85
|
+
if diagnostics.iter().any(|d| d.code == codes::INTRA_DOC_LINK_UNRESOLVED) {
|
|
86
|
+
// Carry intra-doc-link diagnostics into the crate-level diagnostics
|
|
87
|
+
// (already in `crate_spec.diagnostics`); we return the same set in the
|
|
88
|
+
// top-level summary for caller-level visibility.
|
|
89
|
+
}
|
|
90
|
+
ExtractResult {
|
|
91
|
+
crate_spec,
|
|
92
|
+
diagnostics,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn should_emit(item: &RdItem, opts: &ExtractOptions) -> bool {
|
|
97
|
+
if !opts.include_hidden {
|
|
98
|
+
for attr in &item.attrs {
|
|
99
|
+
if let rustdoc_types::Attribute::Other(s) = attr {
|
|
100
|
+
if s.contains("doc(hidden)") {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if opts.include_private {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Without --document-private-items, rustdoc's `index` only contains
|
|
110
|
+
// items it considers documented (public, or implicitly public via a
|
|
111
|
+
// public parent). Most items inside impl blocks, trait assoc items,
|
|
112
|
+
// enum variants, and struct fields carry `Default` visibility but are
|
|
113
|
+
// still public because their parent is. Trust rustdoc's filtering and
|
|
114
|
+
// only drop explicit `Crate`/`Restricted` visibility.
|
|
115
|
+
match &item.visibility {
|
|
116
|
+
RdVisibility::Public | RdVisibility::Default => true,
|
|
117
|
+
RdVisibility::Crate | RdVisibility::Restricted { .. } => false,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn lower_module(id: &Id, item: &RdItem, m: &rustdoc_types::Module, krate: &Crate) -> ModuleSpec {
|
|
122
|
+
let path = path_for_id(id, krate);
|
|
123
|
+
let item_ids: Vec<ItemId> = m.items.iter().map(|i| ItemId(format_id(i))).collect();
|
|
124
|
+
let sub_module_paths: Vec<Vec<String>> = m
|
|
125
|
+
.items
|
|
126
|
+
.iter()
|
|
127
|
+
.filter_map(|i| krate.index.get(i))
|
|
128
|
+
.filter(|child| matches!(child.inner, ItemEnum::Module(_)))
|
|
129
|
+
.map(|child| {
|
|
130
|
+
let mut p = path.clone();
|
|
131
|
+
if let Some(name) = &child.name {
|
|
132
|
+
p.push(name.clone());
|
|
133
|
+
}
|
|
134
|
+
p
|
|
135
|
+
})
|
|
136
|
+
.collect();
|
|
137
|
+
ModuleSpec {
|
|
138
|
+
id: ItemId(format_id(id)),
|
|
139
|
+
path,
|
|
140
|
+
docs_markdown: item.docs.clone(),
|
|
141
|
+
doc_aliases: extract_doc_aliases(item),
|
|
142
|
+
item_ids,
|
|
143
|
+
sub_module_paths,
|
|
144
|
+
source: lower_source(item),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn lower_item(
|
|
149
|
+
id: &Id,
|
|
150
|
+
rd_item: &RdItem,
|
|
151
|
+
krate: &Crate,
|
|
152
|
+
links: &LinkContext<'_>,
|
|
153
|
+
diagnostics: &mut Vec<Diagnostic>,
|
|
154
|
+
) -> Option<Item> {
|
|
155
|
+
let item_id = ItemId(format_id(id));
|
|
156
|
+
let inner = lower_inner(rd_item, krate, links, diagnostics)?;
|
|
157
|
+
let docs_markdown = rd_item.docs.clone();
|
|
158
|
+
let doctests = docs_markdown
|
|
159
|
+
.as_deref()
|
|
160
|
+
.map(extract_doctests)
|
|
161
|
+
.unwrap_or_default();
|
|
162
|
+
let item = Item {
|
|
163
|
+
id: item_id,
|
|
164
|
+
name: rd_item.name.clone(),
|
|
165
|
+
path: path_for_id(id, krate),
|
|
166
|
+
visibility: lower_visibility(&rd_item.visibility),
|
|
167
|
+
source: lower_source(rd_item),
|
|
168
|
+
docs_markdown,
|
|
169
|
+
doc_aliases: extract_doc_aliases(rd_item),
|
|
170
|
+
deprecation: rd_item.deprecation.as_ref().map(|d| Deprecation {
|
|
171
|
+
since: d.since.clone(),
|
|
172
|
+
note: d.note.clone(),
|
|
173
|
+
}),
|
|
174
|
+
stability: extract_stability(rd_item),
|
|
175
|
+
feature_gates: extract_feature_gates(rd_item),
|
|
176
|
+
attrs_structured: extract_structured_attrs(rd_item),
|
|
177
|
+
links: rd_item
|
|
178
|
+
.links
|
|
179
|
+
.iter()
|
|
180
|
+
.map(|(label, id)| {
|
|
181
|
+
let target = links.resolve_id(id).map(|resolved| {
|
|
182
|
+
if resolved.external {
|
|
183
|
+
LinkTarget::External {
|
|
184
|
+
crate_name: links
|
|
185
|
+
.external_crate_index
|
|
186
|
+
.get(&resolved.crate_id)
|
|
187
|
+
.map(|ec| ec.name.clone())
|
|
188
|
+
.unwrap_or_default(),
|
|
189
|
+
path: resolved.path,
|
|
190
|
+
html_root_url: resolved.html_root_url,
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
LinkTarget::Internal { id: resolved.id }
|
|
194
|
+
}
|
|
195
|
+
}).unwrap_or(LinkTarget::Internal { id: ItemId(format_id(id)) });
|
|
196
|
+
(label.clone(), target)
|
|
197
|
+
})
|
|
198
|
+
.collect(),
|
|
199
|
+
inner,
|
|
200
|
+
doctests,
|
|
201
|
+
};
|
|
202
|
+
Some(item)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn lower_inner(
|
|
206
|
+
rd_item: &RdItem,
|
|
207
|
+
krate: &Crate,
|
|
208
|
+
links: &LinkContext<'_>,
|
|
209
|
+
diagnostics: &mut Vec<Diagnostic>,
|
|
210
|
+
) -> Option<ItemInner> {
|
|
211
|
+
let inner = match &rd_item.inner {
|
|
212
|
+
ItemEnum::Function(f) => ItemInner::Function(FunctionItem {
|
|
213
|
+
signature: lower_signature(
|
|
214
|
+
&f.sig,
|
|
215
|
+
rd_item.name.as_deref().unwrap_or(""),
|
|
216
|
+
f.header.is_const,
|
|
217
|
+
f.header.is_async,
|
|
218
|
+
f.header.is_unsafe,
|
|
219
|
+
links,
|
|
220
|
+
),
|
|
221
|
+
generics: lower_generics(&f.generics),
|
|
222
|
+
is_const: f.header.is_const,
|
|
223
|
+
is_async: f.header.is_async,
|
|
224
|
+
is_unsafe: f.header.is_unsafe,
|
|
225
|
+
has_body: f.has_body,
|
|
226
|
+
}),
|
|
227
|
+
ItemEnum::Struct(s) => ItemInner::Struct(StructItem {
|
|
228
|
+
struct_kind: match &s.kind {
|
|
229
|
+
StructKind::Plain { .. } => StructKindKind::Plain,
|
|
230
|
+
StructKind::Tuple(_) => StructKindKind::Tuple,
|
|
231
|
+
StructKind::Unit => StructKindKind::Unit,
|
|
232
|
+
},
|
|
233
|
+
generics: lower_generics(&s.generics),
|
|
234
|
+
fields: extract_struct_fields(&s.kind),
|
|
235
|
+
has_stripped_fields: matches!(
|
|
236
|
+
&s.kind,
|
|
237
|
+
StructKind::Plain {
|
|
238
|
+
has_stripped_fields: true,
|
|
239
|
+
..
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
impls: s.impls.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
243
|
+
}),
|
|
244
|
+
ItemEnum::Enum(e) => ItemInner::Enum(EnumItem {
|
|
245
|
+
generics: lower_generics(&e.generics),
|
|
246
|
+
variants: e.variants.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
247
|
+
has_stripped_variants: e.has_stripped_variants,
|
|
248
|
+
impls: e.impls.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
249
|
+
}),
|
|
250
|
+
ItemEnum::Variant(v) => ItemInner::Variant(VariantItem {
|
|
251
|
+
variant_kind: match &v.kind {
|
|
252
|
+
VariantKind::Plain => VariantKindKind::Plain,
|
|
253
|
+
VariantKind::Tuple(_) => VariantKindKind::Tuple,
|
|
254
|
+
VariantKind::Struct { .. } => VariantKindKind::Struct,
|
|
255
|
+
},
|
|
256
|
+
discriminant: v.discriminant.as_ref().map(|d| d.expr.clone()),
|
|
257
|
+
}),
|
|
258
|
+
ItemEnum::Union(u) => ItemInner::Union(UnionItem {
|
|
259
|
+
generics: lower_generics(&u.generics),
|
|
260
|
+
fields: u.fields.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
261
|
+
has_stripped_fields: u.has_stripped_fields,
|
|
262
|
+
impls: u.impls.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
263
|
+
}),
|
|
264
|
+
ItemEnum::Trait(t) => ItemInner::Trait(TraitItem {
|
|
265
|
+
is_auto: t.is_auto,
|
|
266
|
+
is_unsafe: t.is_unsafe,
|
|
267
|
+
is_dyn_compatible: t.is_dyn_compatible,
|
|
268
|
+
generics: lower_generics(&t.generics),
|
|
269
|
+
bounds: t.bounds.iter().map(crate::signature::render_bound).collect(),
|
|
270
|
+
items: t.items.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
271
|
+
implementations: t
|
|
272
|
+
.implementations
|
|
273
|
+
.iter()
|
|
274
|
+
.map(|i| ItemId(format_id(i)))
|
|
275
|
+
.collect(),
|
|
276
|
+
}),
|
|
277
|
+
ItemEnum::TraitAlias(a) => ItemInner::TraitAlias(TraitAliasItem {
|
|
278
|
+
generics: lower_generics(&a.generics),
|
|
279
|
+
bounds: a.params.iter().map(crate::signature::render_bound).collect(),
|
|
280
|
+
}),
|
|
281
|
+
ItemEnum::Impl(i) => ItemInner::Impl(ImplItem {
|
|
282
|
+
generics: lower_generics(&i.generics),
|
|
283
|
+
trait_path: i.trait_.as_ref().map(|p| type_path_from_resolved(p, links)),
|
|
284
|
+
for_type: type_path_for_type(&i.for_, links),
|
|
285
|
+
items: i.items.iter().map(|x| ItemId(format_id(x))).collect(),
|
|
286
|
+
is_negative: i.is_negative,
|
|
287
|
+
is_synthetic: i.is_synthetic,
|
|
288
|
+
is_blanket: i.blanket_impl.is_some(),
|
|
289
|
+
provided_trait_methods: i.provided_trait_methods.clone(),
|
|
290
|
+
}),
|
|
291
|
+
ItemEnum::TypeAlias(a) => ItemInner::TypeAlias(TypeAliasItem {
|
|
292
|
+
aliased_type: type_path_for_type(&a.type_, links),
|
|
293
|
+
generics: lower_generics(&a.generics),
|
|
294
|
+
}),
|
|
295
|
+
ItemEnum::Constant { type_, const_ } => ItemInner::Constant(ConstantItem {
|
|
296
|
+
type_display: render_type(type_),
|
|
297
|
+
expr: const_.expr.clone(),
|
|
298
|
+
value: const_.value.clone(),
|
|
299
|
+
is_literal: const_.is_literal,
|
|
300
|
+
}),
|
|
301
|
+
ItemEnum::Static(s) => ItemInner::Static(StaticItem {
|
|
302
|
+
type_display: render_type(&s.type_),
|
|
303
|
+
expr: s.expr.clone(),
|
|
304
|
+
is_mutable: s.is_mutable,
|
|
305
|
+
is_unsafe: s.is_unsafe,
|
|
306
|
+
}),
|
|
307
|
+
ItemEnum::Macro(source) => ItemInner::Macro(MacroItem {
|
|
308
|
+
source: source.clone(),
|
|
309
|
+
}),
|
|
310
|
+
ItemEnum::ProcMacro(pm) => ItemInner::ProcMacro(ProcMacroItem {
|
|
311
|
+
macro_kind: match pm.kind {
|
|
312
|
+
rustdoc_types::MacroKind::Bang => ProcMacroKindKind::Bang,
|
|
313
|
+
rustdoc_types::MacroKind::Attr => ProcMacroKindKind::Attr,
|
|
314
|
+
rustdoc_types::MacroKind::Derive => ProcMacroKindKind::Derive,
|
|
315
|
+
},
|
|
316
|
+
helpers: pm.helpers.clone(),
|
|
317
|
+
}),
|
|
318
|
+
ItemEnum::AssocType {
|
|
319
|
+
generics,
|
|
320
|
+
bounds,
|
|
321
|
+
type_,
|
|
322
|
+
} => ItemInner::AssocType(AssocTypeItem {
|
|
323
|
+
generics: lower_generics(generics),
|
|
324
|
+
bounds: bounds.iter().map(crate::signature::render_bound).collect(),
|
|
325
|
+
default_display: type_.as_ref().map(render_type),
|
|
326
|
+
}),
|
|
327
|
+
ItemEnum::AssocConst { type_, value } => ItemInner::AssocConst(AssocConstItem {
|
|
328
|
+
type_display: render_type(type_),
|
|
329
|
+
default_display: value.clone(),
|
|
330
|
+
}),
|
|
331
|
+
ItemEnum::Use(u) => ItemInner::Use(UseItem {
|
|
332
|
+
source: u.source.clone(),
|
|
333
|
+
name: u.name.clone(),
|
|
334
|
+
target_id: u.id.as_ref().map(|i| ItemId(format_id(i))),
|
|
335
|
+
is_glob: u.is_glob,
|
|
336
|
+
}),
|
|
337
|
+
ItemEnum::StructField(t) => ItemInner::StructField(StructFieldItem {
|
|
338
|
+
type_display: render_type(t),
|
|
339
|
+
}),
|
|
340
|
+
ItemEnum::Module(_) => ItemInner::Module,
|
|
341
|
+
ItemEnum::Primitive(_) => ItemInner::Primitive,
|
|
342
|
+
ItemEnum::ExternType => ItemInner::ExternType,
|
|
343
|
+
ItemEnum::ExternCrate { .. } => return None,
|
|
344
|
+
};
|
|
345
|
+
let _ = (krate, links, diagnostics);
|
|
346
|
+
Some(inner)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fn lower_visibility(v: &RdVisibility) -> Visibility {
|
|
350
|
+
match v {
|
|
351
|
+
RdVisibility::Public => Visibility::Public,
|
|
352
|
+
RdVisibility::Crate => Visibility::Crate,
|
|
353
|
+
RdVisibility::Restricted { path, .. } => Visibility::Restricted { path: path.clone() },
|
|
354
|
+
RdVisibility::Default => Visibility::Default,
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
fn lower_source(item: &RdItem) -> Option<SourceLocation> {
|
|
359
|
+
item.span.as_ref().map(|s| SourceLocation {
|
|
360
|
+
file: s.filename.to_string_lossy().into_owned(),
|
|
361
|
+
line_start: s.begin.0 as u32,
|
|
362
|
+
line_end: s.end.0 as u32,
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
fn extract_doc_aliases(item: &RdItem) -> Vec<String> {
|
|
367
|
+
let mut out: Vec<String> = Vec::new();
|
|
368
|
+
for attr in &item.attrs {
|
|
369
|
+
if let rustdoc_types::Attribute::Other(s) = attr {
|
|
370
|
+
if let Some(rest) = s.strip_prefix("#[doc(alias = \"") {
|
|
371
|
+
if let Some(end) = rest.find("\")]") {
|
|
372
|
+
out.push(rest[..end].to_string());
|
|
373
|
+
}
|
|
374
|
+
} else if let Some(rest) = s.strip_prefix("#[doc(alias(") {
|
|
375
|
+
if let Some(end) = rest.find("))]") {
|
|
376
|
+
for raw in rest[..end].split(',') {
|
|
377
|
+
let trimmed = raw.trim().trim_matches('"');
|
|
378
|
+
if !trimmed.is_empty() {
|
|
379
|
+
out.push(trimmed.to_string());
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
out
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fn extract_stability(item: &RdItem) -> Option<Stability> {
|
|
390
|
+
for attr in &item.attrs {
|
|
391
|
+
if let rustdoc_types::Attribute::Other(s) = attr {
|
|
392
|
+
if s.starts_with("#[stable(") {
|
|
393
|
+
return Some(Stability {
|
|
394
|
+
level: StabilityLevel::Stable,
|
|
395
|
+
since: extract_attr_string(s, "since"),
|
|
396
|
+
feature: extract_attr_string(s, "feature"),
|
|
397
|
+
issue: None,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
if s.starts_with("#[unstable(") {
|
|
401
|
+
return Some(Stability {
|
|
402
|
+
level: StabilityLevel::Unstable,
|
|
403
|
+
since: None,
|
|
404
|
+
feature: extract_attr_string(s, "feature"),
|
|
405
|
+
issue: extract_attr_string(s, "issue")
|
|
406
|
+
.and_then(|i| i.trim_matches(|c: char| !c.is_ascii_digit()).parse().ok()),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
None
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
fn extract_attr_string(s: &str, key: &str) -> Option<String> {
|
|
415
|
+
let needle = format!("{} = \"", key);
|
|
416
|
+
let start = s.find(&needle)? + needle.len();
|
|
417
|
+
let rest = &s[start..];
|
|
418
|
+
let end = rest.find('"')?;
|
|
419
|
+
Some(rest[..end].to_string())
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
fn extract_feature_gates(_item: &RdItem) -> Vec<String> {
|
|
423
|
+
// Phase 1: feature gates are not directly carried on items in rustdoc JSON
|
|
424
|
+
// until the renderer reconstructs them from cfg attrs. Placeholder so the
|
|
425
|
+
// field exists in the schema and Phase 3 rendering can fill it in.
|
|
426
|
+
Vec::new()
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
fn extract_structured_attrs(item: &RdItem) -> Vec<String> {
|
|
430
|
+
item.attrs
|
|
431
|
+
.iter()
|
|
432
|
+
.map(|attr| match attr {
|
|
433
|
+
rustdoc_types::Attribute::Other(s) => s.clone(),
|
|
434
|
+
other => format!("{:?}", other),
|
|
435
|
+
})
|
|
436
|
+
.collect()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
fn extract_struct_fields(kind: &StructKind) -> Vec<ItemId> {
|
|
440
|
+
match kind {
|
|
441
|
+
StructKind::Plain { fields, .. } => fields.iter().map(|i| ItemId(format_id(i))).collect(),
|
|
442
|
+
StructKind::Tuple(ids) => ids
|
|
443
|
+
.iter()
|
|
444
|
+
.filter_map(|maybe| maybe.as_ref().map(|i| ItemId(format_id(i))))
|
|
445
|
+
.collect(),
|
|
446
|
+
StructKind::Unit => Vec::new(),
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
fn type_path_from_resolved(p: &rustdoc_types::Path, links: &LinkContext<'_>) -> TypePath {
|
|
451
|
+
let resolved = links.resolve_id(&p.id);
|
|
452
|
+
TypePath {
|
|
453
|
+
crate_id: resolved.as_ref().map(|r| r.crate_id).unwrap_or(0),
|
|
454
|
+
path: resolved.map(|r| r.path).unwrap_or_else(|| vec![p.path.clone()]),
|
|
455
|
+
display: p.path.clone(),
|
|
456
|
+
external: false,
|
|
457
|
+
html_root_url: None,
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
fn type_path_for_type(ty: &rustdoc_types::Type, links: &LinkContext<'_>) -> TypePath {
|
|
462
|
+
if let rustdoc_types::Type::ResolvedPath(p) = ty {
|
|
463
|
+
return type_path_from_resolved(p, links);
|
|
464
|
+
}
|
|
465
|
+
TypePath {
|
|
466
|
+
crate_id: 0,
|
|
467
|
+
path: vec![render_type(ty)],
|
|
468
|
+
display: render_type(ty),
|
|
469
|
+
external: false,
|
|
470
|
+
html_root_url: None,
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
fn path_for_id(id: &Id, krate: &Crate) -> Vec<String> {
|
|
475
|
+
if let Some(summary) = krate.paths.get(id) {
|
|
476
|
+
return summary.path.clone();
|
|
477
|
+
}
|
|
478
|
+
if let Some(item) = krate.index.get(id) {
|
|
479
|
+
if let Some(name) = &item.name {
|
|
480
|
+
return vec![name.clone()];
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
Vec::new()
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
#[allow(dead_code)]
|
|
487
|
+
fn unused_diagnostics_marker() -> Diagnostic {
|
|
488
|
+
diagnostics::warning(codes::INTRA_DOC_LINK_UNRESOLVED, "marker")
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Keep imports referenced even though the immediate use is in helper modules.
|
|
492
|
+
const _: fn(&HashMap<u32, ()>) = |_| {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//! Convert nightly rustdoc JSON into a stable `RustdocSpec` snapshot.
|
|
2
|
+
//!
|
|
3
|
+
//! `sourcey-rustdoc` powers the `rustdoc()` source adapter in [sourcey], the
|
|
4
|
+
//! static documentation generator. It is published as a standalone crate so
|
|
5
|
+
//! any tool that wants a stable, versioned representation of a Rust crate's
|
|
6
|
+
//! API surface can consume the same schema sourcey uses internally.
|
|
7
|
+
//!
|
|
8
|
+
//! [sourcey]: https://sourcey.com
|
|
9
|
+
//!
|
|
10
|
+
//! # Pipeline
|
|
11
|
+
//!
|
|
12
|
+
//! The high-level flow is:
|
|
13
|
+
//!
|
|
14
|
+
//! 1. Run nightly rustdoc against a Cargo manifest and capture its JSON
|
|
15
|
+
//! output (use the [`rustdoc-json`](https://crates.io/crates/rustdoc-json)
|
|
16
|
+
//! crate for this step).
|
|
17
|
+
//! 2. Deserialize into [`rustdoc_types::Crate`] using a matching
|
|
18
|
+
//! `rustdoc-types` version.
|
|
19
|
+
//! 3. Walk the crate through [`extract::extract_crate`] to produce a
|
|
20
|
+
//! [`RustdocSpec`] tree: modules, items, signature tokens, intra-doc
|
|
21
|
+
//! link table, extracted doctests, and resolved external-crate
|
|
22
|
+
//! references.
|
|
23
|
+
//! 4. Serialize the spec to JSON for downstream consumers (sourcey's
|
|
24
|
+
//! `rustdoc()` adapter, alternative renderers, semver-checkers, etc.).
|
|
25
|
+
//!
|
|
26
|
+
//! The bundled `sourcey-rustdoc` binary performs all four steps for the
|
|
27
|
+
//! common case. Library callers can drive each step directly.
|
|
28
|
+
//!
|
|
29
|
+
//! # Schema stability
|
|
30
|
+
//!
|
|
31
|
+
//! [`SPEC_VERSION`] is the schema's major version. Consumers should
|
|
32
|
+
//! check `RustdocSpec.version` on load and bail with a clear remedy when
|
|
33
|
+
//! it does not match.
|
|
34
|
+
//!
|
|
35
|
+
//! # Examples
|
|
36
|
+
//!
|
|
37
|
+
//! ```no_run
|
|
38
|
+
//! use sourcey_rustdoc::{
|
|
39
|
+
//! extract::{extract_crate, ExtractOptions},
|
|
40
|
+
//! RustdocSpec, SPEC_VERSION,
|
|
41
|
+
//! };
|
|
42
|
+
//!
|
|
43
|
+
//! fn read_snapshot(path: &str) -> serde_json::Result<RustdocSpec> {
|
|
44
|
+
//! let bytes = std::fs::read(path).expect("snapshot");
|
|
45
|
+
//! let spec: RustdocSpec = serde_json::from_slice(&bytes)?;
|
|
46
|
+
//! assert_eq!(spec.version, SPEC_VERSION, "schema version mismatch");
|
|
47
|
+
//! Ok(spec)
|
|
48
|
+
//! }
|
|
49
|
+
//! ```
|
|
50
|
+
|
|
51
|
+
pub mod diagnostics;
|
|
52
|
+
pub mod doctest;
|
|
53
|
+
pub mod extract;
|
|
54
|
+
pub mod links;
|
|
55
|
+
pub mod signature;
|
|
56
|
+
pub mod spec;
|
|
57
|
+
|
|
58
|
+
pub use spec::{RustdocSpec, SOURCEY_RUSTDOC_VERSION, SPEC_VERSION};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
use rustdoc_types::{Crate, Id, ItemKind};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
use crate::spec::{ExternalCrateRef, ItemId};
|
|
5
|
+
|
|
6
|
+
/// Build a lookup table for intra-doc link resolution.
|
|
7
|
+
/// Given an Item.links entry (label -> Id), we want the full path so the
|
|
8
|
+
/// renderer can produce a sourcey URL or fall back to an external one.
|
|
9
|
+
pub struct LinkContext<'a> {
|
|
10
|
+
pub krate: &'a Crate,
|
|
11
|
+
pub external_crate_index: HashMap<u32, ExternalCrateRef>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl<'a> LinkContext<'a> {
|
|
15
|
+
pub fn new(krate: &'a Crate) -> Self {
|
|
16
|
+
let external_crate_index = krate
|
|
17
|
+
.external_crates
|
|
18
|
+
.iter()
|
|
19
|
+
.map(|(crate_id, ec)| {
|
|
20
|
+
(
|
|
21
|
+
*crate_id,
|
|
22
|
+
ExternalCrateRef {
|
|
23
|
+
crate_id: *crate_id,
|
|
24
|
+
name: ec.name.clone(),
|
|
25
|
+
html_root_url: ec.html_root_url.clone(),
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
.collect();
|
|
30
|
+
Self {
|
|
31
|
+
krate,
|
|
32
|
+
external_crate_index,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn external_crates(&self) -> Vec<ExternalCrateRef> {
|
|
37
|
+
let mut out: Vec<_> = self.external_crate_index.values().cloned().collect();
|
|
38
|
+
out.sort_by(|a, b| a.crate_id.cmp(&b.crate_id));
|
|
39
|
+
out
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn resolve_id(&self, id: &Id) -> Option<ResolvedLink> {
|
|
43
|
+
if let Some(summary) = self.krate.paths.get(id) {
|
|
44
|
+
let crate_id = summary.crate_id;
|
|
45
|
+
let path = summary.path.clone();
|
|
46
|
+
let kind = summary.kind.clone();
|
|
47
|
+
let external = crate_id != 0;
|
|
48
|
+
let html_root_url = if external {
|
|
49
|
+
self.external_crate_index
|
|
50
|
+
.get(&crate_id)
|
|
51
|
+
.and_then(|ec| ec.html_root_url.clone())
|
|
52
|
+
} else {
|
|
53
|
+
None
|
|
54
|
+
};
|
|
55
|
+
return Some(ResolvedLink {
|
|
56
|
+
id: ItemId(format_id(id)),
|
|
57
|
+
crate_id,
|
|
58
|
+
path,
|
|
59
|
+
kind,
|
|
60
|
+
external,
|
|
61
|
+
html_root_url,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if let Some(item) = self.krate.index.get(id) {
|
|
65
|
+
// Sub-items (struct fields, variants, methods) frequently lack a
|
|
66
|
+
// paths entry. Best effort: surface what we know.
|
|
67
|
+
return Some(ResolvedLink {
|
|
68
|
+
id: ItemId(format_id(id)),
|
|
69
|
+
crate_id: item.crate_id,
|
|
70
|
+
path: item.name.clone().map(|n| vec![n]).unwrap_or_default(),
|
|
71
|
+
kind: classify_inner_kind(item),
|
|
72
|
+
external: item.crate_id != 0,
|
|
73
|
+
html_root_url: self
|
|
74
|
+
.external_crate_index
|
|
75
|
+
.get(&item.crate_id)
|
|
76
|
+
.and_then(|ec| ec.html_root_url.clone()),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
None
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[derive(Debug, Clone)]
|
|
84
|
+
pub struct ResolvedLink {
|
|
85
|
+
pub id: ItemId,
|
|
86
|
+
pub crate_id: u32,
|
|
87
|
+
pub path: Vec<String>,
|
|
88
|
+
pub kind: ItemKind,
|
|
89
|
+
pub external: bool,
|
|
90
|
+
pub html_root_url: Option<String>,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn format_id(id: &Id) -> String {
|
|
94
|
+
format!("{}", id.0)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn classify_inner_kind(item: &rustdoc_types::Item) -> ItemKind {
|
|
98
|
+
use rustdoc_types::ItemEnum;
|
|
99
|
+
match &item.inner {
|
|
100
|
+
ItemEnum::Module(_) => ItemKind::Module,
|
|
101
|
+
ItemEnum::Struct(_) => ItemKind::Struct,
|
|
102
|
+
ItemEnum::StructField(_) => ItemKind::StructField,
|
|
103
|
+
ItemEnum::Union(_) => ItemKind::Union,
|
|
104
|
+
ItemEnum::Enum(_) => ItemKind::Enum,
|
|
105
|
+
ItemEnum::Variant(_) => ItemKind::Variant,
|
|
106
|
+
ItemEnum::Function(_) => ItemKind::Function,
|
|
107
|
+
ItemEnum::Trait(_) => ItemKind::Trait,
|
|
108
|
+
ItemEnum::TraitAlias(_) => ItemKind::TraitAlias,
|
|
109
|
+
ItemEnum::Impl(_) => ItemKind::Impl,
|
|
110
|
+
ItemEnum::TypeAlias(_) => ItemKind::TypeAlias,
|
|
111
|
+
ItemEnum::Constant { .. } => ItemKind::Constant,
|
|
112
|
+
ItemEnum::Static(_) => ItemKind::Static,
|
|
113
|
+
ItemEnum::Macro(_) => ItemKind::Macro,
|
|
114
|
+
ItemEnum::ProcMacro(pm) => match pm.kind {
|
|
115
|
+
rustdoc_types::MacroKind::Bang => ItemKind::Macro,
|
|
116
|
+
rustdoc_types::MacroKind::Attr => ItemKind::ProcAttribute,
|
|
117
|
+
rustdoc_types::MacroKind::Derive => ItemKind::ProcDerive,
|
|
118
|
+
},
|
|
119
|
+
ItemEnum::AssocConst { .. } => ItemKind::AssocConst,
|
|
120
|
+
ItemEnum::AssocType { .. } => ItemKind::AssocType,
|
|
121
|
+
ItemEnum::ExternCrate { .. } => ItemKind::ExternCrate,
|
|
122
|
+
ItemEnum::Use(_) => ItemKind::Use,
|
|
123
|
+
ItemEnum::Primitive(_) => ItemKind::Primitive,
|
|
124
|
+
ItemEnum::ExternType => ItemKind::ExternType,
|
|
125
|
+
}
|
|
126
|
+
}
|