Skip to content

Commit 709ab04

Browse files
committed
move needless_doctest_main to separate file
1 parent 61912d3 commit 709ab04

File tree

2 files changed

+105
-93
lines changed

2 files changed

+105
-93
lines changed

clippy_lints/src/doc/mod.rs

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,31 @@ use pulldown_cmark::Event::{
99
};
1010
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
1111
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
12-
use rustc_ast::ast::{Async, Attribute, Fn, FnRetTy, ItemKind};
12+
use rustc_ast::ast::Attribute;
1313
use rustc_ast::token::CommentKind;
1414
use rustc_ast::{AttrKind, AttrStyle};
1515
use rustc_data_structures::fx::FxHashSet;
16-
use rustc_data_structures::sync::Lrc;
17-
use rustc_errors::emitter::EmitterWriter;
18-
use rustc_errors::{Applicability, Handler};
16+
use rustc_errors::Applicability;
1917
use rustc_hir as hir;
2018
use rustc_hir::intravisit::{self, Visitor};
2119
use rustc_hir::{AnonConst, Expr};
2220
use rustc_lint::{LateContext, LateLintPass};
2321
use rustc_middle::hir::nested_filter;
2422
use rustc_middle::lint::in_external_macro;
2523
use rustc_middle::ty;
26-
use rustc_parse::maybe_new_parser_from_source_str;
27-
use rustc_parse::parser::ForceCollect;
2824
use rustc_resolve::rustdoc::{
2925
add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, DocFragment,
3026
};
31-
use rustc_session::parse::ParseSess;
3227
use rustc_session::{declare_tool_lint, impl_lint_pass};
3328
use rustc_span::edition::Edition;
34-
use rustc_span::source_map::{FilePathMapping, SourceMap};
35-
use rustc_span::{sym, FileName, Span};
29+
use rustc_span::{sym, Span};
3630
use std::ops::Range;
37-
use std::{io, thread};
3831
use url::Url;
3932

4033
mod link_with_quotes;
4134
mod markdown;
4235
mod missing_headers;
36+
mod needless_doctest_main;
4337

