Skip to content

Commit 4f4b2f5

Browse files
committed
New rustdoc lint to respect -Dwarnings correctly
This adds a new lint to `rustc` that is used in rustdoc when a code block is empty or cannot be parsed as valid Rust code. Previously this was unconditionally a warning. As such some documentation comments were (unknowingly) abusing this to pass despite the `-Dwarnings` used when compiling `rustc`, this should not be the case anymore.
1 parent 32dce35 commit 4f4b2f5

File tree

9 files changed

+213
-40
lines changed

9 files changed

+213
-40
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+99
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,105 @@ declare_lint! {
18741874
"detects labels that are never used"
18751875
}
18761876

1877+
declare_lint! {
1878+
/// The `broken_intra_doc_links` lint detects failures in resolving
1879+
/// intra-doc link targets. This is a `rustdoc` only lint, see the
1880+
/// documentation in the [rustdoc book].
1881+
///
1882+
/// [rustdoc book]: ../../../rustdoc/lints.html#broken_intra_doc_links
1883+
pub BROKEN_INTRA_DOC_LINKS,
1884+
Warn,
1885+
"failures in resolving intra-doc link targets"
1886+
}
1887+
1888+
declare_lint! {
1889+
/// This is a subset of `broken_intra_doc_links` that warns when linking from
1890+
/// a public item to a private one. This is a `rustdoc` only lint, see the
1891+
/// documentation in the [rustdoc book].
1892+
///
1893+
/// [rustdoc book]: ../../../rustdoc/lints.html#private_intra_doc_links
1894+
pub PRIVATE_INTRA_DOC_LINKS,
1895+
Warn,
1896+
"linking from a public item to a private one"
1897+
}
1898+
1899+
declare_lint! {
1900+
/// The `invalid_codeblock_attributes` lint detects code block attributes
1901+
/// in documentation examples that have potentially mis-typed values. This
1902+
/// is a `rustdoc` only lint, see the documentation in the [rustdoc book].
1903+
///
1904+
/// [rustdoc book]: ../../../rustdoc/lints.html#invalid_codeblock_attributes
1905+
pub INVALID_CODEBLOCK_ATTRIBUTES,
1906+
Warn,
1907+
"codeblock attribute looks a lot like a known one"
1908+
}
1909+
1910+
declare_lint! {
1911+
/// The `invalid_rust_codeblock` lint detects Rust code blocks in
1912+
/// documentation examples that are invalid (e.g. empty, not parsable as
1913+
/// Rust code). This is a `rustdoc` only lint, see the documentation in the
1914+
/// [rustdoc book].
1915+
///
1916+
/// [rustdoc book]: ../../../rustdoc/lints.html#invalid_rust_codeblock
1917+
pub INVALID_RUST_CODEBLOCK,
1918+
Warn,
1919+
"codeblock could not be parsed as valid Rust or is empty"
1920+
}
1921+
1922+
declare_lint! {
1923+
/// The `missing_crate_level_docs` lint detects if documentation is
1924+
/// missing at the crate root. This is a `rustdoc` only lint, see the
1925+
/// documentation in the [rustdoc book].
1926+
///
1927+
/// [rustdoc book]: ../../../rustdoc/lints.html#missing_crate_level_docs
1928+
pub MISSING_CRATE_LEVEL_DOCS,
1929+
Allow,
1930+
"detects crates with no crate-level documentation"
1931+
}
1932+
1933+
declare_lint! {
1934+
/// The `missing_doc_code_examples` lint detects publicly-exported items
1935+
/// without code samples in their documentation. This is a `rustdoc` only
1936+
/// lint, see the documentation in the [rustdoc book].
1937+
///
1938+
/// [rustdoc book]: ../../../rustdoc/lints.html#missing_doc_code_examples
1939+
pub MISSING_DOC_CODE_EXAMPLES,
1940+
Allow,
1941+
"detects publicly-exported items without code samples in their documentation"
1942+
}
1943+
1944+
declare_lint! {
1945+
/// The `private_doc_tests` lint detects code samples in docs of private
1946+
/// items not documented by `rustdoc`. This is a `rustdoc` only lint, see
1947+
/// the documentation in the [rustdoc book].
1948+
///
1949+
/// [rustdoc book]: ../../../rustdoc/lints.html#private_doc_tests
1950+
pub PRIVATE_DOC_TESTS,
1951+
Allow,
1952+
"detects code samples in docs of private items not documented by rustdoc"
1953+
}
1954+
1955+
declare_lint! {
1956+
/// The `invalid_html_tags` lint detects invalid HTML tags. This is a
1957+
/// `rustdoc` only lint, see the documentation in the [rustdoc book].
1958+
///
1959+
/// [rustdoc book]: ../../../rustdoc/lints.html#invalid_html_tags
1960+
pub INVALID_HTML_TAGS,
1961+
Allow,
1962+
"detects invalid HTML tags in doc comments"
1963+
}
1964+
1965+
declare_lint! {
1966+
/// The `non_autolinks` lint detects when a URL could be written using
1967+
/// only angle brackets. This is a `rustdoc` only lint, see the
1968+
/// documentation in the [rustdoc book].
1969+
///
1970+
/// [rustdoc book]: ../../../rustdoc/lints.html#non_autolinks
1971+
pub NON_AUTOLINKS,
1972+
Warn,
1973+
"detects URLs that could be written using only angle brackets"
1974+
}
1975+
18771976
declare_lint! {
18781977
/// The `where_clauses_object_safety` lint detects for [object safety] of
18791978
/// [where clauses].

compiler/rustc_mir/src/borrow_check/region_infer/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
12411241
/// it. However, it works pretty well in practice. In particular,
12421242
/// this is needed to deal with projection outlives bounds like
12431243
///
1244-
/// ```ignore (internal compiler representation so lifetime syntax is invalid)
1244+
/// ```text (internal compiler representation so lifetime syntax is invalid)
12451245
/// <T as Foo<'0>>::Item: '1
12461246
/// ```
12471247
///

compiler/rustc_trait_selection/src/opaque_types.rs

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub struct OpaqueTypeDecl<'tcx> {
4646
/// type Foo = impl Baz;
4747
/// fn bar() -> Foo {
4848
/// // ^^^ This is the span we are looking for!
49+
/// }
4950
/// ```
5051
///
5152
/// In cases where the fn returns `(impl Trait, impl Trait)` or

compiler/rustc_typeck/src/check/upvar.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
385385
///
386386
/// InferBorrowKind results in a structure like this:
387387
///
388-
/// ```
388+
/// ```text
389389
/// {
390390
/// Place(base: hir_id_s, projections: [], ....) -> {
391391
/// capture_kind_expr: hir_id_L5,
@@ -410,7 +410,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
410410
/// ```
411411
///
412412
/// After the min capture analysis, we get:
413-
/// ```
413+
/// ```text
414414
/// {
415415
/// hir_id_s -> [
416416
/// Place(base: hir_id_s, projections: [], ....) -> {

src/doc/rustdoc/src/lints.md

+35
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,41 @@ warning: unclosed HTML tag `h1`
294294
warning: 2 warnings emitted
295295
```
296296

297+
## invalid_rust_codeblock
298+
299+
This lint **warns by default**. It detects Rust code blocks in documentation
300+
examples that are invalid (e.g. empty, not parsable as Rust). For example:
301+
302+
```rust
303+
/// Empty code blocks (with and without the `rust` marker):
304+
///
305+
/// ```rust
306+
/// ```
307+
///
308+
/// Unclosed code blocks (with and without the `rust` marker):
309+
///
310+
/// ```rust
311+
fn main() {}
312+
```
313+
314+
Which will give:
315+
316+
```text
317+
warning: Rust code block is empty
318+
--> src/lib.rs:3:5
319+
|
320+
3 | /// ```rust
321+
| _____^
322+
4 | | /// ```
323+
| |_______^
324+
325+
warning: Rust code block is empty
326+
--> src/lib.rs:8:5
327+
|
328+
8 | /// ```rust
329+
| ^^^^^^^
330+
```
331+
297332
## non_autolinks
298333

299334
This lint is **nightly-only** and **warns by default**. It detects links which

src/librustdoc/lint.rs

+13
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ declare_rustdoc_lint! {
104104
"codeblock attribute looks a lot like a known one"
105105
}
106106

107+
declare_rustdoc_lint! {
108+
/// The `invalid_rust_codeblock` lint detects Rust code blocks in
109+
/// documentation examples that are invalid (e.g. empty, not parsable as
110+
/// Rust code). This is a `rustdoc` only lint, see the documentation in the
111+
/// [rustdoc book].
112+
///
113+
/// [rustdoc book]: ../../../rustdoc/lints.html#invalid_rust_codeblock
114+
INVALID_RUST_CODEBLOCK,
115+
Warn,
116+
"codeblock could not be parsed as valid Rust or is empty"
117+
}
118+
107119
declare_rustdoc_lint! {
108120
/// The `missing_crate_level_docs` lint detects if documentation is
109121
/// missing at the crate root. This is a `rustdoc` only lint, see the
@@ -165,6 +177,7 @@ crate static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
165177
MISSING_DOC_CODE_EXAMPLES,
166178
PRIVATE_DOC_TESTS,
167179
INVALID_CODEBLOCK_ATTRIBUTES,
180+
INVALID_RUST_CODEBLOCK,
168181
INVALID_HTML_TAGS,
169182
NON_AUTOLINKS,
170183
MISSING_CRATE_LEVEL_DOCS,

src/librustdoc/passes/check_code_block_syntax.rs

+60-33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use rustc_data_structures::sync::{Lock, Lrc};
22
use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler};
3+
use rustc_middle::lint::LintDiagnosticBuilder;
34
use rustc_parse::parse_stream_from_source_str;
5+
use rustc_session::lint;
46
use rustc_session::parse::ParseSess;
57
use rustc_span::source_map::{FilePathMapping, SourceMap};
68
use rustc_span::{FileName, InnerSpan};
@@ -47,50 +49,65 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
4749
.unwrap_or(false);
4850
let buffer = buffer.borrow();
4951

50-
if buffer.has_errors || is_empty {
51-
let mut diag = if let Some(sp) = super::source_span_for_markdown_range(
52-
self.cx.tcx,
53-
&dox,
54-
&code_block.range,
55-
&item.attrs,
56-
) {
57-
let (warning_message, suggest_using_text) = if buffer.has_errors {
58-
("could not parse code block as Rust code", true)
52+
if !(buffer.has_errors || is_empty) {
53+
// No errors in a non-empty program.
54+
return;
55+
}
56+
57+
let local_id = match item.def_id.as_local() {
58+
Some(id) => id,
59+
// We don't need to check the syntax for other crates so returning
60+
// without doing anything should not be a problem.
61+
None => return,
62+
};
63+
64+
let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_id);
65+
let suggest_using_text = code_block.syntax.is_none() && code_block.is_fenced;
66+
let is_ignore = code_block.is_ignore;
67+
68+
// The span and whether it is precise or not.
69+
let (sp, precise_span) = match super::source_span_for_markdown_range(
70+
self.cx.tcx,
71+
&dox,
72+
&code_block.range,
73+
&item.attrs,
74+
) {
75+
Some(sp) => (sp, true),
76+
None => (super::span_of_attrs(&item.attrs).unwrap_or(item.source.span()), false),
77+
};
78+
79+
// lambda that will use the lint to start a new diagnostic and add
80+
// a suggestion to it when needed.
81+
let diag_builder = |lint: LintDiagnosticBuilder<'_>| {
82+
let mut diag = if precise_span {
83+
let msg = if buffer.has_errors {
84+
"could not parse code block as Rust code"
5985
} else {
60-
("Rust code block is empty", false)
86+
"Rust code block is empty"
6187
};
6288

63-
let mut diag = self.cx.sess().struct_span_warn(sp, warning_message);
89+
let mut diag = lint.build(msg);
90+
91+
if suggest_using_text {
92+
let extended_msg = if is_ignore {
93+
"`ignore` code blocks require valid Rust code for syntax highlighting. \
94+
Mark blocks that do not contain Rust code as text"
95+
} else {
96+
"mark blocks that do not contain Rust code as text"
97+
};
6498

65-
if code_block.syntax.is_none() && code_block.is_fenced {
66-
let sp = sp.from_inner(InnerSpan::new(0, 3));
6799
diag.span_suggestion(
68-
sp,
69-
"mark blocks that do not contain Rust code as text",
100+
sp.from_inner(InnerSpan::new(0, 3)),
101+
extended_msg,
70102
String::from("```text"),
71103
Applicability::MachineApplicable,
72104
);
73-
} else if suggest_using_text && code_block.is_ignore {
74-
let sp = sp.from_inner(InnerSpan::new(0, 3));
75-
diag.span_suggestion(
76-
sp,
77-
"`ignore` code blocks require valid Rust code for syntax highlighting. \
78-
Mark blocks that do not contain Rust code as text",
79-
String::from("```text,"),
80-
Applicability::MachineApplicable,
81-
);
82105
}
83106

