]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/highlight.rs
Move `scripts` on the rustdoc template into `head` and apply the `defer` attribute
[rust.git] / src / librustdoc / html / highlight.rs
1 //! Basic syntax highlighting functionality.
2 //!
3 //! This module uses librustc_ast's lexer to provide token-based highlighting for
4 //! the HTML documentation generated by rustdoc.
5 //!
6 //! Use the `render_with_highlighting` to highlight some rust code.
7
8 use crate::clean::PrimitiveType;
9 use crate::html::escape::Escape;
10 use crate::html::render::Context;
11
12 use std::collections::VecDeque;
13 use std::fmt::{Display, Write};
14
15 use rustc_data_structures::fx::FxHashMap;
16 use rustc_lexer::{LiteralKind, TokenKind};
17 use rustc_span::edition::Edition;
18 use rustc_span::symbol::Symbol;
19 use rustc_span::{BytePos, Span, DUMMY_SP};
20
21 use super::format::{self, Buffer};
22 use super::render::LinkFromSrc;
23
24 /// This type is needed in case we want to render links on items to allow to go to their definition.
25 crate struct ContextInfo<'a, 'b, 'c> {
26     crate context: &'a Context<'b>,
27     /// This span contains the current file we're going through.
28     crate file_span: Span,
29     /// This field is used to know "how far" from the top of the directory we are to link to either
30     /// documentation pages or other source pages.
31     crate root_path: &'c str,
32 }
33
34 /// Decorations are represented as a map from CSS class to vector of character ranges.
35 /// Each range will be wrapped in a span with that class.
36 crate struct DecorationInfo(crate FxHashMap<&'static str, Vec<(u32, u32)>>);
37
38 /// Highlights `src`, returning the HTML output.
39 crate fn render_with_highlighting(
40     src: &str,
41     out: &mut Buffer,
42     class: Option<&str>,
43     playground_button: Option<&str>,
44     tooltip: Option<(Option<Edition>, &str)>,
45     edition: Edition,
46     extra_content: Option<Buffer>,
47     context_info: Option<ContextInfo<'_, '_, '_>>,
48     decoration_info: Option<DecorationInfo>,
49 ) {
50     debug!("highlighting: ================\n{}\n==============", src);
51     if let Some((edition_info, class)) = tooltip {
52         write!(
53             out,
54             "<div class='information'><div class='tooltip {}'{}>ⓘ</div></div>",
55             class,
56             if let Some(edition_info) = edition_info {
57                 format!(" data-edition=\"{}\"", edition_info)
58             } else {
59                 String::new()
60             },
61         );
62     }
63
64     write_header(out, class, extra_content);
65     write_code(out, src, edition, context_info, decoration_info);
66     write_footer(out, playground_button);
67 }
68
69 fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buffer>) {
70     write!(out, "<div class=\"example-wrap\">");
71     if let Some(extra) = extra_content {
72         out.push_buffer(extra);
73     }
74     if let Some(class) = class {
75         write!(out, "<pre class=\"rust {}\">", class);
76     } else {
77         write!(out, "<pre class=\"rust\">");
78     }
79     write!(out, "<code>");
80 }
81
82 /// Convert the given `src` source code into HTML by adding classes for highlighting.
83 ///
84 /// This code is used to render code blocks (in the documentation) as well as the source code pages.
85 ///
86 /// Some explanations on the last arguments:
87 ///
88 /// In case we are rendering a code block and not a source code file, `context_info` will be `None`.
89 /// To put it more simply: if `context_info` is `None`, the code won't try to generate links to an
90 /// item definition.
91 ///
92 /// More explanations about spans and how we use them here are provided in the
93 fn write_code(
94     out: &mut Buffer,
95     src: &str,
96     edition: Edition,
97     context_info: Option<ContextInfo<'_, '_, '_>>,
98     decoration_info: Option<DecorationInfo>,
99 ) {
100     // This replace allows to fix how the code source with DOS backline characters is displayed.
101     let src = src.replace("\r\n", "\n");
102     Classifier::new(
103         &src,
104         edition,
105         context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
106         decoration_info,
107     )
108     .highlight(&mut |highlight| {
109         match highlight {
110             Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
111             Highlight::EnterSpan { class } => enter_span(out, class),
112             Highlight::ExitSpan => exit_span(out),
113         };
114     });
115 }
116
117 fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
118     writeln!(out, "</code></pre>{}</div>", playground_button.unwrap_or_default());
119 }
120
121 /// How a span of text is classified. Mostly corresponds to token kinds.
122 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
123 enum Class {
124     Comment,
125     DocComment,
126     Attribute,
127     KeyWord,
128     // Keywords that do pointer/reference stuff.
129     RefKeyWord,
130     Self_(Span),
131     Op,
132     Macro,
133     MacroNonTerminal,
134     String,
135     Number,
136     Bool,
137     Ident(Span),
138     Lifetime,
139     PreludeTy,
140     PreludeVal,
141     QuestionMark,
142     Decoration(&'static str),
143 }
144
145 impl Class {
146     /// Returns the css class expected by rustdoc for each `Class`.
147     fn as_html(self) -> &'static str {
148         match self {
149             Class::Comment => "comment",
150             Class::DocComment => "doccomment",
151             Class::Attribute => "attribute",
152             Class::KeyWord => "kw",
153             Class::RefKeyWord => "kw-2",
154             Class::Self_(_) => "self",
155             Class::Op => "op",
156             Class::Macro => "macro",
157             Class::MacroNonTerminal => "macro-nonterminal",
158             Class::String => "string",
159             Class::Number => "number",
160             Class::Bool => "bool-val",
161             Class::Ident(_) => "ident",
162             Class::Lifetime => "lifetime",
163             Class::PreludeTy => "prelude-ty",
164             Class::PreludeVal => "prelude-val",
165             Class::QuestionMark => "question-mark",
166             Class::Decoration(kind) => kind,
167         }
168     }
169
170     /// In case this is an item which can be converted into a link to a definition, it'll contain
171     /// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
172     fn get_span(self) -> Option<Span> {
173         match self {
174             Self::Ident(sp) | Self::Self_(sp) => Some(sp),
175             _ => None,
176         }
177     }
178 }
179
180 enum Highlight<'a> {
181     Token { text: &'a str, class: Option<Class> },
182     EnterSpan { class: Class },
183     ExitSpan,
184 }
185
186 struct TokenIter<'a> {
187     src: &'a str,
188 }
189
190 impl Iterator for TokenIter<'a> {
191     type Item = (TokenKind, &'a str);
192     fn next(&mut self) -> Option<(TokenKind, &'a str)> {
193         if self.src.is_empty() {
194             return None;
195         }
196         let token = rustc_lexer::first_token(self.src);
197         let (text, rest) = self.src.split_at(token.len);
198         self.src = rest;
199         Some((token.kind, text))
200     }
201 }
202
203 /// Classifies into identifier class; returns `None` if this is a non-keyword identifier.
204 fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option<Class> {
205     let ignore: &[&str] =
206         if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
207     if ignore.iter().any(|k| *k == text) {
208         return None;
209     }
210     Some(match text {
211         "ref" | "mut" => Class::RefKeyWord,
212         "false" | "true" => Class::Bool,
213         _ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
214         _ => return None,
215     })
216 }
217
218 /// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than
219 /// just the next item by using `peek_next`. The `peek` method always returns the next item after
220 /// the current one whereas `peek_next` will return the next item after the last one peeked.
221 ///
222 /// You can use both `peek` and `peek_next` at the same time without problem.
223 struct PeekIter<'a> {
224     stored: VecDeque<(TokenKind, &'a str)>,
225     /// This position is reinitialized when using `next`. It is used in `peek_next`.
226     peek_pos: usize,
227     iter: TokenIter<'a>,
228 }
229
230 impl PeekIter<'a> {
231     fn new(iter: TokenIter<'a>) -> Self {
232         Self { stored: VecDeque::new(), peek_pos: 0, iter }
233     }
234     /// Returns the next item after the current one. It doesn't interfer with `peek_next` output.
235     fn peek(&mut self) -> Option<&(TokenKind, &'a str)> {
236         if self.stored.is_empty() {
237             if let Some(next) = self.iter.next() {
238                 self.stored.push_back(next);
239             }
240         }
241         self.stored.front()
242     }
243     /// Returns the next item after the last one peeked. It doesn't interfer with `peek` output.
244     fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> {
245         self.peek_pos += 1;
246         if self.peek_pos - 1 < self.stored.len() {
247             self.stored.get(self.peek_pos - 1)
248         } else if let Some(next) = self.iter.next() {
249             self.stored.push_back(next);
250             self.stored.back()
251         } else {
252             None
253         }
254     }
255 }
256
257 impl Iterator for PeekIter<'a> {
258     type Item = (TokenKind, &'a str);
259     fn next(&mut self) -> Option<Self::Item> {
260         self.peek_pos = 0;
261         if let Some(first) = self.stored.pop_front() { Some(first) } else { self.iter.next() }
262     }
263 }
264
265 /// Custom spans inserted into the source. Eg --scrape-examples uses this to highlight function calls
266 struct Decorations {
267     starts: Vec<(u32, &'static str)>,
268     ends: Vec<u32>,
269 }
270
271 impl Decorations {
272     fn new(info: DecorationInfo) -> Self {
273         // Extract tuples (start, end, kind) into separate sequences of (start, kind) and (end).
274         let (mut starts, mut ends): (Vec<_>, Vec<_>) = info
275             .0
276             .into_iter()
277             .map(|(kind, ranges)| ranges.into_iter().map(move |(lo, hi)| ((lo, kind), hi)))
278             .flatten()
279             .unzip();
280
281         // Sort the sequences in document order.
282         starts.sort_by_key(|(lo, _)| *lo);
283         ends.sort();
284
285         Decorations { starts, ends }
286     }
287 }
288
289 /// Processes program tokens, classifying strings of text by highlighting
290 /// category (`Class`).
291 struct Classifier<'a> {
292     tokens: PeekIter<'a>,
293     in_attribute: bool,
294     in_macro: bool,
295     in_macro_nonterminal: bool,
296     edition: Edition,
297     byte_pos: u32,
298     file_span: Span,
299     src: &'a str,
300     decorations: Option<Decorations>,
301 }
302
303 impl<'a> Classifier<'a> {
304     /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
305     /// file span which will be used later on by the `span_correspondance_map`.
306     fn new(
307         src: &str,
308         edition: Edition,
309         file_span: Span,
310         decoration_info: Option<DecorationInfo>,
311     ) -> Classifier<'_> {
312         let tokens = PeekIter::new(TokenIter { src });
313         let decorations = decoration_info.map(Decorations::new);
314         Classifier {
315             tokens,
316             in_attribute: false,
317             in_macro: false,
318             in_macro_nonterminal: false,
319             edition,
320             byte_pos: 0,
321             file_span,
322             src,
323             decorations,
324         }
325     }
326
327     /// Convenient wrapper to create a [`Span`] from a position in the file.
328     fn new_span(&self, lo: u32, text: &str) -> Span {
329         let hi = lo + text.len() as u32;
330         let file_lo = self.file_span.lo();
331         self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
332     }
333
334     /// Concatenate colons and idents as one when possible.
335     fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
336         let start = self.byte_pos as usize;
337         let mut pos = start;
338         let mut has_ident = false;
339         let edition = self.edition;
340
341         loop {
342             let mut nb = 0;
343             while let Some((TokenKind::Colon, _)) = self.tokens.peek() {
344                 self.tokens.next();
345                 nb += 1;
346             }
347             // Ident path can start with "::" but if we already have content in the ident path,
348             // the "::" is mandatory.
349             if has_ident && nb == 0 {
350                 return vec![(TokenKind::Ident, start, pos)];
351             } else if nb != 0 && nb != 2 {
352                 if has_ident {
353                     return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
354                 } else {
355                     return vec![(TokenKind::Colon, start, pos + nb)];
356                 }
357             }
358
359             if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
360                 if *token == TokenKind::Ident {
361                     let class = get_real_ident_class(text, edition, true);
362                     (class, text)
363                 } else {
364                     // Doesn't matter which Class we put in here...
365                     (Some(Class::Comment), text)
366                 }
367             }) {
368                 // We only "add" the colon if there is an ident behind.
369                 pos += text.len() + nb;
370                 has_ident = true;
371                 self.tokens.next();
372             } else if nb > 0 && has_ident {
373                 return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
374             } else if nb > 0 {
375                 return vec![(TokenKind::Colon, start, start + nb)];
376             } else if has_ident {
377                 return vec![(TokenKind::Ident, start, pos)];
378             } else {
379                 return Vec::new();
380             }
381         }
382     }
383
384     /// Wraps the tokens iteration to ensure that the `byte_pos` is always correct.
385     ///
386     /// It returns the token's kind, the token as a string and its byte position in the source
387     /// string.
388     fn next(&mut self) -> Option<(TokenKind, &'a str, u32)> {
389         if let Some((kind, text)) = self.tokens.next() {
390             let before = self.byte_pos;
391             self.byte_pos += text.len() as u32;
392             Some((kind, text, before))
393         } else {
394             None
395         }
396     }
397
398     /// Exhausts the `Classifier` writing the output into `sink`.
399     ///
400     /// The general structure for this method is to iterate over each token,
401     /// possibly giving it an HTML span with a class specifying what flavor of
402     /// token is used.
403     fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) {
404         loop {
405             if let Some(decs) = self.decorations.as_mut() {
406                 let byte_pos = self.byte_pos;
407                 let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
408                 for (_, kind) in decs.starts.drain(0..n_starts) {
409                     sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
410                 }
411
412                 let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
413                 for _ in decs.ends.drain(0..n_ends) {
414                     sink(Highlight::ExitSpan);
415                 }
416             }
417
418             if self
419                 .tokens
420                 .peek()
421                 .map(|t| matches!(t.0, TokenKind::Colon | TokenKind::Ident))
422                 .unwrap_or(false)
423             {
424                 let tokens = self.get_full_ident_path();
425                 for (token, start, end) in &tokens {
426                     let text = &self.src[*start..*end];
427                     self.advance(*token, text, sink, *start as u32);
428                     self.byte_pos += text.len() as u32;
429                 }
430                 if !tokens.is_empty() {
431                     continue;
432                 }
433             }
434             if let Some((token, text, before)) = self.next() {
435                 self.advance(token, text, sink, before);
436             } else {
437                 break;
438             }
439         }
440     }
441
442     /// Single step of highlighting. This will classify `token`, but maybe also a couple of
443     /// following ones as well.
444     ///
445     /// `before` is the position of the given token in the `source` string and is used as "lo" byte
446     /// in case we want to try to generate a link for this token using the
447     /// `span_correspondance_map`.
448     fn advance(
449         &mut self,
450         token: TokenKind,
451         text: &'a str,
452         sink: &mut dyn FnMut(Highlight<'a>),
453         before: u32,
454     ) {
455         let lookahead = self.peek();
456         let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
457         let class = match token {
458             TokenKind::Whitespace => return no_highlight(sink),
459             TokenKind::LineComment { doc_style } | TokenKind::BlockComment { doc_style, .. } => {
460                 if doc_style.is_some() {
461                     Class::DocComment
462                 } else {
463                     Class::Comment
464                 }
465             }
466             // Consider this as part of a macro invocation if there was a
467             // leading identifier.
468             TokenKind::Bang if self.in_macro => {
469                 self.in_macro = false;
470                 sink(Highlight::Token { text, class: None });
471                 sink(Highlight::ExitSpan);
472                 return;
473             }
474
475             // Assume that '&' or '*' is the reference or dereference operator
476             // or a reference or pointer type. Unless, of course, it looks like
477             // a logical and or a multiplication operator: `&&` or `* `.
478             TokenKind::Star => match self.tokens.peek() {
479                 Some((TokenKind::Whitespace, _)) => Class::Op,
480                 Some((TokenKind::Ident, "mut")) => {
481                     self.next();
482                     sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
483                     return;
484                 }
485                 Some((TokenKind::Ident, "const")) => {
486                     self.next();
487                     sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) });
488                     return;
489                 }
490                 _ => Class::RefKeyWord,
491             },
492             TokenKind::And => match self.tokens.peek() {
493                 Some((TokenKind::And, _)) => {
494                     self.next();
495                     sink(Highlight::Token { text: "&&", class: Some(Class::Op) });
496                     return;
497                 }
498                 Some((TokenKind::Eq, _)) => {
499                     self.next();
500                     sink(Highlight::Token { text: "&=", class: Some(Class::Op) });
501                     return;
502                 }
503                 Some((TokenKind::Whitespace, _)) => Class::Op,
504                 Some((TokenKind::Ident, "mut")) => {
505                     self.next();
506                     sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
507                     return;
508                 }
509                 _ => Class::RefKeyWord,
510             },
511
512             // These can either be operators, or arrows.
513             TokenKind::Eq => match lookahead {
514                 Some(TokenKind::Eq) => {
515                     self.next();
516                     sink(Highlight::Token { text: "==", class: Some(Class::Op) });
517                     return;
518                 }
519                 Some(TokenKind::Gt) => {
520                     self.next();
521                     sink(Highlight::Token { text: "=>", class: None });
522                     return;
523                 }
524                 _ => Class::Op,
525             },
526             TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
527                 self.next();
528                 sink(Highlight::Token { text: "->", class: None });
529                 return;
530             }
531
532             // Other operators.
533             TokenKind::Minus
534             | TokenKind::Plus
535             | TokenKind::Or
536             | TokenKind::Slash
537             | TokenKind::Caret
538             | TokenKind::Percent
539             | TokenKind::Bang
540             | TokenKind::Lt
541             | TokenKind::Gt => Class::Op,
542
543             // Miscellaneous, no highlighting.
544             TokenKind::Dot
545             | TokenKind::Semi
546             | TokenKind::Comma
547             | TokenKind::OpenParen
548             | TokenKind::CloseParen
549             | TokenKind::OpenBrace
550             | TokenKind::CloseBrace
551             | TokenKind::OpenBracket
552             | TokenKind::At
553             | TokenKind::Tilde
554             | TokenKind::Colon
555             | TokenKind::Unknown => return no_highlight(sink),
556
557             TokenKind::Question => Class::QuestionMark,
558
559             TokenKind::Dollar => match lookahead {
560                 Some(TokenKind::Ident) => {
561                     self.in_macro_nonterminal = true;
562                     Class::MacroNonTerminal
563                 }
564                 _ => return no_highlight(sink),
565             },
566
567             // This might be the start of an attribute. We're going to want to
568             // continue highlighting it as an attribute until the ending ']' is
569             // seen, so skip out early. Down below we terminate the attribute
570             // span when we see the ']'.
571             TokenKind::Pound => {
572                 match lookahead {
573                     // Case 1: #![inner_attribute]
574                     Some(TokenKind::Bang) => {
575                         self.next();
576                         if let Some(TokenKind::OpenBracket) = self.peek() {
577                             self.in_attribute = true;
578                             sink(Highlight::EnterSpan { class: Class::Attribute });
579                         }
580                         sink(Highlight::Token { text: "#", class: None });
581                         sink(Highlight::Token { text: "!", class: None });
582                         return;
583                     }
584                     // Case 2: #[outer_attribute]
585                     Some(TokenKind::OpenBracket) => {
586                         self.in_attribute = true;
587                         sink(Highlight::EnterSpan { class: Class::Attribute });
588                     }
589                     _ => (),
590                 }
591                 return no_highlight(sink);
592             }
593             TokenKind::CloseBracket => {
594                 if self.in_attribute {
595                     self.in_attribute = false;
596                     sink(Highlight::Token { text: "]", class: None });
597                     sink(Highlight::ExitSpan);
598                     return;
599                 }
600                 return no_highlight(sink);
601             }
602             TokenKind::Literal { kind, .. } => match kind {
603                 // Text literals.
604                 LiteralKind::Byte { .. }
605                 | LiteralKind::Char { .. }
606                 | LiteralKind::Str { .. }
607                 | LiteralKind::ByteStr { .. }
608                 | LiteralKind::RawStr { .. }
609                 | LiteralKind::RawByteStr { .. } => Class::String,
610                 // Number literals.
611                 LiteralKind::Float { .. } | LiteralKind::Int { .. } => Class::Number,
612             },
613             TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
614                 self.in_macro = true;
615                 sink(Highlight::EnterSpan { class: Class::Macro });
616                 sink(Highlight::Token { text, class: None });
617                 return;
618             }
619             TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
620                 None => match text {
621                     "Option" | "Result" => Class::PreludeTy,
622                     "Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
623                     // "union" is a weak keyword and is only considered as a keyword when declaring
624                     // a union type.
625                     "union" if self.check_if_is_union_keyword() => Class::KeyWord,
626                     _ if self.in_macro_nonterminal => {
627                         self.in_macro_nonterminal = false;
628                         Class::MacroNonTerminal
629                     }
630                     "self" | "Self" => Class::Self_(self.new_span(before, text)),
631                     _ => Class::Ident(self.new_span(before, text)),
632                 },
633                 Some(c) => c,
634             },
635             TokenKind::RawIdent | TokenKind::UnknownPrefix => {
636                 Class::Ident(self.new_span(before, text))
637             }
638             TokenKind::Lifetime { .. } => Class::Lifetime,
639         };
640         // Anything that didn't return above is the simple case where we the
641         // class just spans a single token, so we can use the `string` method.
642         sink(Highlight::Token { text, class: Some(class) });
643     }
644
645     fn peek(&mut self) -> Option<TokenKind> {
646         self.tokens.peek().map(|(token_kind, _text)| *token_kind)
647     }
648
649     fn check_if_is_union_keyword(&mut self) -> bool {
650         while let Some(kind) = self.tokens.peek_next().map(|(token_kind, _text)| token_kind) {
651             if *kind == TokenKind::Whitespace {
652                 continue;
653             }
654             return *kind == TokenKind::Ident;
655         }
656         false
657     }
658 }
659
660 /// Called when we start processing a span of text that should be highlighted.
661 /// The `Class` argument specifies how it should be highlighted.
662 fn enter_span(out: &mut Buffer, klass: Class) {
663     write!(out, "<span class=\"{}\">", klass.as_html());
664 }
665
666 /// Called at the end of a span of highlighted text.
667 fn exit_span(out: &mut Buffer) {
668     out.write_str("</span>");
669 }
670
671 /// Called for a span of text. If the text should be highlighted differently
672 /// from the surrounding text, then the `Class` argument will be a value other
673 /// than `None`.
674 ///
675 /// The following sequences of callbacks are equivalent:
676 /// ```plain
677 ///     enter_span(Foo), string("text", None), exit_span()
678 ///     string("text", Foo)
679 /// ```
680 ///
681 /// The latter can be thought of as a shorthand for the former, which is more
682 /// flexible.
683 ///
684 /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
685 /// will then try to find this `span` in the `span_correspondance_map`. If found, it'll then
686 /// generate a link for this element (which corresponds to where its definition is located).
687 fn string<T: Display>(
688     out: &mut Buffer,
689     text: T,
690     klass: Option<Class>,
691     context_info: &Option<ContextInfo<'_, '_, '_>>,
692 ) {
693     let klass = match klass {
694         None => return write!(out, "{}", text),
695         Some(klass) => klass,
696     };
697     let def_span = match klass.get_span() {
698         Some(d) => d,
699         None => {
700             write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
701             return;
702         }
703     };
704     let mut text_s = text.to_string();
705     if text_s.contains("::") {
706         text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
707             match t {
708                 "self" | "Self" => write!(
709                     &mut path,
710                     "<span class=\"{}\">{}</span>",
711                     Class::Self_(DUMMY_SP).as_html(),
712                     t
713                 ),
714                 "crate" | "super" => {
715                     write!(&mut path, "<span class=\"{}\">{}</span>", Class::KeyWord.as_html(), t)
716                 }
717                 t => write!(&mut path, "{}", t),
718             }
719             .expect("Failed to build source HTML path");
720             path
721         });
722     }
723     if let Some(context_info) = context_info {
724         if let Some(href) =
725             context_info.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
726                 let context = context_info.context;
727                 // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
728                 // one to the documentation page and one to the source definition.
729                 // FIXME: currently, external items only generate a link to their documentation,
730                 // a link to their definition can be generated using this:
731                 // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
732                 match href {
733                     LinkFromSrc::Local(span) => context
734                         .href_from_span(*span, true)
735                         .map(|s| format!("{}{}", context_info.root_path, s)),
736                     LinkFromSrc::External(def_id) => {
737                         format::href_with_root_path(*def_id, context, Some(context_info.root_path))
738                             .ok()
739                             .map(|(url, _, _)| url)
740                     }
741                     LinkFromSrc::Primitive(prim) => format::href_with_root_path(
742                         PrimitiveType::primitive_locations(context.tcx())[prim],
743                         context,
744                         Some(context_info.root_path),
745                     )
746                     .ok()
747                     .map(|(url, _, _)| url),
748                 }
749             })
750         {
751             write!(out, "<a class=\"{}\" href=\"{}\">{}</a>", klass.as_html(), href, text_s);
752             return;
753         }
754     }
755     write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text_s);
756 }
757
758 #[cfg(test)]
759 mod tests;