Skip to content

Commit 7803a41

Browse files
committed
rustdoc: Unindent doc fragments on Attributes construction
1 parent 3d3dafb commit 7803a41

File tree

7 files changed

+89
-133
lines changed

7 files changed

+89
-133
lines changed

src/librustdoc/clean/types.rs

+86-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use std::cell::RefCell;
22
use std::default::Default;
3-
use std::fmt;
43
use std::hash::Hash;
5-
use std::iter;
64
use std::lazy::SyncOnceCell as OnceCell;
75
use std::path::PathBuf;
86
use std::rc::Rc;
97
use std::sync::Arc;
10-
use std::vec;
8+
use std::{cmp, fmt, iter};
119

1210
use arrayvec::ArrayVec;
1311

@@ -55,6 +53,9 @@ crate use self::Type::{
5553
};
5654
crate use self::Visibility::{Inherited, Public};
5755

56+
#[cfg(test)]
57+
mod tests;
58+
5859
crate type ItemIdSet = FxHashSet<ItemId>;
5960

6061
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
@@ -1028,6 +1029,86 @@ crate fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String {
10281029
acc
10291030
}
10301031

1032+
/// Removes excess indentation on comments in order for the Markdown
1033+
/// to be parsed correctly. This is necessary because the convention for
1034+
/// writing documentation is to provide a space between the /// or //! marker
1035+
/// and the doc text, but Markdown is whitespace-sensitive. For example,
1036+
/// a block of text with four-space indentation is parsed as a code block,
1037+
/// so if we didn't unindent comments, these list items
1038+
///
1039+
/// /// A list:
1040+
/// ///
1041+
/// /// - Foo
1042+
/// /// - Bar
1043+
///
1044+
/// would be parsed as if they were in a code block, which is likely not what the user intended.
1045+
fn unindent_doc_fragments(docs: &mut Vec<DocFragment>) {
1046+
// `add` is used in case the most common sugared doc syntax is used ("/// "). The other
1047+
// fragments kind's lines are never starting with a whitespace unless they are using some
1048+
// markdown formatting requiring it. Therefore, if the doc block have a mix between the two,
1049+
// we need to take into account the fact that the minimum indent minus one (to take this
1050+
// whitespace into account).
1051+
//
1052+
// For example:
1053+
//
1054+
// /// hello!
1055+
// #[doc = "another"]
1056+
//
1057+
// In this case, you want "hello! another" and not "hello! another".
1058+
let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind)
1059+
&& docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc)
1060+
{
1061+
// In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
1062+
// "decide" how much the minimum indent will be.
1063+
1
1064+
} else {
1065+
0
1066+
};
1067+
1068+
// `min_indent` is used to know how much whitespaces from the start of each lines must be
1069+
// removed. Example:
1070+
//
1071+
// /// hello!
1072+
// #[doc = "another"]
1073+
//
1074+
// In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum
1075+
// 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4
1076+
// (5 - 1) whitespaces.
1077+
let Some(min_indent) = docs
1078+
.iter()
1079+
.map(|fragment| {
1080+
fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| {
1081+
if line.chars().all(|c| c.is_whitespace()) {
1082+
min_indent
1083+
} else {
1084+
// Compare against either space or tab, ignoring whether they are
1085+
// mixed or not.
1086+
let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
1087+
cmp::min(min_indent, whitespace)
1088+
+ if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }
1089+
}
1090+
})
1091+
})
1092+
.min()
1093+
else {
1094+
return;
1095+
};
1096+
1097+
for fragment in docs {
1098+
if fragment.doc == kw::Empty {
1099+
continue;
1100+
}
1101+
1102+
let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 {
1103+
min_indent - add
1104+
} else {
1105+
min_indent
1106+
};
1107+
1108+
fragment.indent = min_indent;
1109+
}
1110+
}
1111+
10311112
/// A link that has not yet been rendered.
10321113
///
10331114
/// This link will be turned into a rendered link by [`Item::links`].
@@ -1119,6 +1200,8 @@ impl Attributes {
11191200
}
11201201
}
11211202

1203+
unindent_doc_fragments(&mut doc_strings);
1204+
11221205
Attributes { doc_strings, other_attrs }
11231206
}
11241207