4438
declare_clippy_lint! {
4539
/// ### What it does
@@ -624,7 +618,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
624618
if in_code {
625619
if is_rust && !no_test {
626620
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
627-
check_code(cx, &text, edition, range.clone(), fragments);
621+
needless_doctest_main::check(cx, &text, edition, range.clone(), fragments);
628622
}
629623
} else {
630624
if in_link.is_some() {
@@ -645,88 +639,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
645639
headers
646640
}
647641

648-
fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<usize>, fragments: Fragments<'_>) {
649-
fn has_needless_main(code: String, edition: Edition) -> bool {
650-
rustc_driver::catch_fatal_errors(|| {
651-
rustc_span::create_session_globals_then(edition, || {
652-
let filename = FileName::anon_source_code(&code);
653-
654-
let fallback_bundle =
655-
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
656-
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
657-
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
658-
#[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_span_handler
659-
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
660-
let sess = ParseSess::with_span_handler(handler, sm);
661-
662-
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
663-
Ok(p) => p,
664-
Err(errs) => {
665-
drop(errs);
666-
return false;
667-
},
668-
};
669-
670-
let mut relevant_main_found = false;
671-
loop {
672-
match parser.parse_item(ForceCollect::No) {
673-
Ok(Some(item)) => match &item.kind {
674-
ItemKind::Fn(box Fn {
675-
sig, body: Some(block), ..
676-
}) if item.ident.name == sym::main => {
677-
let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
678-
let returns_nothing = match &sig.decl.output {
679-
FnRetTy::Default(..) => true,
680-
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
681-
FnRetTy::Ty(_) => false,
682-
};
683-
684-
if returns_nothing && !is_async && !block.stmts.is_empty() {
685-
// This main function should be linted, but only if there are no other functions
686-
relevant_main_found = true;
687-
} else {
688-
// This main function should not be linted, we're done
689-
return false;
690-
}
691-
},
692-
// Tests with one of these items are ignored
693-
ItemKind::Static(..)
694-
| ItemKind::Const(..)
695-
| ItemKind::ExternCrate(..)
696-
| ItemKind::ForeignMod(..)
697-
// Another function was found; this case is ignored
698-
| ItemKind::Fn(..) => return false,
699-
_ => {},
700-
},
701-
Ok(None) => break,
702-
Err(e) => {
703-
e.cancel();
704-
return false;
705-
},
706-
}
707-
}
708-
709-
relevant_main_found
710-
})
711-
})
712-
.ok()
713-
.unwrap_or_default()
714-
}
715-
716-
let trailing_whitespace = text.len() - text.trim_end().len();
717-
718-
// Because of the global session, we need to create a new session in a different thread with
719-
// the edition we need.
720-
let text = text.to_owned();
721-
if thread::spawn(move || has_needless_main(text, edition))
722-
.join()
723-
.expect("thread::spawn failed")
724-
&& let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace)
725-
{
726-
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
727-
}
728-
}
729-
730642
struct FindPanicUnwrap<'a, 'tcx> {
731643
cx: &'a LateContext<'tcx>,
732644
panic_span: Option<Span>,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::ops::Range;
2+
use std::{io, thread};
3+
4+
use crate::doc::NEEDLESS_DOCTEST_MAIN;
5+
use clippy_utils::diagnostics::span_lint;
6+
use rustc_ast::{Async, Fn, FnRetTy, ItemKind};
7+
use rustc_data_structures::sync::Lrc;
8+
use rustc_errors::emitter::EmitterWriter;
9+
use rustc_errors::Handler;
10+
use rustc_lint::LateContext;
11+
use rustc_parse::maybe_new_parser_from_source_str;
12+
use rustc_parse::parser::ForceCollect;
13+
use rustc_session::parse::ParseSess;
14+
use rustc_span::edition::Edition;
15+
use rustc_span::source_map::{FilePathMapping, SourceMap};
16+
use rustc_span::{sym, FileName};
17+
18+
use super::Fragments;
19+
20+
pub fn check(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<usize>, fragments: Fragments<'_>) {
21+
fn has_needless_main(code: String, edition: Edition) -> bool {
22+
rustc_driver::catch_fatal_errors(|| {
23+
rustc_span::create_session_globals_then(edition, || {
24+
let filename = FileName::anon_source_code(&code);
25+
26+
let fallback_bundle =
27+
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
28+
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
29+
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
30+
#[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_span_handler
31+
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
32+
let sess = ParseSess::with_span_handler(handler, sm);
33+
34+
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
35+
Ok(p) => p,
36+
Err(errs) => {
37+
drop(errs);
38+
return false;
39+
},
40+
};
41+
42+
let mut relevant_main_found = false;
43+
loop {
44+
match parser.parse_item(ForceCollect::No) {
45+
Ok(Some(item)) => match &item.kind {
46+
ItemKind::Fn(box Fn {
47+
sig, body: Some(block), ..
48+
}) if item.ident.name == sym::main => {
49+
let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
50+
let returns_nothing = match &sig.decl.output {
51+
FnRetTy::Default(..) => true,
52+
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
53+
FnRetTy::Ty(_) => false,
54+
};
55+
56+
if returns_nothing && !is_async && !block.stmts.is_empty() {
57+
// This main function should be linted, but only if there are no other functions
58+
relevant_main_found = true;
59+
} else {
60+
// This main function should not be linted, we're done
61+
return false;
62+
}
63+
},
64+
// Tests with one of these items are ignored
65+
ItemKind::Static(..)
66+
| ItemKind::Const(..)
67+
| ItemKind::ExternCrate(..)
68+
| ItemKind::ForeignMod(..)
69+
// Another function was found; this case is ignored
70+
| ItemKind::Fn(..) => return false,
71+
_ => {},
72+
},
73+
Ok(None) => break,
74+
Err(e) => {
75+
e.cancel();
76+
return false;
77+
},
78+
}
79+
}
80+
81+
relevant_main_found
82+
})
83+
})
84+
.ok()
85+
.unwrap_or_default()
86+
}
87+
88+
let trailing_whitespace = text.len() - text.trim_end().len();
89+
90+
// Because of the global session, we need to create a new session in a different thread with
91+
// the edition we need.
92+
let text = text.to_owned();
93+
if thread::spawn(move || has_needless_main(text, edition))
94+
.join()
95+
.expect("thread::spawn failed")
96+
&& let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace)
97+
{
98+
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
99+
}
100+
}

0 commit comments

Comments
 (0)