Skip to content

Commit 270a46c

Browse files
committed
Use syn in needless_doctest_main lint
1 parent 42f32a0 commit 270a46c

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed

clippy_lints/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ url = { version = "2.1.0", features = ["serde"] } # cargo requires serde feat i
3232
# see https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864
3333
if_chain = "1.0.0"
3434
smallvec = { version = "0.6.5", features = ["union"] }
35+
syn = { version = "1.0.5", features = ["full"] }
3536

3637
[features]
3738
debugging = []

clippy_lints/src/doc.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use syntax::ast::Attribute;
1010
use syntax::source_map::{BytePos, Span};
1111
use syntax_pos::Pos;
1212
use url::Url;
13+
use syn::*;
1314

1415
declare_clippy_lint! {
1516
/// **What it does:** Checks for the presence of `_`, `::` or camel-case words
@@ -73,7 +74,9 @@ declare_clippy_lint! {
7374
/// **What it does:** Checks for `fn main() { .. }` in doctests
7475
///
7576
/// **Why is this bad?** The test can be shorter (and likely more readable)
76-
/// if the `fn main()` is left implicit.
77+
/// if the `fn main()` is left implicit. In some cases, you'll need an
78+
/// empty `fn main() {}` in your doctest to avoid rustdoc introducing one,
79+
/// which is why the line will ignore empty `main` functions.
7780
///
7881
/// **Known problems:** None.
7982
///
@@ -86,6 +89,7 @@ declare_clippy_lint! {
8689
/// /// ```
8790
/// /// fn main() {
8891
/// /// // this needs not be in an `fn`
92+
/// /// unimplemented!();
8993
/// /// }
9094
/// /// ```
9195
/// fn needless_main() {
@@ -344,9 +348,34 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
344348
safety_header
345349
}
346350

347-
fn check_code(cx: &LateContext<'_, '_>, text: &str, span: Span) {
348-
if text.contains("fn main() {") {
349-
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
351+
// check a syn Item for non-empty `fn main() { .. }`
352+
fn is_default_main_fn(item: &syn::Item) -> bool {
353+
match item {
354+
Item::Fn(ItemFn { ref sig, ref block, .. }) => {
355+
!block.stmts.is_empty()
356+
&& sig.ident == "main"
357+
&& match sig.output {
358+
ReturnType::Default => true,
359+
ReturnType::Type(_, ref ty) => match **ty {
360+
Type::Tuple(TypeTuple { ref elems, .. }) => elems.is_empty(),
361+
_ => false,
362+
},
363+
}
364+
},
365+
_ => false,
366+
}
367+
}
368+
369+
fn check_code(cx: &LateContext<'_, '_>, code: &str, span: Span) {
370+
if let Ok(file) = syn::parse_file(code) {
371+
if file.items.iter().any(is_default_main_fn) {
372+
span_lint(
373+
cx,
374+
NEEDLESS_DOCTEST_MAIN,
375+
span,
376+
"needless `fn main() {} in doctest",
377+
);
378+
}
350379
}
351380
}
352381

tests/ui/needless_doc_main.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// This method does nothing. It's just there for testing.
2+
///
3+
/// # Examples:
4+
///
5+
/// ```text,ignore
6+
/// This is not Rust code
7+
/// Just check that parsing won't crash
8+
/// R€fridger@tør.
9+
/// ```
10+
///
11+
/// ```
12+
/// fn main() {
13+
/// let this_is_linted = 42u8;
14+
/// assert_eq!(this_is_linted / 2, 21);
15+
/// }
16+
/// ```
17+
///
18+
/// ```
19+
/// #[allow(unused)]
20+
/// const I = 1;
21+
/// fn main() {
22+
/// // this main method contains no code and is thus not linted.
23+
/// }
24+
/// ```
25+
fn main() {
26+
// nothing to see here, move along.
27+
}

0 commit comments

Comments
 (0)