Skip to content

Commit b835952

Browse files
committed
move needless_doctest_main to separate file
1 parent 1f0dc9c commit b835952

File tree

2 files changed

+105
-92
lines changed

2 files changed

+105
-92
lines changed

clippy_lints/src/doc/mod.rs

Lines changed: 6 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,32 @@ 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, Span};
35-
use rustc_span::{sym, FileName};
29+
use rustc_span::source_map::Span;
30+
use rustc_span::sym;
3631
use std::ops::Range;
37-
use std::{io, thread};
3832
use url::Url;
3933

4034
mod link_with_quotes;
4135
mod markdown;
4236
mod missing_headers;
37+
mod needless_doctest_main;
4338

4439
declare_clippy_lint! {
4540
/// ### What it does
@@ -624,7 +619,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
624619
if in_code {
625620
if is_rust && !no_test {
626621
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
627-
check_code(cx, &text, edition, range.clone(), fragments);
622+
needless_doctest_main::check(cx, &text, edition, range.clone(), fragments);
628623
}
629624
} else {
630625
if in_link.is_some() {
@@ -645,87 +640,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
645640
headers
646641
}
647642

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

0 commit comments

Comments
 (0)