Skip to content

Rollup of 4 pull requests #80329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
3 changes: 2 additions & 1 deletion compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ use rustc_middle::ty::TyCtxt;
use rustc_session::lint::builtin::{
BARE_TRAIT_OBJECTS, BROKEN_INTRA_DOC_LINKS, ELIDED_LIFETIMES_IN_PATHS,
EXPLICIT_OUTLIVES_REQUIREMENTS, INVALID_CODEBLOCK_ATTRIBUTES, INVALID_HTML_TAGS,
MISSING_DOC_CODE_EXAMPLES, NON_AUTOLINKS, PRIVATE_DOC_TESTS,
INVALID_RUST_CODEBLOCK, MISSING_DOC_CODE_EXAMPLES, NON_AUTOLINKS, PRIVATE_DOC_TESTS,
};
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::Span;
Expand Down Expand Up @@ -319,6 +319,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
BROKEN_INTRA_DOC_LINKS,
PRIVATE_INTRA_DOC_LINKS,
INVALID_CODEBLOCK_ATTRIBUTES,
INVALID_RUST_CODEBLOCK,
MISSING_DOC_CODE_EXAMPLES,
PRIVATE_DOC_TESTS,
INVALID_HTML_TAGS
Expand Down
13 changes: 13 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1846,6 +1846,18 @@ declare_lint! {
"codeblock attribute looks a lot like a known one"
}

declare_lint! {
/// The `invalid_rust_codeblock` lint detects Rust code blocks in
/// documentation examples that are invalid (e.g. empty, not parsable as
/// Rust code). This is a `rustdoc` only lint, see the documentation in the
/// [rustdoc book].
///
/// [rustdoc book]: ../../../rustdoc/lints.html#invalid_rust_codeblock
pub INVALID_RUST_CODEBLOCK,
Warn,
"codeblock could not be parsed as valid Rust or is empty"
}

