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