]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/sources.rs
remove Clean trait implementation for hir::BareFnTy
[rust.git] / src / librustdoc / html / sources.rs
1 use crate::clean;
2 use crate::docfs::PathError;
3 use crate::error::Error;
4 use crate::html::format::Buffer;
5 use crate::html::highlight;
6 use crate::html::layout;
7 use crate::html::render::{Context, BASIC_KEYWORDS};
8 use crate::visit::DocVisitor;
9
10 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
11 use rustc_hir::def_id::LOCAL_CRATE;
12 use rustc_middle::ty::TyCtxt;
13 use rustc_session::Session;
14 use rustc_span::source_map::FileName;
15
16 use std::ffi::OsStr;
17 use std::fs;
18 use std::path::{Component, Path, PathBuf};
19 use std::rc::Rc;
20
21 pub(crate) fn render(cx: &mut Context<'_>, krate: &clean::Crate) -> Result<(), Error> {
22     info!("emitting source files");
23
24     let dst = cx.dst.join("src").join(krate.name(cx.tcx()).as_str());
25     cx.shared.ensure_dir(&dst)?;
26
27     let mut collector = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() };
28     collector.visit_crate(krate);
29     Ok(())
30 }
31
32 pub(crate) fn collect_local_sources<'tcx>(
33     tcx: TyCtxt<'tcx>,
34     src_root: &Path,
35     krate: &clean::Crate,
36 ) -> FxHashMap<PathBuf, String> {
37     let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root };
38     lsc.visit_crate(krate);
39     lsc.local_sources
40 }
41
42 struct LocalSourcesCollector<'a, 'tcx> {
43     tcx: TyCtxt<'tcx>,
44     local_sources: FxHashMap<PathBuf, String>,
45     src_root: &'a Path,
46 }
47
48 fn is_real_and_local(span: clean::Span, sess: &Session) -> bool {
49     span.cnum(sess) == LOCAL_CRATE && span.filename(sess).is_real()
50 }
51
52 impl LocalSourcesCollector<'_, '_> {
53     fn add_local_source(&mut self, item: &clean::Item) {
54         let sess = self.tcx.sess;
55         let span = item.span(self.tcx);
56         // skip all synthetic "files"
57         if !is_real_and_local(span, sess) {
58             return;
59         }
60         let filename = span.filename(sess);
61         let p = if let FileName::Real(file) = filename {
62             match file.into_local_path() {
63                 Some(p) => p,
64                 None => return,
65             }
66         } else {
67             return;
68         };
69         if self.local_sources.contains_key(&*p) {
70             // We've already emitted this source
71             return;
72         }
73
74         let mut href = String::new();
75         clean_path(self.src_root, &p, false, |component| {
76             href.push_str(&component.to_string_lossy());
77             href.push('/');
78         });
79
80         let mut src_fname = p.file_name().expect("source has no filename").to_os_string();
81         src_fname.push(".html");
82         href.push_str(&src_fname.to_string_lossy());
83         self.local_sources.insert(p, href);
84     }
85 }
86
87 impl DocVisitor for LocalSourcesCollector<'_, '_> {
88     fn visit_item(&mut self, item: &clean::Item) {
89         self.add_local_source(item);
90
91         self.visit_item_recur(item)
92     }
93 }
94
95 /// Helper struct to render all source code to HTML pages
96 struct SourceCollector<'a, 'tcx> {
97     cx: &'a mut Context<'tcx>,
98
99     /// Root destination to place all HTML output into
100     dst: PathBuf,
101     emitted_local_sources: FxHashSet<PathBuf>,
102 }
103
104 impl DocVisitor for SourceCollector<'_, '_> {
105     fn visit_item(&mut self, item: &clean::Item) {
106         if !self.cx.include_sources {
107             return;
108         }
109
110         let tcx = self.cx.tcx();
111         let span = item.span(tcx);
112         let sess = tcx.sess;
113
114         // If we're not rendering sources, there's nothing to do.
115         // If we're including source files, and we haven't seen this file yet,
116         // then we need to render it out to the filesystem.
117         if is_real_and_local(span, sess) {
118             let filename = span.filename(sess);
119             let span = span.inner();
120             let pos = sess.source_map().lookup_source_file(span.lo());
121             let file_span = span.with_lo(pos.start_pos).with_hi(pos.end_pos);
122             // If it turns out that we couldn't read this file, then we probably
123             // can't read any of the files (generating html output from json or
124             // something like that), so just don't include sources for the
125             // entire crate. The other option is maintaining this mapping on a
126             // per-file basis, but that's probably not worth it...
127             self.cx.include_sources = match self.emit_source(&filename, file_span) {
128                 Ok(()) => true,
129                 Err(e) => {
130                     self.cx.shared.tcx.sess.span_err(
131                         span,
132                         &format!(
133                             "failed to render source code for `{}`: {}",
134                             filename.prefer_local(),
135                             e,
136                         ),
137                     );
138                     false
139                 }
140             };
141         }
142
143         self.visit_item_recur(item)
144     }
145 }
146
147 impl SourceCollector<'_, '_> {
148     /// Renders the given filename into its corresponding HTML source file.
149     fn emit_source(
150         &mut self,
151         filename: &FileName,
152         file_span: rustc_span::Span,
153     ) -> Result<(), Error> {
154         let p = match *filename {
155             FileName::Real(ref file) => {
156                 if let Some(local_path) = file.local_path() {
157                     local_path.to_path_buf()
158                 } else {
159                     unreachable!("only the current crate should have sources emitted");
160                 }
161             }
162             _ => return Ok(()),
163         };
164         if self.emitted_local_sources.contains(&*p) {
165             // We've already emitted this source
166             return Ok(());
167         }
168
169         let contents = match fs::read_to_string(&p) {
170             Ok(contents) => contents,
171             Err(e) => {
172                 return Err(Error::new(e, &p));
173             }
174         };
175
176         // Remove the utf-8 BOM if any
177         let contents = contents.strip_prefix('\u{feff}').unwrap_or(&contents);
178
179         let shared = Rc::clone(&self.cx.shared);
180         // Create the intermediate directories
181         let mut cur = self.dst.clone();
182         let mut root_path = String::from("../../");
183         clean_path(&shared.src_root, &p, false, |component| {
184             cur.push(component);
185             root_path.push_str("../");
186         });
187
188         shared.ensure_dir(&cur)?;
189
190         let src_fname = p.file_name().expect("source has no filename").to_os_string();
191         let mut fname = src_fname.clone();
192         fname.push(".html");
193         cur.push(&fname);
194
195         let title = format!("{} - source", src_fname.to_string_lossy());
196         let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
197         let page = layout::Page {
198             title: &title,
199             css_class: "source",
200             root_path: &root_path,
201             static_root_path: shared.static_root_path.as_deref(),
202             description: &desc,
203             keywords: BASIC_KEYWORDS,
204             resource_suffix: &shared.resource_suffix,
205         };
206         let v = layout::render(
207             &shared.layout,
208             &page,
209             "",
210             |buf: &mut _| {
211                 let cx = &mut self.cx;
212                 print_src(
213                     buf,
214                     contents,
215                     file_span,
216                     cx,
217                     &root_path,
218                     highlight::DecorationInfo::default(),
219                     SourceContext::Standalone,
220                 )
221             },
222             &shared.style_files,
223         );
224         shared.fs.write(cur, v)?;
225         self.emitted_local_sources.insert(p);
226         Ok(())
227     }
228 }
229
230 /// Takes a path to a source file and cleans the path to it. This canonicalizes
231 /// things like ".." to components which preserve the "top down" hierarchy of a
232 /// static HTML tree. Each component in the cleaned path will be passed as an
233 /// argument to `f`. The very last component of the path (ie the file name) will
234 /// be passed to `f` if `keep_filename` is true, and ignored otherwise.
235 pub(crate) fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
236 where
237     F: FnMut(&OsStr),
238 {
239     // make it relative, if possible
240     let p = p.strip_prefix(src_root).unwrap_or(p);
241
242     let mut iter = p.components().peekable();
243
244     while let Some(c) = iter.next() {
245         if !keep_filename && iter.peek().is_none() {
246             break;
247         }
248
249         match c {
250             Component::ParentDir => f("up".as_ref()),
251             Component::Normal(c) => f(c),
252             _ => continue,
253         }
254     }
255 }
256
257 pub(crate) enum SourceContext {
258     Standalone,
259     Embedded { offset: usize },
260 }
261
262 /// Wrapper struct to render the source code of a file. This will do things like
263 /// adding line numbers to the left-hand side.
264 pub(crate) fn print_src(
265     buf: &mut Buffer,
266     s: &str,
267     file_span: rustc_span::Span,
268     context: &Context<'_>,
269     root_path: &str,
270     decoration_info: highlight::DecorationInfo,
271     source_context: SourceContext,
272 ) {
273     let lines = s.lines().count();
274     let mut line_numbers = Buffer::empty_from(buf);
275     line_numbers.write_str("<pre class=\"line-numbers\">");
276     match source_context {
277         SourceContext::Standalone => {
278             for line in 1..=lines {
279                 writeln!(line_numbers, "<span id=\"{0}\">{0}</span>", line)
280             }
281         }
282         SourceContext::Embedded { offset } => {
283             for line in 1..=lines {
284                 writeln!(line_numbers, "<span>{0}</span>", line + offset)
285             }
286         }
287     }
288     line_numbers.write_str("</pre>");
289     highlight::render_source_with_highlighting(
290         s,
291         buf,
292         line_numbers,
293         highlight::HrefContext { context, file_span, root_path },
294         decoration_info,
295     );
296 }