84107
diag
85108
} else {
86-
// We couldn't calculate the span of the markdown block that had the error, so our
87-
// diagnostics are going to be a bit lacking.
88-
let mut diag = self.cx.sess().struct_span_warn(
89-
super::span_of_attrs(&item.attrs).unwrap_or(item.source.span()),
90-
"doc comment contains an invalid Rust code block",
91-
);
92-
93-
if code_block.syntax.is_none() && code_block.is_fenced {
109+
let mut diag = lint.build("doc comment contains an invalid Rust code block");
110+
if suggest_using_text {
94111
diag.help("mark blocks that do not contain Rust code as text: ```text");
95112
}
96113

@@ -103,7 +120,17 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
103120
}
104121

105122
diag.emit();
106-
}
123+
};
124+
125+
// Finally build and emit the completed diagnostic.
126+
// All points of divergence have been handled earlier so this can be
127+
// done the same way whether the span is precise or not.
128+
self.cx.tcx.struct_span_lint_hir(
129+
lint::builtin::INVALID_RUST_CODEBLOCK,
130+
hir_id,
131+
sp,
132+
diag_builder,
133+
);
107134
}
108135
}
109136

src/test/rustdoc-ui/ignore-block-help.stderr

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@ LL | | /// let heart = '❤️';
77
LL | | /// ```
88
| |_______^
99
|
10+
= note: `#[warn(invalid_rust_codeblock)]` on by default
1011
= note: error from rustc: character literal may only contain one codepoint
11-
help: `ignore` code blocks require valid Rust code for syntax highlighting. Mark blocks that do not contain Rust code as text
12-
|
13-
LL | /// ```text,ignore (to-prevent-tidy-error)
14-
| ^^^^^^^^
1512

1613
warning: 1 warning emitted
1714

src/test/rustdoc-ui/invalid-syntax.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ LL | | /// \__________pkt->size___________/ \_result->size_/ \__pkt->si
77
LL | | /// ```
88
| |_______^
99
|
10+
= note: `#[warn(invalid_rust_codeblock)]` on by default
1011
= note: error from rustc: unknown start of token: \
1112
= note: error from rustc: unknown start of token: \
1213
= note: error from rustc: unknown start of token: \

0 commit comments

Comments
 (0)