declare_lint! {
/// The `missing_crate_level_docs` lint detects if documentation is
/// missing at the crate root. This is a `rustdoc` only lint, see the
Expand Down Expand Up @@ -2846,6 +2858,7 @@ declare_lint_pass! {
BROKEN_INTRA_DOC_LINKS,
PRIVATE_INTRA_DOC_LINKS,
INVALID_CODEBLOCK_ATTRIBUTES,
INVALID_RUST_CODEBLOCK,
MISSING_CRATE_LEVEL_DOCS,
MISSING_DOC_CODE_EXAMPLES,
INVALID_HTML_TAGS,
Expand Down
5 changes: 1 addition & 4 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,7 @@ pub enum TyKind<'tcx> {
impl TyKind<'tcx> {
#[inline]
pub fn is_primitive(&self) -> bool {
match self {
Bool | Char | Int(_) | Uint(_) | Float(_) => true,
_ => false,
}
matches!(self, Bool | Char | Int(_) | Uint(_) | Float(_))
}

/// Get the article ("a" or "an") to use with this type.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_trait_selection/src/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub struct OpaqueTypeDecl<'tcx> {
/// type Foo = impl Baz;
/// fn bar() -> Foo {
/// // ^^^ This is the span we are looking for!
/// # }
/// ```
///
/// In cases where the fn returns `(impl Trait, impl Trait)` or
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,8 @@ impl Step for Rustdoc {
cargo.arg("-p").arg("rustdoc");

cargo.rustdocflag("--document-private-items");
cargo.rustdocflag("--enable-index-page");
cargo.rustdocflag("-Zunstable-options");
builder.run(&mut cargo.into());
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/doc/rustdoc/src/lints.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,41 @@ warning: unclosed HTML tag `h1`

warning: 2 warnings emitted
```

## invalid_rust_codeblock

This lint **warns by default**. It detects Rust code blocks in documentation
examples that are invalid (e.g. empty, not parsable as Rust). For example:

```rust
/// Empty code block, with and without Rust:
///
/// ```rust
/// ```
///
/// Unclosed code block:
///
/// ```rust
fn main() {}
```

Which will give:

```text
warning: Rust code block is empty
--> src/lib.rs:3:5
|
3 | /// ```rust
| _____^
4 | | /// ```
| |_______^

warning: Rust code block is empty
--> src/lib.rs:8:5
|
8 | /// ```rust
| ^^^^^^^
```

## non_autolinks

Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ crate fn create_config(
let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name;
let no_crate_level_docs = rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS.name;
let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
let invalid_rust_codeblock = rustc_lint::builtin::INVALID_RUST_CODEBLOCK.name;
let invalid_html_tags = rustc_lint::builtin::INVALID_HTML_TAGS.name;
let renamed_and_removed_lints = rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name;
let non_autolinks = rustc_lint::builtin::NON_AUTOLINKS.name;
Expand All @@ -323,6 +324,7 @@ crate fn create_config(
private_doc_tests.to_owned(),
no_crate_level_docs.to_owned(),
invalid_codeblock_attributes_name.to_owned(),
invalid_rust_codeblock.to_owned(),
invalid_html_tags.to_owned(),
renamed_and_removed_lints.to_owned(),
unknown_lints.to_owned(),
Expand Down
23 changes: 16 additions & 7 deletions src/librustdoc/html/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use std::fmt::{Display, Write};
use std::iter::Peekable;

use rustc_lexer::{LiteralKind, TokenKind};
use rustc_span::symbol::Ident;
use rustc_span::edition::Edition;
use rustc_span::symbol::Symbol;
use rustc_span::with_default_session_globals;

/// Highlights `src`, returning the HTML output.
Expand All @@ -20,6 +21,7 @@ crate fn render_with_highlighting(
class: Option<&str>,
playground_button: Option<&str>,
tooltip: Option<(&str, &str)>,
edition: Edition,
) -> String {
debug!("highlighting: ================\n{}\n==============", src);
let mut out = String::with_capacity(src.len());
Expand All @@ -34,7 +36,7 @@ crate fn render_with_highlighting(
}

write_header(&mut out, class);
write_code(&mut out, &src);
write_code(&mut out, &src, edition);
write_footer(&mut out, playground_button);

out
Expand All @@ -45,10 +47,10 @@ fn write_header(out: &mut String, class: Option<&str>) {
.unwrap()
}

fn write_code(out: &mut String, src: &str) {
fn write_code(out: &mut String, src: &str, edition: Edition) {
// This replace allows to fix how the code source with DOS backline characters is displayed.
let src = src.replace("\r\n", "\n");
Classifier::new(&src).highlight(&mut |highlight| {
Classifier::new(&src, edition).highlight(&mut |highlight| {
match highlight {
Highlight::Token { text, class } => string(out, Escape(text), class),
Highlight::EnterSpan { class } => enter_span(out, class),
Expand Down Expand Up @@ -139,12 +141,19 @@ struct Classifier<'a> {
in_attribute: bool,
in_macro: bool,
in_macro_nonterminal: bool,
edition: Edition,
}

impl<'a> Classifier<'a> {
fn new(src: &str) -> Classifier<'_> {
fn new(src: &str, edition: Edition) -> Classifier<'_> {
let tokens = TokenIter { src }.peekable();
Classifier { tokens, in_attribute: false, in_macro: false, in_macro_nonterminal: false }
Classifier {
tokens,
in_attribute: false,
in_macro: false,
in_macro_nonterminal: false,
edition,
}
}

/// Exhausts the `Classifier` writing the output into `sink`.
Expand Down Expand Up @@ -296,7 +305,7 @@ impl<'a> Classifier<'a> {
"Option" | "Result" => Class::PreludeTy,
"Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
// Keywords are also included in the identifier set.
_ if Ident::from_str(text).is_reserved() => Class::KeyWord,
_ if Symbol::intern(text).is_reserved(|| self.edition) => Class::KeyWord,
_ if self.in_macro_nonterminal => {
self.in_macro_nonterminal = false;
Class::MacroNonTerminal
Expand Down
5 changes: 3 additions & 2 deletions src/librustdoc/html/highlight/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::write_code;
use expect_test::expect_file;
use rustc_span::edition::Edition;

const STYLE: &str = r#"
<style>
Expand All @@ -18,7 +19,7 @@ fn test_html_highlighting() {
let src = include_str!("fixtures/sample.rs");
let html = {
let mut out = String::new();
write_code(&mut out, src);
write_code(&mut out, src, Edition::Edition2018);
format!("{}<pre><code>{}</code></pre>\n", STYLE, out)
};
expect_file!["fixtures/sample.html"].assert_eq(&html);
Expand All @@ -30,6 +31,6 @@ fn test_dos_backline() {
println!(\"foo\");\r\n\
}\r\n";
let mut html = String::new();
write_code(&mut html, src);
write_code(&mut html, src, Edition::Edition2018);
expect_file!["fixtures/dos_line.html"].assert_eq(&html);
}
2 changes: 2 additions & 0 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
)),
playground_button.as_deref(),
Some((s1.as_str(), s2)),
edition,
));
Some(Event::Html(s.into()))
} else {
Expand All @@ -335,6 +336,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
)),
playground_button.as_deref(),
None,
edition,
));
Some(Event::Html(s.into()))
}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4747,6 +4747,7 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac
Some("macro"),
None,
None,
it.source.span().edition(),
))
});
document(w, cx, it, None)
Expand Down
7 changes: 4 additions & 3 deletions src/librustdoc/html/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::html::layout;
use crate::html::render::{SharedContext, BASIC_KEYWORDS};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_session::Session;
use rustc_span::edition::Edition;
use rustc_span::source_map::FileName;
use std::ffi::OsStr;
use std::fs;
Expand Down Expand Up @@ -132,7 +133,7 @@ impl SourceCollector<'_, '_> {
&self.scx.layout,
&page,
"",
|buf: &mut _| print_src(buf, contents),
|buf: &mut _| print_src(buf, contents, self.scx.edition),
&self.scx.style_files,
);
self.scx.fs.write(&cur, v.as_bytes())?;
Expand Down Expand Up @@ -170,7 +171,7 @@ where

/// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side.
fn print_src(buf: &mut Buffer, s: String) {
fn print_src(buf: &mut Buffer, s: String, edition: Edition) {
let lines = s.lines().count();
let mut cols = 0;
let mut tmp = lines;
Expand All @@ -183,5 +184,5 @@ fn print_src(buf: &mut Buffer, s: String) {
write!(buf, "<span id=\"{0}\">{0:1$}</span>\n", i, cols);
}
write!(buf, "</pre>");
write!(buf, "{}", highlight::render_with_highlighting(s, None, None, None));
write!(buf, "{}", highlight::render_with_highlighting(s, None, None, None, edition));
}
71 changes: 49 additions & 22 deletions src/librustdoc/passes/check_code_block_syntax.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use rustc_data_structures::sync::{Lock, Lrc};
use rustc_errors::{emitter::Emitter, Applicability, Diagnostic, Handler};
use rustc_middle::lint::LintDiagnosticBuilder;
use rustc_parse::parse_stream_from_source_str;
use rustc_session::lint;
use rustc_session::parse::ParseSess;
use rustc_span::source_map::{FilePathMapping, SourceMap};
use rustc_span::{FileName, InnerSpan};
Expand Down Expand Up @@ -47,51 +49,76 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
.unwrap_or(false);
let buffer = buffer.borrow();

if buffer.has_errors || is_empty {
let mut diag = if let Some(sp) =
super::source_span_for_markdown_range(self.cx, &dox, &code_block.range, &item.attrs)
{
let warning_message = if buffer.has_errors {
if !(buffer.has_errors || is_empty) {
// No errors in a non-empty program.
return;
}

let local_id = match item.def_id.as_local() {
Some(id) => id,
// We don't need to check the syntax for other crates so returning
// without doing anything should not be a problem.
None => return,
};

let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_id);
let mark_with_text = code_block.syntax.is_none() && code_block.is_fenced;

// The span and whether it is precise or not.
let (sp, precise_span) = match super::source_span_for_markdown_range(
self.cx,
&dox,
&code_block.range,
&item.attrs,
) {
Some(sp) => (sp, true),
None => (super::span_of_attrs(&item.attrs).unwrap_or(item.source.span()), false),
};

// lambda that will use the lint to start a new diagnostic and add
// a suggestion to it when needed.
let diag_builder = |lint: LintDiagnosticBuilder<'_>| {
let mut diag = if precise_span {
let msg = if buffer.has_errors {
"could not parse code block as Rust code"
} else {
"Rust code block is empty"
};

let mut diag = self.cx.sess().struct_span_warn(sp, warning_message);

if code_block.syntax.is_none() && code_block.is_fenced {
let sp = sp.from_inner(InnerSpan::new(0, 3));
let mut diag = lint.build(msg);
if mark_with_text {
diag.span_suggestion(
sp,
sp.from_inner(InnerSpan::new(0, 3)),
"mark blocks that do not contain Rust code as text",
String::from("```text"),
Applicability::MachineApplicable,
);
}

diag
} else {
// We couldn't calculate the span of the markdown block that had the error, so our
// diagnostics are going to be a bit lacking.
let mut diag = self.cx.sess().struct_span_warn(
super::span_of_attrs(&item.attrs).unwrap_or(item.source.span()),
"doc comment contains an invalid Rust code block",
);

if code_block.syntax.is_none() && code_block.is_fenced {
let mut diag = lint.build("doc comment contains an invalid Rust code block");
if mark_with_text {
diag.help("mark blocks that do not contain Rust code as text: ```text");
}

diag
};

// FIXME(#67563): Provide more context for these errors by displaying the spans inline.
for message in buffer.messages.iter() {
diag.note(&message);
}

diag.emit();
}
};

// Finally build and emit the completed diagnostic.
// All points of divergence have been handled earlier so this can be
// done the same way whether the span is precise or not.
self.cx.tcx.struct_span_lint_hir(
lint::builtin::INVALID_RUST_CODEBLOCK,
hir_id,
sp,
diag_builder,
);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/test/rustdoc-ui/invalid-syntax.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ LL | | /// \__________pkt->size___________/ \_result->size_/ \__pkt->si
LL | | /// ```
| |_______^
|
= note: `#[warn(invalid_rust_codeblock)]` on by default
= note: error from rustc: unknown start of token: \
= note: error from rustc: unknown start of token: \
= note: error from rustc: unknown start of token: \
Expand Down