From 037d47b31880d60dc268ad1927b53b10ed1772a9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 14 Feb 2025 17:34:29 +0100 Subject: [PATCH 1/8] Add support for macro expansion in rustdoc source code pages --- src/librustdoc/html/highlight.rs | 222 ++++++++++++++++----- src/librustdoc/html/render/context.rs | 7 +- src/librustdoc/html/render/mod.rs | 2 +- src/librustdoc/html/render/span_map.rs | 114 ++++++++++- src/librustdoc/html/static/css/rustdoc.css | 128 ++++++------ 5 files changed, 355 insertions(+), 118 deletions(-) diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index ed4b97d36252c..f874acdcb0225 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -5,6 +5,7 @@ //! //! Use the `render_with_highlighting` to highlight some rust code. +use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::{Display, Write}; @@ -17,7 +18,7 @@ use rustc_span::{BytePos, DUMMY_SP, Span}; use super::format::{self, write_str}; use crate::clean::PrimitiveType; use crate::html::escape::EscapeBodyText; -use crate::html::render::{Context, LinkFromSrc}; +use crate::html::render::{Context, ExpandedCode, LinkFromSrc}; /// This type is needed in case we want to render links on items to allow to go to their definition. pub(crate) struct HrefContext<'a, 'tcx> { @@ -156,7 +157,7 @@ struct TokenHandler<'a, 'tcx, F: Write> { current_class: Option, /// We need to keep the `Class` for each element because it could contain a `Span` which is /// used to generate links. - pending_elems: Vec<(&'a str, Option)>, + pending_elems: Vec<(Cow<'a, str>, Option)>, href_context: Option>, write_line_number: fn(&mut F, u32, &'static str), } @@ -264,6 +265,62 @@ fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) { out.write_str(extra).unwrap(); } +fn get_expansion<'a, W: Write>( + token_handler: &mut TokenHandler<'_, '_, W>, + expanded_codes: Option<&'a Vec>, + line: u32, +) -> Option<&'a ExpandedCode> { + if let Some(expanded_codes) = expanded_codes + && let Some(expanded_code) = expanded_codes.iter().find(|code| code.start_line == line) + { + let (closing, reopening) = if let Some(current_class) = token_handler.current_class + && let class = current_class.as_html() + && !class.is_empty() + { + ("", format!("")) + } else { + ("", String::new()) + }; + let id = format!("expand-{line}"); + token_handler.pending_elems.push(( + Cow::Owned(format!( + "{closing}\ +\ + \ + {reopening}", + )), + Some(Class::Expansion), + )); + Some(expanded_code) + } else { + None + } +} + +fn start_expansion(out: &mut Vec<(Cow<'_, str>, Option)>, expanded_code: &ExpandedCode) { + out.push(( + Cow::Owned(format!( + "{}", + expanded_code.code, + )), + Some(Class::Expansion), + )); +} + +fn end_expansion(token_handler: &mut TokenHandler<'_, '_, W>, level: usize) { + if level == 0 { + token_handler.pending_elems.push((Cow::Borrowed(""), Some(Class::Expansion))); + return; + } + let mut out = String::new(); + let mut end = String::new(); + for (tag, class) in token_handler.closing_tags.iter().skip(token_handler.closing_tags.len() - level) { + out.push_str(tag); + end.push_str(&format!("", class.as_html())); + } + token_handler.pending_elems.push((Cow::Owned(format!("{out}{end}")), Some(Class::Expansion))); +} + #[derive(Clone, Copy)] pub(super) struct LineInfo { pub(super) start_line: u32, @@ -310,7 +367,7 @@ pub(super) fn write_code( closing_tags: Vec::new(), pending_exit_span: None, current_class: None, - pending_elems: Vec::new(), + pending_elems: Vec::with_capacity(20), href_context, write_line_number: match line_info { Some(line_info) => { @@ -331,12 +388,21 @@ pub(super) fn write_code( (0, u32::MAX) }; + let expanded_codes = token_handler + .href_context + .as_ref() + .and_then(|c| c.context.shared.expanded_codes.get(&c.file_span.lo())); + let mut current_expansion = + get_expansion(&mut token_handler, expanded_codes, line); + token_handler.write_pending_elems(None); + let mut level = 0; + Classifier::new( &src, token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP), decoration_info, ) - .highlight(&mut |highlight| { + .highlight(&mut |span, highlight| { match highlight { Highlight::Token { text, class } => { // If we received a `ExitSpan` event and then have a non-compatible `Class`, we @@ -362,10 +428,32 @@ pub(super) fn write_code( if text == "\n" { line += 1; if line < max_lines { - token_handler.pending_elems.push((text, Some(Class::Backline(line)))); + token_handler + .pending_elems + .push((Cow::Borrowed(text), Some(Class::Backline(line)))); + } + if current_expansion.is_none() { + current_expansion = + get_expansion(&mut token_handler, expanded_codes, line); } } else { - token_handler.pending_elems.push((text, class)); + token_handler.pending_elems.push((Cow::Borrowed(text), class)); + + let mut need_end = false; + if let Some(ref current_expansion) = current_expansion { + if current_expansion.span.lo() == span.hi() { + start_expansion(&mut token_handler.pending_elems, current_expansion); + } else if current_expansion.end_line == line + && span.hi() >= current_expansion.span.hi() + { + need_end = true; + } + } + if need_end { + end_expansion(&mut token_handler, level); + current_expansion = None; + level = 0; + } } } Highlight::EnterSpan { class } => { @@ -385,6 +473,9 @@ pub(super) fn write_code( if should_add { let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context); + if current_expansion.is_some() { + level += 1; + } token_handler.closing_tags.push((closing_tag, class)); } @@ -393,6 +484,9 @@ pub(super) fn write_code( } Highlight::ExitSpan => { token_handler.current_class = None; + if current_expansion.is_some() { + level -= 1; + } token_handler.pending_exit_span = Some( token_handler .closing_tags @@ -433,6 +527,8 @@ enum Class { QuestionMark, Decoration(&'static str), Backline(u32), + /// Macro expansion. + Expansion, } impl Class { @@ -482,6 +578,7 @@ impl Class { Class::QuestionMark => "question-mark", Class::Decoration(kind) => kind, Class::Backline(_) => "", + Class::Expansion => "", } } @@ -506,7 +603,8 @@ impl Class { | Self::Lifetime | Self::QuestionMark | Self::Decoration(_) - | Self::Backline(_) => None, + | Self::Backline(_) + | Self::Expansion => None, } } } @@ -621,6 +719,13 @@ impl Decorations { } } +/// Convenient wrapper to create a [`Span`] from a position in the file. +fn new_span(lo: u32, text: &str, file_span: Span) -> Span { + let hi = lo + text.len() as u32; + let file_lo = file_span.lo(); + file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi)) +} + /// Processes program tokens, classifying strings of text by highlighting /// category (`Class`). struct Classifier<'src> { @@ -652,13 +757,6 @@ impl<'src> Classifier<'src> { } } - /// Convenient wrapper to create a [`Span`] from a position in the file. - fn new_span(&self, lo: u32, text: &str) -> Span { - let hi = lo + text.len() as u32; - let file_lo = self.file_span.lo(); - self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi)) - } - /// Concatenate colons and idents as one when possible. fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> { let start = self.byte_pos as usize; @@ -727,18 +825,18 @@ impl<'src> Classifier<'src> { /// The general structure for this method is to iterate over each token, /// possibly giving it an HTML span with a class specifying what flavor of /// token is used. - fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'src>)) { + fn highlight(mut self, sink: &mut dyn FnMut(Span, Highlight<'src>)) { loop { if let Some(decs) = self.decorations.as_mut() { let byte_pos = self.byte_pos; let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count(); for (_, kind) in decs.starts.drain(0..n_starts) { - sink(Highlight::EnterSpan { class: Class::Decoration(kind) }); + sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Decoration(kind) }); } let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count(); for _ in decs.ends.drain(0..n_ends) { - sink(Highlight::ExitSpan); + sink(DUMMY_SP, Highlight::ExitSpan); } } @@ -776,14 +874,20 @@ impl<'src> Classifier<'src> { &mut self, token: TokenKind, text: &'src str, - sink: &mut dyn FnMut(Highlight<'src>), + sink: &mut dyn FnMut(Span, Highlight<'src>), before: u32, ) { let lookahead = self.peek(); - let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None }); - let whitespace = |sink: &mut dyn FnMut(_)| { + let file_span = self.file_span; + let no_highlight = |sink: &mut dyn FnMut(_, _)| sink(new_span(before, text, file_span), Highlight::Token { text, class: None }); + let whitespace = |sink: &mut dyn FnMut(_, _)| { + let mut start = 0u32; for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) { - sink(Highlight::Token { text: part, class: None }); + sink( + new_span(before + start, part, file_span), + Highlight::Token { text: part, class: None }, + ); + start += part.len() as u32; } }; let class = match token { @@ -799,8 +903,8 @@ impl<'src> Classifier<'src> { // leading identifier. TokenKind::Bang if self.in_macro => { self.in_macro = false; - sink(Highlight::Token { text, class: None }); - sink(Highlight::ExitSpan); + sink(new_span(before, text, file_span), Highlight::Token { text, class: None }); + sink(DUMMY_SP, Highlight::ExitSpan); return; } @@ -811,12 +915,18 @@ impl<'src> Classifier<'src> { Some((TokenKind::Whitespace, _)) => return whitespace(sink), Some((TokenKind::Ident, "mut")) => { self.next(); - sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) }); + sink( + DUMMY_SP, + Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) }, + ); return; } Some((TokenKind::Ident, "const")) => { self.next(); - sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) }); + sink( + DUMMY_SP, + Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) }, + ); return; } _ => Class::RefKeyWord, @@ -824,18 +934,21 @@ impl<'src> Classifier<'src> { TokenKind::And => match self.tokens.peek() { Some((TokenKind::And, _)) => { self.next(); - sink(Highlight::Token { text: "&&", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "&&", class: None }); return; } Some((TokenKind::Eq, _)) => { self.next(); - sink(Highlight::Token { text: "&=", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "&=", class: None }); return; } Some((TokenKind::Whitespace, _)) => return whitespace(sink), Some((TokenKind::Ident, "mut")) => { self.next(); - sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) }); + sink( + DUMMY_SP, + Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) }, + ); return; } _ => Class::RefKeyWord, @@ -845,19 +958,19 @@ impl<'src> Classifier<'src> { TokenKind::Eq => match lookahead { Some(TokenKind::Eq) => { self.next(); - sink(Highlight::Token { text: "==", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "==", class: None }); return; } Some(TokenKind::Gt) => { self.next(); - sink(Highlight::Token { text: "=>", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "=>", class: None }); return; } _ => return no_highlight(sink), }, TokenKind::Minus if lookahead == Some(TokenKind::Gt) => { self.next(); - sink(Highlight::Token { text: "->", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "->", class: None }); return; } @@ -907,16 +1020,22 @@ impl<'src> Classifier<'src> { self.next(); if let Some(TokenKind::OpenBracket) = self.peek() { self.in_attribute = true; - sink(Highlight::EnterSpan { class: Class::Attribute }); + sink( + new_span(before, text, file_span), + Highlight::EnterSpan { class: Class::Attribute }, + ); } - sink(Highlight::Token { text: "#", class: None }); - sink(Highlight::Token { text: "!", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "#", class: None }); + sink(DUMMY_SP, Highlight::Token { text: "!", class: None }); return; } // Case 2: #[outer_attribute] Some(TokenKind::OpenBracket) => { self.in_attribute = true; - sink(Highlight::EnterSpan { class: Class::Attribute }); + sink( + new_span(before, text, file_span), + Highlight::EnterSpan { class: Class::Attribute }, + ); } _ => (), } @@ -925,8 +1044,8 @@ impl<'src> Classifier<'src> { TokenKind::CloseBracket => { if self.in_attribute { self.in_attribute = false; - sink(Highlight::Token { text: "]", class: None }); - sink(Highlight::ExitSpan); + sink(new_span(before, text, file_span), Highlight::Token { text: "]", class: None }); + sink(DUMMY_SP, Highlight::ExitSpan); return; } return no_highlight(sink); @@ -947,15 +1066,16 @@ impl<'src> Classifier<'src> { TokenKind::GuardedStrPrefix => return no_highlight(sink), TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => { self.in_macro = true; - sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) }); - sink(Highlight::Token { text, class: None }); + let span = new_span(before, text, file_span); + sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) }); + sink(span, Highlight::Token { text, class: None }); return; } TokenKind::Ident => match get_real_ident_class(text, false) { None => match text { - "Option" | "Result" => Class::PreludeTy(self.new_span(before, text)), + "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)), "Some" | "None" | "Ok" | "Err" => { - Class::PreludeVal(self.new_span(before, text)) + Class::PreludeVal(new_span(before, text, file_span)) } // "union" is a weak keyword and is only considered as a keyword when declaring // a union type. @@ -964,13 +1084,13 @@ impl<'src> Classifier<'src> { self.in_macro_nonterminal = false; Class::MacroNonTerminal } - "self" | "Self" => Class::Self_(self.new_span(before, text)), - _ => Class::Ident(self.new_span(before, text)), + "self" | "Self" => Class::Self_(new_span(before, text, file_span)), + _ => Class::Ident(new_span(before, text, file_span)), }, Some(c) => c, }, TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => { - Class::Ident(self.new_span(before, text)) + Class::Ident(new_span(before, text, file_span)) } TokenKind::Lifetime { .. } | TokenKind::RawLifetime @@ -979,8 +1099,13 @@ impl<'src> Classifier<'src> { }; // Anything that didn't return above is the simple case where we the // class just spans a single token, so we can use the `string` method. + let mut start = 0u32; for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) { - sink(Highlight::Token { text: part, class: Some(class) }); + sink( + new_span(before + start, part, file_span), + Highlight::Token { text: part, class: Some(class) }, + ); + start += part.len() as u32; } } @@ -1033,9 +1158,9 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) { /// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function /// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then /// generate a link for this element (which corresponds to where its definition is located). -fn string( +fn string( out: &mut W, - text: T, + text: EscapeBodyText<'_>, klass: Option, href_context: &Option>, open_tag: bool, @@ -1043,6 +1168,9 @@ fn string( ) { if let Some(Class::Backline(line)) = klass { write_line_number_callback(out, line, "\n"); + } else if let Some(Class::Expansion) = klass { + // Nothing to escape here so we get the text to write it directly. + out.write_str(text.0).unwrap(); } else if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag) { diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 146bdd340697b..1479cd6e958c1 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -11,7 +11,7 @@ use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::edition::Edition; -use rustc_span::{FileName, Symbol, sym}; +use rustc_span::{BytePos, FileName, Symbol, sym}; use tracing::info; use super::print_item::{full_path, item_path, print_item}; @@ -30,6 +30,7 @@ use crate::html::escape::Escape; use crate::html::format::join_with_double_colon; use crate::html::layout::{self, BufDisplay}; use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary}; +use crate::html::render::ExpandedCode; use crate::html::render::write_shared::write_shared; use crate::html::url_parts_builder::UrlPartsBuilder; use crate::html::{sources, static_files}; @@ -140,6 +141,7 @@ pub(crate) struct SharedContext<'tcx> { /// Correspondence map used to link types used in the source code pages to allow to click on /// links to jump to the type's definition. pub(crate) span_correspondence_map: FxHashMap, + pub(crate) expanded_codes: FxHashMap>, /// The [`Cache`] used during rendering. pub(crate) cache: Cache, pub(crate) call_locations: AllCallLocations, @@ -545,7 +547,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { } } - let (local_sources, matches) = collect_spans_and_sources( + let (local_sources, matches, expanded_codes) = collect_spans_and_sources( tcx, &krate, &src_root, @@ -576,6 +578,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { cache, call_locations, should_merge: options.should_merge, + expanded_codes, }; let dst = output; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 204631063a23a..e3d484eb5c271 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -60,7 +60,7 @@ use serde::{Serialize, Serializer}; use tracing::{debug, info}; pub(crate) use self::context::*; -pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources}; +pub(crate) use self::span_map::{ExpandedCode, LinkFromSrc, collect_spans_and_sources}; pub(crate) use self::write_shared::*; use crate::clean::{self, ItemId, RenderedLink}; use crate::display::{Joined as _, MaybeDisplay as _}; diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index ce9c42c01cc73..8e90731e1423c 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -30,12 +30,26 @@ pub(crate) enum LinkFromSrc { Doc(DefId), } +/// Contains information about macro expansion in the source code pages. +#[derive(Debug)] +pub(crate) struct ExpandedCode { + /// The line where the macro expansion starts. + pub(crate) start_line: u32, + /// The line where the macro expansion ends. + pub(crate) end_line: u32, + /// The source code of the expanded macro. + pub(crate) code: String, + /// The span of macro callsite. + pub(crate) span: Span, +} + /// This function will do at most two things: /// /// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`. /// 2. Collect the source code files. /// -/// It returns the `krate`, the source code files and the `span` correspondence map. +/// It returns the source code files, the `span` correspondence map and the expanded macros +/// correspondence map. /// /// Note about the `span` correspondence map: the keys are actually `(lo, hi)` of `span`s. We don't /// need the `span` context later on, only their position, so instead of keeping a whole `Span`, we @@ -46,17 +60,23 @@ pub(crate) fn collect_spans_and_sources( src_root: &Path, include_sources: bool, generate_link_to_definition: bool, -) -> (FxIndexMap, FxHashMap) { +) -> ( + FxIndexMap, + FxHashMap, + FxHashMap>, +) { if include_sources { let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; + let mut expanded_visitor = ExpandedCodeVisitor { tcx, expanded_codes: Vec::new() }; + tcx.hir_walk_toplevel_module(&mut expanded_visitor); if generate_link_to_definition { tcx.hir_walk_toplevel_module(&mut visitor); } let sources = sources::collect_local_sources(tcx, src_root, krate); - (sources, visitor.matches) + (sources, visitor.matches, expanded_visitor.compute_expanded()) } else { - (Default::default(), Default::default()) + (Default::default(), Default::default(), Default::default()) } } @@ -295,3 +315,89 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { intravisit::walk_item(self, item); } } + +/// Contains temporary information of macro expanded code. +/// +/// As we go through the HIR visitor, if any span overlaps with another, they will +/// both be merged. +struct ExpandedCodeInfo { + /// Callsite of the macro. + span: Span, + /// Expanded macro source code. + code: String, +} + +/// HIR visitor which retrieves expanded macro. +/// +/// Once done, the `expanded_codes` will be transformed into a vec of [`ExpandedCode`] +/// which contains more information needed when running the source code highlighter. +pub struct ExpandedCodeVisitor<'tcx> { + tcx: TyCtxt<'tcx>, + expanded_codes: Vec, +} + +impl<'tcx> ExpandedCodeVisitor<'tcx> { + fn handle_new_span) -> String>(&mut self, new_span: Span, f: F) { + if new_span.is_dummy() || !new_span.from_expansion() { + return; + } + let new_span = new_span.source_callsite(); + if let Some(index) = + self.expanded_codes.iter().position(|info| info.span.overlaps(new_span)) + { + if !self.expanded_codes[index].span.contains(new_span) { + // We replace the item. + let info = &mut self.expanded_codes[index]; + info.span = new_span; + info.code = f(self.tcx); + } + } else { + // We add a new item. + self.expanded_codes.push(ExpandedCodeInfo { + span: new_span, + code: f(self.tcx), + }); + } + } + + fn compute_expanded(mut self) -> FxHashMap> { + self.expanded_codes.sort_unstable_by(|item1, item2| item1.span.cmp(&item2.span)); + let source_map = self.tcx.sess.source_map(); + let mut expanded: FxHashMap> = FxHashMap::default(); + for ExpandedCodeInfo { span, code } in self.expanded_codes { + if let Ok(lines) = source_map.span_to_lines(span) + && !lines.lines.is_empty() + { + let mut out = String::new(); + super::highlight::write_code(&mut out, &code, None, None, None); + let first = lines.lines.first().unwrap(); + let end = lines.lines.last().unwrap(); + expanded.entry(lines.file.start_pos).or_default().push(ExpandedCode { + start_line: first.line_index as u32 + 1, + end_line: end.line_index as u32 + 1, + code: out, + span, + }); + } + } + expanded + } +} + +impl<'tcx> Visitor<'tcx> for ExpandedCodeVisitor<'tcx> { + type NestedFilter = nested_filter::All; + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.tcx + } + + fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) { + self.handle_new_span(expr.span, |tcx| rustc_hir_pretty::expr_to_string(&tcx, expr)); + intravisit::walk_expr(self, expr); + } + + fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) { + self.handle_new_span(item.span, |tcx| rustc_hir_pretty::item_to_string(&tcx, item)); + intravisit::walk_item(self, item); + } +} diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 0ea4d8f1e3914..8e3141aad5cd5 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -6,6 +6,8 @@ 3. Copy the filenames with updated suffixes from the directory. */ +/* ignore-tidy-filelength */ + :root { --nav-sub-mobile-padding: 8px; --search-typename-width: 6.75rem; @@ -893,32 +895,70 @@ ul.block, .block li, .block ul { overflow: auto; } -.example-wrap.digits-1:not(.hide-lines) [data-nosnippet] { - width: calc(1ch + var(--line-number-padding) * 2); +.example-wrap code { + position: relative; } -.example-wrap.digits-2:not(.hide-lines) [data-nosnippet] { - width: calc(2ch + var(--line-number-padding) * 2); +.example-wrap pre code span { + display: inline; } -.example-wrap.digits-3:not(.hide-lines) [data-nosnippet] { - width: calc(3ch + var(--line-number-padding) * 2); +.example-wrap .expansion { + position: relative; + display: inline; } -.example-wrap.digits-4:not(.hide-lines) [data-nosnippet] { - width: calc(4ch + var(--line-number-padding) * 2); +.example-wrap .expansion > input { + display: none; } -.example-wrap.digits-5:not(.hide-lines) [data-nosnippet] { - width: calc(5ch + var(--line-number-padding) * 2); +.example-wrap .expansion > label { + position: absolute; + left: -20px; + top: 0; + border: 1px solid var(--border-color); + border-radius: 4px; + cursor: pointer; + color: var(--main-color); + padding: 0 2px; + line-height: 20px; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + } -.example-wrap.digits-6:not(.hide-lines) [data-nosnippet] { - width: calc(6ch + var(--line-number-padding) * 2); +.example-wrap .expansion .expanded { + display: none; + color: var(--main-color); } -.example-wrap.digits-7:not(.hide-lines) [data-nosnippet] { - width: calc(7ch + var(--line-number-padding) * 2); +.example-wrap .expansion > input:checked ~ .expanded, +.example-wrap .expansion > input:checked ~ * .expanded { + display: inherit; } -.example-wrap.digits-8:not(.hide-lines) [data-nosnippet] { - width: calc(8ch + var(--line-number-padding) * 2); +.example-wrap .expansion > input:checked ~ .original, +.example-wrap .expansion > input:checked ~ * .original { + display: none; } -.example-wrap.digits-9:not(.hide-lines) [data-nosnippet] { - width: calc(9ch + var(--line-number-padding) * 2); + +.example-wrap.digits-1 { --example-wrap-digits-count: 1ch; } +.example-wrap.digits-2 { --example-wrap-digits-count: 2ch; } +.example-wrap.digits-3 { --example-wrap-digits-count: 3ch; } +.example-wrap.digits-4 { --example-wrap-digits-count: 4ch; } +.example-wrap.digits-5 { --example-wrap-digits-count: 5ch; } +.example-wrap.digits-6 { --example-wrap-digits-count: 6ch; } +.example-wrap.digits-7 { --example-wrap-digits-count: 7ch; } +.example-wrap.digits-8 { --example-wrap-digits-count: 8ch; } +.example-wrap.digits-9 { --example-wrap-digits-count: 9ch; } + +.src .example-wrap [data-nosnippet] { + width: calc(var(--example-wrap-digits-count) + var(--line-number-padding) * 2); +} +.src .example-wrap pre > code { + padding-left: calc( + var(--example-wrap-digits-count) + var(--line-number-padding) * 2 + + var(--line-number-right-margin)); +} +.src .example-wrap .expansion [data-nosnippet] { + left: calc(( + var(--example-wrap-digits-count) + var(--line-number-padding) * 2 + + var(--line-number-right-margin)) * -1); } .example-wrap [data-nosnippet] { @@ -931,63 +971,23 @@ ul.block, .block li, .block ul { -ms-user-select: none; user-select: none; padding: 0 var(--line-number-padding); -} -.example-wrap [data-nosnippet]:target { - border-right: none; -} -.example-wrap .line-highlighted[data-nosnippet] { - background-color: var(--src-line-number-highlighted-background-color); -} -:root.word-wrap-source-code .example-wrap [data-nosnippet] { position: absolute; left: 0; } -.word-wrap-source-code .example-wrap pre > code { +.example-wrap pre > code { position: relative; word-break: break-all; -} -:root.word-wrap-source-code .example-wrap pre > code { display: block; white-space: pre-wrap; } -:root.word-wrap-source-code .example-wrap pre > code * { +.example-wrap pre > code * { word-break: break-all; } -:root.word-wrap-source-code .example-wrap.digits-1 pre > code { - padding-left: calc( - 1ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-2 pre > code { - padding-left: calc( - 2ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-3 pre > code { - padding-left: calc( - 3ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-4 pre > code { - padding-left: calc( - 4ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-5 pre > code { - padding-left: calc( - 5ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-6 pre > code { - padding-left: calc( - 6ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-7 pre > code { - padding-left: calc( - 7ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); -} -:root.word-wrap-source-code .example-wrap.digits-8 pre > code { - padding-left: calc( - 8ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +.example-wrap [data-nosnippet]:target { + border-right: none; } -:root.word-wrap-source-code .example-wrap.digits-9 pre > code { - padding-left: calc( - 9ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +.example-wrap .line-highlighted[data-nosnippet] { + background-color: var(--src-line-number-highlighted-background-color); } .example-wrap.hide-lines [data-nosnippet] { display: none; From e3672fae4864a8ce625b2398223eef4b5b3e3013 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 18 Feb 2025 18:07:14 +0100 Subject: [PATCH 2/8] Add new unstable `--generate-macro-expansion` rustdoc command line flag --- src/librustdoc/config.rs | 11 +++++++++++ src/librustdoc/html/highlight.rs | 25 ++++++++++++++++--------- src/librustdoc/html/render/context.rs | 2 ++ src/librustdoc/html/render/span_map.rs | 10 +++++----- src/librustdoc/lib.rs | 8 ++++++++ 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index fd4d9845c8920..e7b1950166778 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -307,6 +307,8 @@ pub(crate) struct RenderOptions { pub(crate) parts_out_dir: Option, /// disable minification of CSS/JS pub(crate) disable_minification: bool, + /// If `true`, HTML source pages will generate the possibility to expand macros. + pub(crate) generate_macro_expansion: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -775,6 +777,7 @@ impl Options { let show_type_layout = matches.opt_present("show-type-layout"); let nocapture = matches.opt_present("nocapture"); let generate_link_to_definition = matches.opt_present("generate-link-to-definition"); + let generate_macro_expansion = matches.opt_present("generate-macro-expansion"); let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); @@ -790,6 +793,13 @@ impl Options { .with_note("`--generate-link-to-definition` option will be ignored") .emit(); } + if generate_macro_expansion && (show_coverage || output_format != OutputFormat::Html) { + dcx.struct_warn( + "`--generate-macro-expansion` option can only be used with HTML output format", + ) + .with_note("`--generate-macro-expansion` option will be ignored") + .emit(); + } let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx); let with_examples = matches.opt_strs("with-examples"); @@ -870,6 +880,7 @@ impl Options { unstable_features, emit, generate_link_to_definition, + generate_macro_expansion, call_locations, no_emit_shared: false, html_no_source, diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index f874acdcb0225..161101422f670 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -274,7 +274,7 @@ fn get_expansion<'a, W: Write>( && let Some(expanded_code) = expanded_codes.iter().find(|code| code.start_line == line) { let (closing, reopening) = if let Some(current_class) = token_handler.current_class - && let class = current_class.as_html() + && let class = current_class.as_html() && !class.is_empty() { ("", format!("")) @@ -314,11 +314,15 @@ fn end_expansion(token_handler: &mut TokenHandler<'_, '_, W>, level: u } let mut out = String::new(); let mut end = String::new(); - for (tag, class) in token_handler.closing_tags.iter().skip(token_handler.closing_tags.len() - level) { + for (tag, class) in + token_handler.closing_tags.iter().skip(token_handler.closing_tags.len() - level) + { out.push_str(tag); end.push_str(&format!("", class.as_html())); } - token_handler.pending_elems.push((Cow::Owned(format!("{out}{end}")), Some(Class::Expansion))); + token_handler + .pending_elems + .push((Cow::Owned(format!("{out}{end}")), Some(Class::Expansion))); } #[derive(Clone, Copy)] @@ -392,8 +396,7 @@ pub(super) fn write_code( .href_context .as_ref() .and_then(|c| c.context.shared.expanded_codes.get(&c.file_span.lo())); - let mut current_expansion = - get_expansion(&mut token_handler, expanded_codes, line); + let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, line); token_handler.write_pending_elems(None); let mut level = 0; @@ -433,8 +436,7 @@ pub(super) fn write_code( .push((Cow::Borrowed(text), Some(Class::Backline(line)))); } if current_expansion.is_none() { - current_expansion = - get_expansion(&mut token_handler, expanded_codes, line); + current_expansion = get_expansion(&mut token_handler, expanded_codes, line); } } else { token_handler.pending_elems.push((Cow::Borrowed(text), class)); @@ -879,7 +881,9 @@ impl<'src> Classifier<'src> { ) { let lookahead = self.peek(); let file_span = self.file_span; - let no_highlight = |sink: &mut dyn FnMut(_, _)| sink(new_span(before, text, file_span), Highlight::Token { text, class: None }); + let no_highlight = |sink: &mut dyn FnMut(_, _)| { + sink(new_span(before, text, file_span), Highlight::Token { text, class: None }) + }; let whitespace = |sink: &mut dyn FnMut(_, _)| { let mut start = 0u32; for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) { @@ -1044,7 +1048,10 @@ impl<'src> Classifier<'src> { TokenKind::CloseBracket => { if self.in_attribute { self.in_attribute = false; - sink(new_span(before, text, file_span), Highlight::Token { text: "]", class: None }); + sink( + new_span(before, text, file_span), + Highlight::Token { text: "]", class: None }, + ); sink(DUMMY_SP, Highlight::ExitSpan); return; } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 1479cd6e958c1..70f817145c5f2 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -489,6 +489,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { generate_redirect_map, show_type_layout, generate_link_to_definition, + generate_macro_expansion, call_locations, no_emit_shared, html_no_source, @@ -553,6 +554,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { &src_root, include_sources, generate_link_to_definition, + generate_macro_expansion, ); let (sender, receiver) = channel(); diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 8e90731e1423c..c69f89ecebd46 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -60,6 +60,7 @@ pub(crate) fn collect_spans_and_sources( src_root: &Path, include_sources: bool, generate_link_to_definition: bool, + generate_macro_expansion: bool, ) -> ( FxIndexMap, FxHashMap, @@ -69,7 +70,9 @@ pub(crate) fn collect_spans_and_sources( let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; let mut expanded_visitor = ExpandedCodeVisitor { tcx, expanded_codes: Vec::new() }; - tcx.hir_walk_toplevel_module(&mut expanded_visitor); + if generate_macro_expansion { + tcx.hir_walk_toplevel_module(&mut expanded_visitor); + } if generate_link_to_definition { tcx.hir_walk_toplevel_module(&mut visitor); } @@ -353,10 +356,7 @@ impl<'tcx> ExpandedCodeVisitor<'tcx> { } } else { // We add a new item. - self.expanded_codes.push(ExpandedCodeInfo { - span: new_span, - code: f(self.tcx), - }); + self.expanded_codes.push(ExpandedCodeInfo { span: new_span, code: f(self.tcx) }); } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 1f2f8f7d33a4a..68c8966c2a1b3 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -699,6 +699,14 @@ fn opts() -> Vec { "removed, see issue #44136 for more information", "[rust]", ), + opt( + Unstable, + Flag, + "", + "generate-macro-expansion", + "Add possibility to expand macros in the HTML source code pages", + "", + ), ] } From 89682f55a93ebd1c9d738cde7f382db501b3bb53 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 21 Feb 2025 00:44:32 +0100 Subject: [PATCH 3/8] Add documentation for `--generate-macro-expansion` --- src/doc/rustdoc/src/unstable-features.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index d4ff69a99338b..ad09ee205e473 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -830,3 +830,7 @@ will be split as follows: "you today?", ] ``` + +## `--generate-macro-expansion`: Generate macros expansion toggles in source code + +This flag enables the generation of toggles to expand macros in the HTML source code pages, From 615f19dc1d4b06d69f127c33d246ba657adfda50 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 21 Feb 2025 10:39:50 +0100 Subject: [PATCH 4/8] Update `tests/run-make/rustdoc-default-output` test --- tests/run-make/rustdoc-default-output/output-default.stdout | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/run-make/rustdoc-default-output/output-default.stdout b/tests/run-make/rustdoc-default-output/output-default.stdout index c1b246e849ce4..ac66451405a40 100644 --- a/tests/run-make/rustdoc-default-output/output-default.stdout +++ b/tests/run-make/rustdoc-default-output/output-default.stdout @@ -214,6 +214,9 @@ Options: removed, see issue #44136 for more information + --generate-macro-expansion + Add possibility to expand macros in the HTML source + code pages @path Read newline separated options from `path` From e7a9d999a9b7f0126d5261cab47dcf1d4a21d8de Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 24 Feb 2025 18:13:50 +0100 Subject: [PATCH 5/8] Correctly handle multiple macro expansions on a same line --- src/librustdoc/html/highlight.rs | 57 ++++++++++++++++------ src/librustdoc/html/static/css/rustdoc.css | 10 ++-- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 161101422f670..6ccc2b5b9b671 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -265,14 +265,25 @@ fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) { out.write_str(extra).unwrap(); } +fn get_next_expansion<'a>( + expanded_codes: Option<&'a Vec>, + line: u32, + span: Span, +) -> Option<&'a ExpandedCode> { + if let Some(expanded_codes) = expanded_codes { + expanded_codes.iter().find(|code| code.start_line == line && code.span.lo() >= span.lo()) + } else { + None + } +} + fn get_expansion<'a, W: Write>( token_handler: &mut TokenHandler<'_, '_, W>, expanded_codes: Option<&'a Vec>, line: u32, + span: Span, ) -> Option<&'a ExpandedCode> { - if let Some(expanded_codes) = expanded_codes - && let Some(expanded_code) = expanded_codes.iter().find(|code| code.start_line == line) - { + if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) { let (closing, reopening) = if let Some(current_class) = token_handler.current_class && let class = current_class.as_html() && !class.is_empty() @@ -307,10 +318,21 @@ fn start_expansion(out: &mut Vec<(Cow<'_, str>, Option)>, expanded_code: )); } -fn end_expansion(token_handler: &mut TokenHandler<'_, '_, W>, level: usize) { +fn end_expansion<'a, W: Write>( + token_handler: &mut TokenHandler<'_, '_, W>, + expanded_codes: Option<&'a Vec>, + level: usize, + line: u32, + span: Span, +) -> Option<&'a ExpandedCode> { + if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) { + // We close the current "original" content. + token_handler.pending_elems.push((Cow::Borrowed(""), Some(Class::Expansion))); + return Some(expanded_code); + } if level == 0 { token_handler.pending_elems.push((Cow::Borrowed(""), Some(Class::Expansion))); - return; + return None; } let mut out = String::new(); let mut end = String::new(); @@ -323,6 +345,7 @@ fn end_expansion(token_handler: &mut TokenHandler<'_, '_, W>, level: u token_handler .pending_elems .push((Cow::Owned(format!("{out}{end}")), Some(Class::Expansion))); + None } #[derive(Clone, Copy)] @@ -392,11 +415,14 @@ pub(super) fn write_code( (0, u32::MAX) }; - let expanded_codes = token_handler - .href_context - .as_ref() - .and_then(|c| c.context.shared.expanded_codes.get(&c.file_span.lo())); - let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, line); + let (expanded_codes, file_span) = match token_handler.href_context.as_ref().and_then(|c| { + let expanded_codes = c.context.shared.expanded_codes.get(&c.file_span.lo())?; + Some((expanded_codes, c.file_span)) + }) { + Some((expanded_codes, file_span)) => (Some(expanded_codes), file_span), + None => (None, DUMMY_SP), + }; + let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, line, file_span); token_handler.write_pending_elems(None); let mut level = 0; @@ -436,7 +462,8 @@ pub(super) fn write_code( .push((Cow::Borrowed(text), Some(Class::Backline(line)))); } if current_expansion.is_none() { - current_expansion = get_expansion(&mut token_handler, expanded_codes, line); + current_expansion = + get_expansion(&mut token_handler, expanded_codes, line, span); } } else { token_handler.pending_elems.push((Cow::Borrowed(text), class)); @@ -452,9 +479,11 @@ pub(super) fn write_code( } } if need_end { - end_expansion(&mut token_handler, level); - current_expansion = None; - level = 0; + current_expansion = + end_expansion(&mut token_handler, expanded_codes, level, line, span); + if current_expansion.is_none() { + level = 0; + } } } } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 8e3141aad5cd5..a987c537b8b53 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -947,10 +947,10 @@ ul.block, .block li, .block ul { .example-wrap.digits-8 { --example-wrap-digits-count: 8ch; } .example-wrap.digits-9 { --example-wrap-digits-count: 9ch; } -.src .example-wrap [data-nosnippet] { +.example-wrap [data-nosnippet] { width: calc(var(--example-wrap-digits-count) + var(--line-number-padding) * 2); } -.src .example-wrap pre > code { +.example-wrap pre > code { padding-left: calc( var(--example-wrap-digits-count) + var(--line-number-padding) * 2 + var(--line-number-right-margin)); @@ -976,11 +976,13 @@ ul.block, .block li, .block ul { } .example-wrap pre > code { position: relative; - word-break: break-all; display: block; +} +:root.word-wrap-source-code .example-wrap pre > code { + word-break: break-all; white-space: pre-wrap; } -.example-wrap pre > code * { +:root.word-wrap-source-code .example-wrap pre > code * { word-break: break-all; } .example-wrap [data-nosnippet]:target { From 6346af2ab60a57651b78668afb5f24e33e6de86f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 25 Feb 2025 18:04:31 +0100 Subject: [PATCH 6/8] Update browser-ui-test version to `0.20.5` --- .../docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version index 3428dd4826a73..1b619f3482367 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version @@ -1 +1 @@ -0.20.3 \ No newline at end of file +0.20.5 From e3ff70762c85bd7aa4ba1593d1c1aa39fe671477 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 25 Feb 2025 18:12:35 +0100 Subject: [PATCH 7/8] Update rustdoc-gui tests --- .../docblock-code-block-line-number.goml | 4 +-- .../scrape-examples-button-focus.goml | 2 +- tests/rustdoc-gui/source-code-wrapping.goml | 29 ++++++++++++++----- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/rustdoc-gui/docblock-code-block-line-number.goml b/tests/rustdoc-gui/docblock-code-block-line-number.goml index 97273ceb195fa..0df9cc2a65983 100644 --- a/tests/rustdoc-gui/docblock-code-block-line-number.goml +++ b/tests/rustdoc-gui/docblock-code-block-line-number.goml @@ -129,13 +129,13 @@ define-function: ("check-line-numbers-existence", [], block { wait-for-local-storage-false: {"rustdoc-line-numbers": "true" } assert-false: ".example-line-numbers" // Line numbers should still be there. - assert-css: ("[data-nosnippet]", { "display": "inline-block"}) + assert-css: ("[data-nosnippet]", { "display": "block"}) // Now disabling the setting. click: "input#line-numbers" wait-for-local-storage: {"rustdoc-line-numbers": "true" } assert-false: ".example-line-numbers" // Line numbers should still be there. - assert-css: ("[data-nosnippet]", { "display": "inline-block"}) + assert-css: ("[data-nosnippet]", { "display": "block"}) // Closing settings menu. click: "#settings-menu" wait-for-css: ("#settings", {"display": "none"}) diff --git a/tests/rustdoc-gui/scrape-examples-button-focus.goml b/tests/rustdoc-gui/scrape-examples-button-focus.goml index 12246a3766151..f6e836e2360d8 100644 --- a/tests/rustdoc-gui/scrape-examples-button-focus.goml +++ b/tests/rustdoc-gui/scrape-examples-button-focus.goml @@ -5,7 +5,7 @@ go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html" // The next/prev buttons vertically scroll the code viewport between examples move-cursor-to: ".scraped-example-list > .scraped-example" wait-for: ".scraped-example-list > .scraped-example .next" -store-value: (initialScrollTop, 250) +store-value: (initialScrollTop, 236) assert-property: (".scraped-example-list > .scraped-example .rust", { "scrollTop": |initialScrollTop|, }, NEAR) diff --git a/tests/rustdoc-gui/source-code-wrapping.goml b/tests/rustdoc-gui/source-code-wrapping.goml index cb2fd3052cdac..0dab9c72ea9fd 100644 --- a/tests/rustdoc-gui/source-code-wrapping.goml +++ b/tests/rustdoc-gui/source-code-wrapping.goml @@ -31,17 +31,32 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/trait_bounds/index.html" click: "#settings-menu" wait-for: "#settings" -store-size: (".example-wrap .rust code", {"width": rust_width, "height": rust_height}) -store-size: (".example-wrap .language-text code", {"width": txt_width, "height": txt_height}) +store-property: (".example-wrap .rust code", {"scrollWidth": rust_width, "scrollHeight": rust_height}) +store-property: (".example-wrap .language-text code", {"scrollWidth": txt_width, "scrollHeight": txt_height}) call-function: ("click-code-wrapping", {"expected": "true"}) -wait-for-size-false: (".example-wrap .rust code", {"width": |rust_width|, "height": |rust_height|}) +wait-for-property-false: ( + ".example-wrap .rust code", + {"scrollWidth": |rust_width|, "scrollHeight": |rust_height|}, +) -store-size: (".example-wrap .rust code", {"width": new_rust_width, "height": new_rust_height}) -store-size: (".example-wrap .language-text code", {"width": new_txt_width, "height": new_txt_height}) +store-property: ( + ".example-wrap .rust code", + {"scrollWidth": new_rust_width, "scrollHeight": new_rust_height}, +) +store-property: ( + ".example-wrap .language-text code", + {"scrollWidth": new_txt_width, "scrollHeight": new_txt_height}, +) assert: |rust_width| > |new_rust_width| && |rust_height| < |new_rust_height| assert: |txt_width| > |new_txt_width| && |txt_height| < |new_txt_height| call-function: ("click-code-wrapping", {"expected": "false"}) -wait-for-size: (".example-wrap .rust code", {"width": |rust_width|, "height": |rust_height|}) -assert-size: (".example-wrap .language-text code", {"width": |txt_width|, "height": |txt_height|}) +wait-for-property: ( + ".example-wrap .rust code", + {"scrollWidth": |rust_width|, "scrollHeight": |rust_height|}, +) +assert-property: ( + ".example-wrap .language-text code", + {"scrollWidth": |txt_width|, "scrollHeight": |txt_height|}, +) From b666b7f331f7b0fc5458511d5c10a5d736a1c8f0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 26 Feb 2025 01:13:05 +0100 Subject: [PATCH 8/8] Add GUI test for `--generate-macro-expansion` option --- tests/rustdoc-gui/macro-expansion.goml | 74 +++++++++++++++++++ tests/rustdoc-gui/sidebar-source-code.goml | 2 +- .../src/macro_expansion/Cargo.lock | 7 ++ .../src/macro_expansion/Cargo.toml | 7 ++ tests/rustdoc-gui/src/macro_expansion/lib.rs | 28 +++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-gui/macro-expansion.goml create mode 100644 tests/rustdoc-gui/src/macro_expansion/Cargo.lock create mode 100644 tests/rustdoc-gui/src/macro_expansion/Cargo.toml create mode 100644 tests/rustdoc-gui/src/macro_expansion/lib.rs diff --git a/tests/rustdoc-gui/macro-expansion.goml b/tests/rustdoc-gui/macro-expansion.goml new file mode 100644 index 0000000000000..3d790ae37a8f0 --- /dev/null +++ b/tests/rustdoc-gui/macro-expansion.goml @@ -0,0 +1,74 @@ +// This test ensures that the macro expansion is generated and working as expected. +go-to: "file://" + |DOC_PATH| + "/src/macro_expansion/lib.rs.html" + +define-function: ( + "check-expansion", + [line, original_content], + block { + assert-text: ("a[id='" + |line| + "'] + .expansion .original", |original_content|) + // The "original" content should be expanded. + assert-css: ("a[id='" + |line| + "'] + .expansion .original", {"display": "inline"}) + // The expanded macro should be hidden. + assert-css: ("a[id='" + |line| + "'] + .expansion .expanded", {"display": "none"}) + + // We "expand" the macro. + click: "a[id='" + |line| + "'] + .expansion label" + // The "original" content is hidden. + assert-css: ("a[id='" + |line| + "'] + .expansion .original", {"display": "none"}) + // The expanded macro is visible. + assert-css: ("a[id='" + |line| + "'] + .expansion .expanded", {"display": "inline"}) + + // We collapse the macro. + click: "a[id='" + |line| + "'] + .expansion label" + // The "original" content is expanded. + assert-css: ("a[id='" + |line| + "'] + .expansion .original", {"display": "inline"}) + // The expanded macro is hidden. + assert-css: ("a[id='" + |line| + "'] + .expansion .expanded", {"display": "none"}) + } +) + +// First we check the derive macro expansion at line 12. +call-function: ("check-expansion", {"line": 12, "original_content": "Debug"}) +// Then we check the `bar` macro expansion at line 22. +call-function: ("check-expansion", {"line": 22, "original_content": "bar!(y)"}) +// Then we check the `println` macro expansion at line 23-25. +call-function: ("check-expansion", {"line": 23, "original_content": 'println!(" +24 {y} +25 ")'}) + +// Then finally we check when there are two macro calls on a same line. +assert-count: ("#expand-27 ~ .original", 2) +assert-count: ("#expand-27 ~ .expanded", 2) + +store-value: (repeat_o, '/following-sibling::*[@class="original"]') +store-value: (repeat_e, '/following-sibling::*[@class="expanded"]') +assert-text: ('//*[@id="expand-27"]' + |repeat_o|, "stringify!(foo)") +assert-text: ('//*[@id="expand-27"]' + |repeat_o| + |repeat_o|, "stringify!(bar)") +assert-text: ('//*[@id="expand-27"]' + |repeat_e|, '"foo"') +assert-text: ('//*[@id="expand-27"]' + |repeat_e| + |repeat_e|, '"bar"') + +// The "original" content should be expanded. +assert-css: ('//*[@id="expand-27"]' + |repeat_o|, {"display": "inline"}) +assert-css: ('//*[@id="expand-27"]' + |repeat_o| + |repeat_o|, {"display": "inline"}) +// The expanded macro should be hidden. +assert-css: ('//*[@id="expand-27"]' + |repeat_e|, {"display": "none"}) +assert-css: ('//*[@id="expand-27"]' + |repeat_e| + |repeat_e|, {"display": "none"}) + +// We "expand" the macro (because the line starts with a string, the label is not at the "top +// level" of the ``, so we need to use a different selector). +click: ".expansion label[for='expand-27']" +// The "original" content is hidden. +assert-css: ('//*[@id="expand-27"]' + |repeat_o|, {"display": "none"}) +assert-css: ('//*[@id="expand-27"]' + |repeat_o| + |repeat_o|, {"display": "none"}) +// The expanded macro is visible. +assert-css: ('//*[@id="expand-27"]' + |repeat_e|, {"display": "inline"}) +assert-css: ('//*[@id="expand-27"]' + |repeat_e| + |repeat_e|, {"display": "inline"}) + +// We collapse the macro. +click: ".expansion label[for='expand-27']" +// The "original" content is expanded. +assert-css: ('//*[@id="expand-27"]' + |repeat_o|, {"display": "inline"}) +assert-css: ('//*[@id="expand-27"]' + |repeat_o| + |repeat_o|, {"display": "inline"}) +// The expanded macro is hidden. +assert-css: ('//*[@id="expand-27"]' + |repeat_e|, {"display": "none"}) +assert-css: ('//*[@id="expand-27"]' + |repeat_e| + |repeat_e|, {"display": "none"}) diff --git a/tests/rustdoc-gui/sidebar-source-code.goml b/tests/rustdoc-gui/sidebar-source-code.goml index 6afccf6a95fe2..a28e9b22a7191 100644 --- a/tests/rustdoc-gui/sidebar-source-code.goml +++ b/tests/rustdoc-gui/sidebar-source-code.goml @@ -71,7 +71,7 @@ assert: "//*[@class='dir-entry' and @open]/*[normalize-space()='sub_mod']" // Only "another_folder" should be "open" in "lib2". assert: "//*[@class='dir-entry' and not(@open)]/*[normalize-space()='another_mod']" // All other trees should be collapsed. -assert-count: ("//*[@id='src-sidebar']/details[not(normalize-space()='lib2') and not(@open)]", 11) +assert-count: ("//*[@id='src-sidebar']/details[not(normalize-space()='lib2') and not(@open)]", 12) // We now switch to mobile mode. set-window-size: (600, 600) diff --git a/tests/rustdoc-gui/src/macro_expansion/Cargo.lock b/tests/rustdoc-gui/src/macro_expansion/Cargo.lock new file mode 100644 index 0000000000000..9c5cee8fb9dca --- /dev/null +++ b/tests/rustdoc-gui/src/macro_expansion/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "macro_expansion" +version = "0.1.0" diff --git a/tests/rustdoc-gui/src/macro_expansion/Cargo.toml b/tests/rustdoc-gui/src/macro_expansion/Cargo.toml new file mode 100644 index 0000000000000..6d362850fc52f --- /dev/null +++ b/tests/rustdoc-gui/src/macro_expansion/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "macro_expansion" +version = "0.1.0" +edition = "2021" + +[lib] +path = "lib.rs" diff --git a/tests/rustdoc-gui/src/macro_expansion/lib.rs b/tests/rustdoc-gui/src/macro_expansion/lib.rs new file mode 100644 index 0000000000000..c61428049f0b3 --- /dev/null +++ b/tests/rustdoc-gui/src/macro_expansion/lib.rs @@ -0,0 +1,28 @@ +// Test crate used to check the `--generate-macro-expansion` option. +//@ compile-flags: -Zunstable-options --generate-macro-expansion --generate-link-to-definition + +#[macro_export] +macro_rules! bar { + ($x:ident) => {{ + $x += 2; + $x *= 2; + }} +} + +#[derive(Debug)] +pub struct Bar { + a: String, + b: u8, +} + +fn y_f(_: &str, _: &str, _: &str) {} + +fn foo() { + let mut y = 0; + bar!(y); + println!(" + {y} + "); + let s = y_f("\ +bla", stringify!(foo), stringify!(bar)); +}