1 //! Rustdoc's JSON backend
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.
10 use std
::cell
::RefCell
;
11 use std
::fs
::{create_dir_all, File}
;
12 use std
::io
::{BufWriter, Write}
;
13 use std
::path
::PathBuf
;
16 use rustc_data_structures
::fx
::FxHashMap
;
17 use rustc_hir
::def_id
::{DefId, DefIdSet}
;
18 use rustc_middle
::ty
::TyCtxt
;
19 use rustc_session
::Session
;
20 use rustc_span
::def_id
::LOCAL_CRATE
;
22 use rustdoc_json_types
as types
;
24 use crate::clean
::types
::{ExternalCrate, ExternalLocation}
;
25 use crate::clean
::ItemKind
;
26 use crate::config
::RenderOptions
;
27 use crate::docfs
::PathError
;
28 use crate::error
::Error
;
29 use crate::formats
::cache
::Cache
;
30 use crate::formats
::FormatRenderer
;
31 use crate::json
::conversions
::{id_from_item, id_from_item_default, IntoWithTcx}
;
32 use crate::{clean, try_err}
;
35 pub(crate) struct JsonRenderer
<'tcx
> {
37 /// A mapping of IDs that contains all local items for this crate which gets output as a top
38 /// level field of the JSON blob.
39 index
: Rc
<RefCell
<FxHashMap
<types
::Id
, types
::Item
>>>,
40 /// The directory where the blob will be written to.
43 imported_items
: DefIdSet
,
46 impl<'tcx
> JsonRenderer
<'tcx
> {
47 fn sess(&self) -> &'tcx Session
{
51 fn get_trait_implementors(&mut self, id
: DefId
) -> Vec
<types
::Id
> {
52 Rc
::clone(&self.cache
)
59 let item
= &i
.impl_item
;
60 self.item(item
.clone()).unwrap();
61 id_from_item(&item
, self.tcx
)
68 fn get_impls(&mut self, id
: DefId
) -> Vec
<types
::Id
> {
69 Rc
::clone(&self.cache
)
76 let item
= &i
.impl_item
;
78 // HACK(hkmatsumoto): For impls of primitive types, we index them
79 // regardless of whether they're local. This is because users can
80 // document primitive items in an arbitrary crate by using
81 // `rustc_doc_primitive`.
82 let mut is_primitive_impl
= false;
83 if let clean
::types
::ItemKind
::ImplItem(ref impl_
) = *item
.kind
&&
84 impl_
.trait_
.is_none() &&
85 let clean
::types
::Type
::Primitive(_
) = impl_
.for_
87 is_primitive_impl
= true;
90 if item
.item_id
.is_local() || is_primitive_impl
{
91 self.item(item
.clone()).unwrap();
92 Some(id_from_item(&item
, self.tcx
))
103 impl<'tcx
> FormatRenderer
<'tcx
> for JsonRenderer
<'tcx
> {
104 fn descr() -> &'
static str {
108 const RUN_ON_MODULE
: bool
= false;
112 options
: RenderOptions
,
115 ) -> Result
<(Self, clean
::Crate
), Error
> {
116 debug
!("Initializing json renderer");
118 let (krate
, imported_items
) = import_finder
::get_imports(krate
);
123 index
: Rc
::new(RefCell
::new(FxHashMap
::default())),
124 out_path
: options
.output
,
125 cache
: Rc
::new(cache
),
132 fn make_child_renderer(&self) -> Self {
136 /// Inserts an item into the index. This should be used rather than directly calling insert on
137 /// the hashmap because certain items (traits and types) need to have their mappings for trait
138 /// implementations filled out before they're inserted.
139 fn item(&mut self, item
: clean
::Item
) -> Result
<(), Error
> {
140 let item_type
= item
.type_();
141 let item_name
= item
.name
;
142 trace
!("rendering {item_type} {item_name:?}");
144 // Flatten items that recursively store other items. We include orphaned items from
145 // stripped modules and etc that are otherwise reachable.
146 if let ItemKind
::StrippedItem(inner
) = &*item
.kind
{
147 inner
.inner_items().for_each(|i
| self.item(i
.clone()).unwrap());
150 // Flatten items that recursively store other items
151 item
.kind
.inner_items().for_each(|i
| self.item(i
.clone()).unwrap());
153 let item_id
= item
.item_id
;
154 if let Some(mut new_item
) = self.convert_item(item
) {
155 let can_be_ignored
= match new_item
.inner
{
156 types
::ItemEnum
::Trait(ref mut t
) => {
157 t
.implementations
= self.get_trait_implementors(item_id
.expect_def_id());
160 types
::ItemEnum
::Struct(ref mut s
) => {
161 s
.impls
= self.get_impls(item_id
.expect_def_id());
164 types
::ItemEnum
::Enum(ref mut e
) => {
165 e
.impls
= self.get_impls(item_id
.expect_def_id());
168 types
::ItemEnum
::Union(ref mut u
) => {
169 u
.impls
= self.get_impls(item_id
.expect_def_id());
172 types
::ItemEnum
::Primitive(ref mut p
) => {
173 p
.impls
= self.get_impls(item_id
.expect_def_id());
177 types
::ItemEnum
::Function(_
)
178 | types
::ItemEnum
::Module(_
)
179 | types
::ItemEnum
::Import(_
)
180 | types
::ItemEnum
::AssocConst { .. }
181 | types
::ItemEnum
::AssocType { .. }
=> true,
182 types
::ItemEnum
::ExternCrate { .. }
183 | types
::ItemEnum
::StructField(_
)
184 | types
::ItemEnum
::Variant(_
)
185 | types
::ItemEnum
::TraitAlias(_
)
186 | types
::ItemEnum
::Impl(_
)
187 | types
::ItemEnum
::TypeAlias(_
)
188 | types
::ItemEnum
::OpaqueTy(_
)
189 | types
::ItemEnum
::Constant(_
)
190 | types
::ItemEnum
::Static(_
)
191 | types
::ItemEnum
::ForeignType
192 | types
::ItemEnum
::Macro(_
)
193 | types
::ItemEnum
::ProcMacro(_
) => false,
195 let removed
= self.index
.borrow_mut().insert(new_item
.id
.clone(), new_item
.clone());
197 // FIXME(adotinthevoid): Currently, the index is duplicated. This is a sanity check
198 // to make sure the items are unique. The main place this happens is when an item, is
199 // reexported in more than one place. See `rustdoc-json/reexport/in_root_and_mod`
200 if let Some(old_item
) = removed
{
201 // In case of generic implementations (like `impl<T> Trait for T {}`), all the
202 // inner items will be duplicated so we can ignore if they are slightly different.
204 assert_eq
!(old_item
, new_item
);
206 trace
!("replaced {old_item:?}\nwith {new_item:?}");
210 trace
!("done rendering {item_type} {item_name:?}");
214 fn mod_item_in(&mut self, _item
: &clean
::Item
) -> Result
<(), Error
> {
215 unreachable
!("RUN_ON_MODULE = false should never call mod_item_in")
218 fn after_krate(&mut self) -> Result
<(), Error
> {
219 debug
!("Done with crate");
221 debug
!("Adding Primitive impls");
222 for primitive
in Rc
::clone(&self.cache
).primitive_locations
.values() {
223 self.get_impls(*primitive
);
226 let e
= ExternalCrate { crate_num: LOCAL_CRATE }
;
228 let index
= (*self.index
).clone().into_inner();
230 debug
!("Constructing Output");
231 // This needs to be the default HashMap for compatibility with the public interface for
232 // rustdoc-json-types
233 #[allow(rustc::default_hash_types)]
234 let output
= types
::Crate
{
235 root
: types
::Id(format
!("0:0:{}", e
.name(self.tcx
).as_u32())),
236 crate_version
: self.cache
.crate_version
.clone(),
237 includes_private
: self.cache
.document_private
,
238 index
: index
.into_iter().collect(),
243 .chain(&self.cache
.external_paths
)
244 .map(|(&k
, &(ref path
, kind
))| {
246 id_from_item_default(k
.into(), self.tcx
),
248 crate_id
: k
.krate
.as_u32(),
249 path
: path
.iter().map(|s
| s
.to_string()).collect(),
250 kind
: kind
.into_tcx(self.tcx
),
255 external_crates
: self
259 .map(|(crate_num
, external_location
)| {
260 let e
= ExternalCrate { crate_num: *crate_num }
;
263 types
::ExternalCrate
{
264 name
: e
.name(self.tcx
).to_string(),
265 html_root_url
: match external_location
{
266 ExternalLocation
::Remote(s
) => Some(s
.clone()),
273 format_version
: types
::FORMAT_VERSION
,
275 let out_dir
= self.out_path
.clone();
276 try_err
!(create_dir_all(&out_dir
), out_dir
);
279 p
.push(output
.index
.get(&output
.root
).unwrap().name
.clone().unwrap());
280 p
.set_extension("json");
281 let mut file
= BufWriter
::new(try_err
!(File
::create(&p
), p
));
284 .time("rustdoc_json_serialization", || serde_json
::ser
::to_writer(&mut file
, &output
))
286 try_err
!(file
.flush(), p
);
291 fn cache(&self) -> &Cache
{