]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/render/cache.rs
Rollup merge of #81636 - LingMan:slice_not_vec, r=petrochenkov
[rust.git] / src / librustdoc / html / render / cache.rs
1 use std::collections::BTreeMap;
2 use std::path::Path;
3
4 use rustc_data_structures::fx::FxHashMap;
5 use rustc_span::symbol::{sym, Symbol};
6 use serde::Serialize;
7
8 use crate::clean::types::GetDefId;
9 use crate::clean::{self, AttributesExt};
10 use crate::formats::cache::Cache;
11 use crate::formats::item_type::ItemType;
12 use crate::html::markdown::short_markdown_summary;
13 use crate::html::render::{Generic, IndexItem, IndexItemFunctionType, RenderType, TypeWithKind};
14
15 /// Indicates where an external crate can be found.
16 crate enum ExternalLocation {
17     /// Remote URL root of the external crate
18     Remote(String),
19     /// This external crate can be found in the local doc/ folder
20     Local,
21     /// The external crate could not be found.
22     Unknown,
23 }
24
25 /// Attempts to find where an external crate is located, given that we're
26 /// rendering in to the specified source destination.
27 crate fn extern_location(
28     e: &clean::ExternalCrate,
29     extern_url: Option<&str>,
30     dst: &Path,
31 ) -> ExternalLocation {
32     use ExternalLocation::*;
33     // See if there's documentation generated into the local directory
34     let local_location = dst.join(&*e.name.as_str());
35     if local_location.is_dir() {
36         return Local;
37     }
38
39     if let Some(url) = extern_url {
40         let mut url = url.to_string();
41         if !url.ends_with('/') {
42             url.push('/');
43         }
44         return Remote(url);
45     }
46
47     // Failing that, see if there's an attribute specifying where to find this
48     // external crate
49     e.attrs
50         .lists(sym::doc)
51         .filter(|a| a.has_name(sym::html_root_url))
52         .filter_map(|a| a.value_str())
53         .map(|url| {
54             let mut url = url.to_string();
55             if !url.ends_with('/') {
56                 url.push('/')
57             }
58             Remote(url)
59         })
60         .next()
61         .unwrap_or(Unknown) // Well, at least we tried.
62 }
63
64 /// Builds the search index from the collected metadata
65 crate fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
66     let mut defid_to_pathid = FxHashMap::default();
67     let mut crate_items = Vec::with_capacity(cache.search_index.len());
68     let mut crate_paths = vec![];
69
70     // Attach all orphan items to the type's definition if the type
71     // has since been learned.
72     for &(did, ref item) in &cache.orphan_impl_items {
73         if let Some(&(ref fqp, _)) = cache.paths.get(&did) {
74             cache.search_index.push(IndexItem {
75                 ty: item.type_(),
76                 name: item.name.unwrap().to_string(),
77                 path: fqp[..fqp.len() - 1].join("::"),
78                 desc: item.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)),
79                 parent: Some(did),
80                 parent_idx: None,
81                 search_type: get_index_search_type(&item, Some(cache)),
82             });
83             for alias in item.attrs.get_doc_aliases() {
84                 cache
85                     .aliases
86                     .entry(alias.to_lowercase())
87                     .or_insert(Vec::new())
88                     .push(cache.search_index.len() - 1);
89             }
90         }
91     }
92
93     let Cache { ref mut search_index, ref paths, ref mut aliases, .. } = *cache;
94
95     // Reduce `DefId` in paths into smaller sequential numbers,
96     // and prune the paths that do not appear in the index.
97     let mut lastpath = String::new();
98     let mut lastpathid = 0usize;
99
100     for item in search_index {
101         item.parent_idx = item.parent.and_then(|defid| {
102             if defid_to_pathid.contains_key(&defid) {
103                 defid_to_pathid.get(&defid).copied()
104             } else {
105                 let pathid = lastpathid;
106                 defid_to_pathid.insert(defid, pathid);
107                 lastpathid += 1;
108
109                 if let Some(&(ref fqp, short)) = paths.get(&defid) {
110                     crate_paths.push((short, fqp.last().unwrap().clone()));
111                     Some(pathid)
112                 } else {
113                     None
114                 }
115             }
116         });
117
118         // Omit the parent path if it is same to that of the prior item.
119         if lastpath == item.path {
120             item.path.clear();
121         } else {
122             lastpath = item.path.clone();
123         }
124         crate_items.push(&*item);
125     }
126
127     let crate_doc = krate
128         .module
129         .as_ref()
130         .map(|module| module.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)))
131         .unwrap_or_default();
132
133     #[derive(Serialize)]
134     struct CrateData<'a> {
135         doc: String,
136         #[serde(rename = "i")]
137         items: Vec<&'a IndexItem>,
138         #[serde(rename = "p")]
139         paths: Vec<(ItemType, String)>,
140         // The String is alias name and the vec is the list of the elements with this alias.
141         //
142         // To be noted: the `usize` elements are indexes to `items`.
143         #[serde(rename = "a")]
144         #[serde(skip_serializing_if = "BTreeMap::is_empty")]
145         aliases: &'a BTreeMap<String, Vec<usize>>,
146     }
147
148     // Collect the index into a string
149     format!(
150         r#""{}":{}"#,
151         krate.name,
152         serde_json::to_string(&CrateData {
153             doc: crate_doc,
154             items: crate_items,
155             paths: crate_paths,
156             aliases,
157         })
158         .expect("failed serde conversion")
159         // All these `replace` calls are because we have to go through JS string for JSON content.
160         .replace(r"\", r"\\")
161         .replace("'", r"\'")
162         // We need to escape double quotes for the JSON.
163         .replace("\\\"", "\\\\\"")
164     )
165 }
166
167 crate fn get_index_search_type(
168     item: &clean::Item,
169     cache: Option<&Cache>,
170 ) -> Option<IndexItemFunctionType> {
171     let (all_types, ret_types) = match *item.kind {
172         clean::FunctionItem(ref f) => (&f.all_types, &f.ret_types),
173         clean::MethodItem(ref m, _) => (&m.all_types, &m.ret_types),
174         clean::TyMethodItem(ref m) => (&m.all_types, &m.ret_types),
175         _ => return None,
176     };
177
178     let inputs = all_types
179         .iter()
180         .map(|(ty, kind)| TypeWithKind::from((get_index_type(&ty, &cache), *kind)))
181         .filter(|a| a.ty.name.is_some())
182         .collect();
183     let output = ret_types
184         .iter()
185         .map(|(ty, kind)| TypeWithKind::from((get_index_type(&ty, &cache), *kind)))
186         .filter(|a| a.ty.name.is_some())
187         .collect::<Vec<_>>();
188     let output = if output.is_empty() { None } else { Some(output) };
189
190     Some(IndexItemFunctionType { inputs, output })
191 }
192
193 fn get_index_type(clean_type: &clean::Type, cache: &Option<&Cache>) -> RenderType {
194     RenderType {
195         ty: cache.map_or_else(|| clean_type.def_id(), |cache| clean_type.def_id_full(cache)),
196         idx: None,
197         name: get_index_type_name(clean_type, true).map(|s| s.as_str().to_ascii_lowercase()),
198         generics: get_generics(clean_type, cache),
199     }
200 }
201
202 fn get_index_type_name(clean_type: &clean::Type, accept_generic: bool) -> Option<Symbol> {
203     match *clean_type {
204         clean::ResolvedPath { ref path, .. } => {
205             let segments = &path.segments;
206             let path_segment = segments.iter().last().unwrap_or_else(|| {
207                 panic!(
208                 "get_index_type_name(clean_type: {:?}, accept_generic: {:?}) had length zero path",
209                 clean_type, accept_generic
210             )
211             });
212             Some(path_segment.name)
213         }
214         clean::Generic(s) if accept_generic => Some(s),
215         clean::Primitive(ref p) => Some(p.as_sym()),
216         clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_, accept_generic),
217         // FIXME: add all from clean::Type.
218         _ => None,
219     }
220 }
221
222 fn get_generics(clean_type: &clean::Type, cache: &Option<&Cache>) -> Option<Vec<Generic>> {
223     clean_type.generics().and_then(|types| {
224         let r = types
225             .iter()
226             .filter_map(|t| {
227                 get_index_type_name(t, false).map(|name| Generic {
228                     name: name.as_str().to_ascii_lowercase(),
229                     defid: cache.map_or_else(|| t.def_id(), |cache| t.def_id_full(cache)),
230                     idx: None,
231                 })
232             })
233             .collect::<Vec<_>>();
234         if r.is_empty() { None } else { Some(r) }
235     })
236 }