Code change:
} fn unindent_fragments(docs: &mut Vec<DocFragment>) { <del> for fragment in docs { fragment.doc = unindent(&fragment.doc); } } fn unindent(s: &str) -> String { let lines = s.lines().collect::<Vec<&str>>(); let mut saw_first_line = false; let mut saw_second_line = false; let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| { // After we see the first non-whitespace line, look at // the line we have. If it is not whitespace, and therefore // part of the first paragraph, then ignore the indentation // level of the first line let ignore_previous_indents = saw_first_line && !saw_second_line && !line.chars().all(|c| c.is_whitespace()); </del> <ins> // `add` is used in case the most common sugared doc syntax is used ("/// "). The other // fragments kind's lines are never starting with a whitespace unless they are using some // markdown formatting requiring it. Therefore, if the doc block have a mix between the two, // we need to take into account the fact that the minimum indent minus one (to take this // whitespace into account). // // For example: // // /// hello! // #[doc = "another"] // // In this case, you want "hello! another" and not "hello!  another". let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) { // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to // "decide" how much the minimum indent will be. 1 } else { 0 }; </ins> <del> let min_indent = if ignore_previous_indents { usize::MAX } else { min_indent }; </del> <ins> // `min_indent` is used to know how much whitespaces from the start of each lines must be // removed. Example: // // ///     hello! // #[doc = "another"] // // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4 // (5 - 1) whitespaces. let min_indent = match docs .iter() .map(|fragment| { fragment.doc.lines().fold(usize::MAX, |min_indent, line| { if line.chars().all(|c| c.is_whitespace()) { min_indent } else { // Compare against either space or tab, ignoring whether they are // mixed or not. let whitespace = line.chars().take_while(|c| *c == ' ' || *c == 't').count(); cmp::min(min_indent, whitespace) + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add } } }) }) .min() { Some(x) => x, None => return, }; </ins> <del> if saw_first_line { saw_second_line = true; </del> <ins> for fragment in docs { if fragment.doc.lines().count() == 0 { continue; </ins> } <del> if line.chars().all(|c| c.is_whitespace()) { min_indent </del> <ins> let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { min_indent - add </ins> } else { <del> saw_first_line = true; let mut whitespace = 0; line.chars().all(|char| { // Compare against either space or tab, ignoring whether they // are mixed or not if char == ' ' || char == 't' { whitespace += 1; true </del> <ins> min_indent }; fragment.doc = fragment .doc .lines() .map(|line| { if line.chars().all(|c| c.is_whitespace()) { line.to_string() </ins> } else { <del> false </del> <ins> assert!(line.len() >= min_indent); line[min_indent..].to_string() </ins> } <del> }); cmp::min(min_indent, whitespace) } }); if !lines.is_empty() { let mut unindented = vec![lines[0].trim_start().to_string()]; unindented.extend_from_slice( &lines[1..] .iter() .map(|&line| { if line.chars().all(|c| c.is_whitespace()) { line.to_string() } else { assert!(line.len() >= min_indent); line[min_indent..].to_string() } }) .collect::<Vec<_>>(), ); unindented.join("n") } else { s.to_string() </del> <ins> }) .collect::<Vec<_>>() .join("n"); </ins> } }

Related discussion:
text: <!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: /// let example = Example::new() /// .first("hello") #[cfgattr(not(feature = "one"), doc = " .second("hello")n")] /// .third("hello") /// .build(); /// with I expected to see this: ! Instead, this happened: ! <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: running over the code gives me /// let example = Example::new() /// .first("hello") /// .second("hello") /// .third("hello") /// .build(); ///
As explained on discord, <strikethe attributes aren't applied to doc comments</strike(EDIT: they are applied, the problem seems to be that it results in two doc comments for rustdoc). However, your doc comment is split in two so the second half isn't considered as being inside a code block, explaining why you have this display. Even if shows it as one doc comment, rustdoc sees two of them. <strikeMaybe we should add a warning or something that attributes aren't applied to doc comments... What do you think ?</strike
How can an attribute get applied to a doc comment? Both are, under the hood, attributes, no? I would expect that to be equivalent to expecting to only derive Debug if is passed, which is not how that works. I'm guessing what is true is that attributes directly written don't actually merge with -style comments. Potentially relevant: , ,
Simplified reproducer rust //! foo(); #![doc = " bar();"] //! Checking with 1.31.0 it still ignores it (so likely not related), but checking with 1.21.0 (and 1.0.0) it worked as expected, so somewhere within that range (but I don't have any other versions installed at the moment to check).
Compilation broke somewhere between 1.23.0 (works) 1.24.0 (does not work) I tested it with rust //! let x = r#" #![doc = " bar();"] //! "#; //! assert_eq!