src/librustdoc/passes/unindent_comments/tests.rs renamed to src/librustdoc/clean/types/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
2020
fn run_test(input: &str, expected: &str) {
2121
create_default_session_globals_then(|| {
2222
let mut s = create_doc_fragment(input);
23-
unindent_fragments(&mut s);
23+
unindent_doc_fragments(&mut s);
2424
assert_eq!(collapse_doc_fragments(&s), expected);
2525
});
2626
}

src/librustdoc/doctest.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1174,8 +1174,6 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
11741174
nested: F,
11751175
) {
11761176
let ast_attrs = self.tcx.hir().attrs(hir_id);
1177-
let mut attrs = Attributes::from_ast(ast_attrs, None);
1178-
11791177
if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) {
11801178
if !cfg.matches(&self.sess.parse_sess, Some(self.sess.features_untracked())) {
11811179
return;
@@ -1187,9 +1185,9 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
11871185
self.collector.names.push(name);
11881186
}
11891187

1190-
attrs.unindent_doc_comments();
11911188
// The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
11921189
// anything else, this will combine them for us.
1190+
let attrs = Attributes::from_ast(ast_attrs, None);
11931191
if let Some(doc) = attrs.collapsed_doc_value() {
11941192
// Use the outermost invocation, so that doctest names come from where the docs were written.
11951193
let span = ast_attrs

src/librustdoc/passes/collect_intra_doc_links/early.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ crate fn early_resolve_intra_doc_links(
6363
}
6464

6565
fn doc_attrs<'a>(attrs: impl Iterator<Item = &'a ast::Attribute>) -> Attributes {
66-
let mut attrs = Attributes::from_ast_iter(attrs.map(|attr| (attr, None)), true);
67-
attrs.unindent_doc_comments();
68-
attrs
66+
Attributes::from_ast_iter(attrs.map(|attr| (attr, None)), true)
6967
}
7068

7169
struct EarlyDocLinkResolver<'r, 'ra> {

src/librustdoc/passes/mod.rs

-5
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ crate use self::strip_private::STRIP_PRIVATE;
2424
mod strip_priv_imports;
2525
crate use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
2626

27-
mod unindent_comments;
28-
crate use self::unindent_comments::UNINDENT_COMMENTS;
29-
3027
mod propagate_doc_cfg;
3128
crate use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
3229

@@ -81,7 +78,6 @@ crate enum Condition {
8178
crate const PASSES: &[Pass] = &[
8279
CHECK_DOC_TEST_VISIBILITY,
8380
STRIP_HIDDEN,
84-
UNINDENT_COMMENTS,
8581
STRIP_PRIVATE,
8682
STRIP_PRIV_IMPORTS,
8783
PROPAGATE_DOC_CFG,
@@ -96,7 +92,6 @@ crate const PASSES: &[Pass] = &[
9692
/// The list of passes run by default.
9793
crate const DEFAULT_PASSES: &[ConditionalPass] = &[
9894
ConditionalPass::always(COLLECT_TRAIT_IMPLS),
99-
ConditionalPass::always(UNINDENT_COMMENTS),
10095
ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY),
10196
ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
10297
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),

src/librustdoc/passes/unindent_comments.rs

-116
This file was deleted.

src/test/rustdoc-ui/issue-91713.stdout

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
Available passes for running rustdoc:
22
check_doc_test_visibility - run various visibility-related lints on doctests
33
strip-hidden - strips all `#[doc(hidden)]` items from the output
4-
unindent-comments - removes excess indentation on comments in order for markdown to like it
54
strip-private - strips all private items from a crate which cannot be seen externally, implies strip-priv-imports
65
strip-priv-imports - strips all private import statements (`use`, `extern crate`) from a crate
76
propagate-doc-cfg - propagates `#[doc(cfg(...))]` to child items
@@ -14,7 +13,6 @@ check-invalid-html-tags - detects invalid HTML tags in doc comments
1413

1514
Default passes for rustdoc:
1615
collect-trait-impls
17-
unindent-comments
1816
check_doc_test_visibility
1917
strip-hidden (when not --document-hidden-items)
2018
strip-private (when not --document-private-items)

0 commit comments

Comments
 (0)