mod.rs - source (original) (raw)
rustdoc/json/
mod.rs
1//! Rustdoc's JSON backend
2//!
3//! This module contains the logic for rendering a crate as JSON rather than the normal static HTML
4//! output. See [the RFC](https://github.com/rust-lang/rfcs/pull/2963) and the [`types`] module
5//! docs for usage and details.
6
7mod conversions;
8mod ids;
9mod import_finder;
10
11use std::cell::RefCell;
12use std::fs::{File, create_dir_all};
13use std::io::{BufWriter, Write, stdout};
14use std::path::PathBuf;
15use std::rc::Rc;
16
17use rustc_data_structures::fx::FxHashSet;
18use rustc_hir::def_id::{DefId, DefIdSet};
19use rustc_middle::ty::TyCtxt;
20use rustc_session::Session;
21use rustc_span::def_id::LOCAL_CRATE;
22use rustdoc_json_types as types;
23// It's important to use the FxHashMap from rustdoc_json_types here, instead of
24// the one from rustc_data_structures, as they're different types due to sysroots.
25// See #110051 and #127456 for details
26use rustdoc_json_types::FxHashMap;
27use tracing::{debug, trace};
28
29use crate::clean::ItemKind;
30use crate::clean::types::{ExternalCrate, ExternalLocation};
31use crate::config::RenderOptions;
32use crate::docfs::PathError;
33use crate::error::Error;
34use crate::formats::FormatRenderer;
35use crate::formats::cache::Cache;
36use crate::json::conversions::IntoJson;
37use crate::{clean, try_err};
38
39#[derive(Clone)]
40pub(crate) struct JsonRenderer<'tcx> {
41 tcx: TyCtxt<'tcx>,
42 /// A mapping of IDs that contains all local items for this crate which gets output as a top
43 /// level field of the JSON blob.
44 index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>,
45 /// The directory where the JSON blob should be written to.
46 ///
47 /// If this is `None`, the blob will be printed to `stdout` instead.
48 out_dir: Option<PathBuf>,
49 cache: Rc<Cache>,
50 imported_items: DefIdSet,
51 id_interner: Rc<RefCell<ids::IdInterner>>,
52}
53
54impl<'tcx> JsonRenderer<'tcx> {
55 fn sess(&self) -> &'tcx Session {
56 self.tcx.sess
57 }
58
59 fn get_trait_implementors(&mut self, id: DefId) -> Vec<types::Id> {
60 Rc::clone(&self.cache)
61 .implementors
62 .get(&id)
63 .map(|implementors| {
64 implementors
65 .iter()
66 .map(|i| {
67 let item = &i.impl_item;
68 self.item(item.clone()).unwrap();
69 self.id_from_item(item)
70 })
71 .collect()
72 })
73 .unwrap_or_default()
74 }
75
76 fn get_impls(&mut self, id: DefId) -> Vec<types::Id> {
77 Rc::clone(&self.cache)
78 .impls
79 .get(&id)
80 .map(|impls| {
81 impls
82 .iter()
83 .filter_map(|i| {
84 let item = &i.impl_item;
85
86 // HACK(hkmatsumoto): For impls of primitive types, we index them
87 // regardless of whether they're local. This is because users can
88 // document primitive items in an arbitrary crate by using
89 // `rustc_doc_primitive`.
90 let mut is_primitive_impl = false;
91 if let clean::types::ItemKind::ImplItem(ref impl_) = item.kind
92 && impl_.trait_.is_none()
93 && let clean::types::Type::Primitive(_) = impl_.for_
94 {
95 is_primitive_impl = true;
96 }
97
98 if item.item_id.is_local() || is_primitive_impl {
99 self.item(item.clone()).unwrap();
100 Some(self.id_from_item(item))
101 } else {
102 None
103 }
104 })
105 .collect()
106 })
107 .unwrap_or_default()
108 }
109
110 fn serialize_and_write<T: Write>(
111 &self,
112 output_crate: types::Crate,
113 mut writer: BufWriter<T>,
114 path: &str,
115 ) -> Result<(), Error> {
116 self.sess().time("rustdoc_json_serialize_and_write", || {
117 try_err!(
118 serde_json::ser::to_writer(&mut writer, &output_crate).map_err(|e| e.to_string()),
119 path
120 );
121 try_err!(writer.flush(), path);
122 Ok(())
123 })
124 }
125}
126
127fn target(sess: &rustc_session::Session) -> types::Target {
128 // Build a set of which features are enabled on this target
129 let globally_enabled_features: FxHashSet<&str> =
130 sess.unstable_target_features.iter().map(|name| name.as_str()).collect();
131
132 // Build a map of target feature stability by feature name
133 use rustc_target::target_features::Stability;
134 let feature_stability: FxHashMap<&str, Stability> = sess
135 .target
136 .rust_target_features()
137 .into_iter()
138 .copied()
139 .map(|(name, stability, _)| (name, stability))
140 .collect();
141
142 types::Target {
143 triple: sess.opts.target_triple.tuple().into(),
144 target_features: sess
145 .target
146 .rust_target_features()
147 .into_iter()
148 .copied()
149 .filter(|(_, stability, _)| {
150 // Describe only target features which the user can toggle
151 stability.toggle_allowed().is_ok()
152 })
153 .map(|(name, stability, implied_features)| {
154 types::TargetFeature {
155 name: name.into(),
156 unstable_feature_gate: match stability {
157 Stability::Unstable(feature_gate) => Some(feature_gate.as_str().into()),
158 _ => None,
159 },
160 implies_features: implied_features
161 .into_iter()
162 .copied()
163 .filter(|name| {
164 // Imply only target features which the user can toggle
165 feature_stability
166 .get(name)
167 .map(|stability| stability.toggle_allowed().is_ok())
168 .unwrap_or(false)
169 })
170 .map(String::from)
171 .collect(),
172 globally_enabled: globally_enabled_features.contains(name),
173 }
174 })
175 .collect(),
176 }
177}
178
179impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
180 fn descr() -> &'static str {
181 "json"
182 }
183
184 const RUN_ON_MODULE: bool = false;
185 type ModuleData = ();
186
187 fn init(
188 krate: clean::Crate,
189 options: RenderOptions,
190 cache: Cache,
191 tcx: TyCtxt<'tcx>,
192 ) -> Result<(Self, clean::Crate), Error> {
193 debug!("Initializing json renderer");
194
195 let (krate, imported_items) = import_finder::get_imports(krate);
196
197 Ok((
198 JsonRenderer {
199 tcx,
200 index: Rc::new(RefCell::new(FxHashMap::default())),
201 out_dir: if options.output_to_stdout { None } else { Some(options.output) },
202 cache: Rc::new(cache),
203 imported_items,
204 id_interner: Default::default(),
205 },
206 krate,
207 ))
208 }
209
210 fn save_module_data(&mut self) -> Self::ModuleData {
211 unreachable!("RUN_ON_MODULE = false, should never call save_module_data")
212 }
213 fn restore_module_data(&mut self, _info: Self::ModuleData) {
214 unreachable!("RUN_ON_MODULE = false, should never call set_back_info")
215 }
216
217 /// Inserts an item into the index. This should be used rather than directly calling insert on
218 /// the hashmap because certain items (traits and types) need to have their mappings for trait
219 /// implementations filled out before they're inserted.
220 fn item(&mut self, item: clean::Item) -> Result<(), Error> {
221 let item_type = item.type_();
222 let item_name = item.name;
223 trace!("rendering {item_type} {item_name:?}");
224
225 // Flatten items that recursively store other items. We include orphaned items from
226 // stripped modules and etc that are otherwise reachable.
227 if let ItemKind::StrippedItem(inner) = &item.kind {
228 inner.inner_items().for_each(|i| self.item(i.clone()).unwrap());
229 }
230
231 // Flatten items that recursively store other items
232 item.kind.inner_items().for_each(|i| self.item(i.clone()).unwrap());
233
234 let item_id = item.item_id;
235 if let Some(mut new_item) = self.convert_item(item) {
236 let can_be_ignored = match new_item.inner {
237 types::ItemEnum::Trait(ref mut t) => {
238 t.implementations = self.get_trait_implementors(item_id.expect_def_id());
239 false
240 }
241 types::ItemEnum::Struct(ref mut s) => {
242 s.impls = self.get_impls(item_id.expect_def_id());
243 false
244 }
245 types::ItemEnum::Enum(ref mut e) => {
246 e.impls = self.get_impls(item_id.expect_def_id());
247 false
248 }
249 types::ItemEnum::Union(ref mut u) => {
250 u.impls = self.get_impls(item_id.expect_def_id());
251 false
252 }
253 types::ItemEnum::Primitive(ref mut p) => {
254 p.impls = self.get_impls(item_id.expect_def_id());
255 false
256 }
257
258 types::ItemEnum::Function(_)
259 | types::ItemEnum::Module(_)
260 | types::ItemEnum::Use(_)
261 | types::ItemEnum::AssocConst { .. }
262 | types::ItemEnum::AssocType { .. } => true,
263 types::ItemEnum::ExternCrate { .. }
264 | types::ItemEnum::StructField(_)
265 | types::ItemEnum::Variant(_)
266 | types::ItemEnum::TraitAlias(_)
267 | types::ItemEnum::Impl(_)
268 | types::ItemEnum::TypeAlias(_)
269 | types::ItemEnum::Constant { .. }
270 | types::ItemEnum::Static(_)
271 | types::ItemEnum::ExternType
272 | types::ItemEnum::Macro(_)
273 | types::ItemEnum::ProcMacro(_) => false,
274 };
275 let removed = self.index.borrow_mut().insert(new_item.id, new_item.clone());
276
277 // FIXME(adotinthevoid): Currently, the index is duplicated. This is a sanity check
278 // to make sure the items are unique. The main place this happens is when an item, is
279 // reexported in more than one place. See `rustdoc-json/reexport/in_root_and_mod`
280 if let Some(old_item) = removed {
281 // In case of generic implementations (like `impl<T> Trait for T {}`), all the
282 // inner items will be duplicated so we can ignore if they are slightly different.
283 if !can_be_ignored {
284 assert_eq!(old_item, new_item);
285 }
286 trace!("replaced {old_item:?}\nwith {new_item:?}");
287 }
288 }
289
290 trace!("done rendering {item_type} {item_name:?}");
291 Ok(())
292 }
293
294 fn mod_item_in(&mut self, _item: &clean::Item) -> Result<(), Error> {
295 unreachable!("RUN_ON_MODULE = false, should never call mod_item_in")
296 }
297
298 fn after_krate(&mut self) -> Result<(), Error> {
299 debug!("Done with crate");
300
301 let e = ExternalCrate { crate_num: LOCAL_CRATE };
302 let index = (*self.index).clone().into_inner();
303
304 // Note that tcx.rust_target_features is inappropriate here because rustdoc tries to run for
305 // multiple targets: https://github.com/rust-lang/rust/pull/137632
306 //
307 // We want to describe a single target, so pass tcx.sess rather than tcx.
308 let target = target(self.tcx.sess);
309
310 debug!("Constructing Output");
311 let output_crate = types::Crate {
312 root: self.id_from_item_default(e.def_id().into()),
313 crate_version: self.cache.crate_version.clone(),
314 includes_private: self.cache.document_private,
315 index,
316 paths: self
317 .cache
318 .paths
319 .iter()
320 .chain(&self.cache.external_paths)
321 .map(|(&k, &(ref path, kind))| {
322 (
323 self.id_from_item_default(k.into()),
324 types::ItemSummary {
325 crate_id: k.krate.as_u32(),
326 path: path.iter().map(|s| s.to_string()).collect(),
327 kind: kind.into_json(self),
328 },
329 )
330 })
331 .collect(),
332 external_crates: self
333 .cache
334 .extern_locations
335 .iter()
336 .map(|(crate_num, external_location)| {
337 let e = ExternalCrate { crate_num: *crate_num };
338 (
339 crate_num.as_u32(),
340 types::ExternalCrate {
341 name: e.name(self.tcx).to_string(),
342 html_root_url: match external_location {
343 ExternalLocation::Remote(s) => Some(s.clone()),
344 _ => None,
345 },
346 },
347 )
348 })
349 .collect(),
350 target,
351 format_version: types::FORMAT_VERSION,
352 };
353 if let Some(ref out_dir) = self.out_dir {
354 try_err!(create_dir_all(out_dir), out_dir);
355
356 let mut p = out_dir.clone();
357 p.push(output_crate.index.get(&output_crate.root).unwrap().name.clone().unwrap());
358 p.set_extension("json");
359
360 self.serialize_and_write(
361 output_crate,
362 try_err!(File::create_buffered(&p), p),
363 &p.display().to_string(),
364 )
365 } else {
366 self.serialize_and_write(output_crate, BufWriter::new(stdout().lock()), "<stdout>")
367 }
368 }
369
370 fn cache(&self) -> &Cache {
371 &self.cache
372 }
373}