]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/doc.rs
use a variable
[rust.git] / clippy_lints / src / doc.rs
1 use clippy_utils::attrs::is_doc_hidden;
2 use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note};
3 use clippy_utils::source::first_line_of_span;
4 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
5 use clippy_utils::{is_entrypoint_fn, is_expn_of, match_panic_def_id, method_chain_args, return_ty};
6 use if_chain::if_chain;
7 use itertools::Itertools;
8 use rustc_ast::ast::{Async, AttrKind, Attribute, FnKind, FnRetTy, ItemKind};
9 use rustc_ast::token::CommentKind;
10 use rustc_data_structures::fx::FxHashSet;
11 use rustc_data_structures::sync::Lrc;
12 use rustc_errors::emitter::EmitterWriter;
13 use rustc_errors::Handler;
14 use rustc_hir as hir;
15 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
16 use rustc_hir::{AnonConst, Expr, ExprKind, QPath};
17 use rustc_lint::{LateContext, LateLintPass};
18 use rustc_middle::hir::map::Map;
19 use rustc_middle::lint::in_external_macro;
20 use rustc_middle::ty;
21 use rustc_parse::maybe_new_parser_from_source_str;
22 use rustc_parse::parser::ForceCollect;
23 use rustc_session::parse::ParseSess;
24 use rustc_session::{declare_tool_lint, impl_lint_pass};
25 use rustc_span::def_id::LocalDefId;
26 use rustc_span::edition::Edition;
27 use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
28 use rustc_span::{sym, FileName, Pos};
29 use std::io;
30 use std::ops::Range;
31 use std::thread;
32 use url::Url;
33
34 declare_clippy_lint! {
35     /// ### What it does
36     /// Checks for the presence of `_`, `::` or camel-case words
37     /// outside ticks in documentation.
38     ///
39     /// ### Why is this bad?
40     /// *Rustdoc* supports markdown formatting, `_`, `::` and
41     /// camel-case probably indicates some code which should be included between
42     /// ticks. `_` can also be used for emphasis in markdown, this lint tries to
43     /// consider that.
44     ///
45     /// ### Known problems
46     /// Lots of bad docs won’t be fixed, what the lint checks
47     /// for is limited, and there are still false positives. HTML elements and their
48     /// content are not linted.
49     ///
50     /// In addition, when writing documentation comments, including `[]` brackets
51     /// inside a link text would trip the parser. Therfore, documenting link with
52     /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec
53     /// would fail.
54     ///
55     /// ### Examples
56     /// ```rust
57     /// /// Do something with the foo_bar parameter. See also
58     /// /// that::other::module::foo.
59     /// // ^ `foo_bar` and `that::other::module::foo` should be ticked.
60     /// fn doit(foo_bar: usize) {}
61     /// ```
62     ///
63     /// ```rust
64     /// // Link text with `[]` brackets should be written as following:
65     /// /// Consume the array and return the inner
66     /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec].
67     /// /// [SmallVec]: SmallVec
68     /// fn main() {}
69     /// ```
70     pub DOC_MARKDOWN,
71     pedantic,
72     "presence of `_`, `::` or camel-case outside backticks in documentation"
73 }
74
75 declare_clippy_lint! {
76     /// ### What it does
77     /// Checks for the doc comments of publicly visible
78     /// unsafe functions and warns if there is no `# Safety` section.
79     ///
80     /// ### Why is this bad?
81     /// Unsafe functions should document their safety
82     /// preconditions, so that users can be sure they are using them safely.
83     ///
84     /// ### Examples
85     /// ```rust
86     ///# type Universe = ();
87     /// /// This function should really be documented
88     /// pub unsafe fn start_apocalypse(u: &mut Universe) {
89     ///     unimplemented!();
90     /// }
91     /// ```
92     ///
93     /// At least write a line about safety:
94     ///
95     /// ```rust
96     ///# type Universe = ();
97     /// /// # Safety
98     /// ///
99     /// /// This function should not be called before the horsemen are ready.
100     /// pub unsafe fn start_apocalypse(u: &mut Universe) {
101     ///     unimplemented!();
102     /// }
103     /// ```
104     pub MISSING_SAFETY_DOC,
105     style,
106     "`pub unsafe fn` without `# Safety` docs"
107 }
108
109 declare_clippy_lint! {
110     /// ### What it does
111     /// Checks the doc comments of publicly visible functions that
112     /// return a `Result` type and warns if there is no `# Errors` section.
113     ///
114     /// ### Why is this bad?
115     /// Documenting the type of errors that can be returned from a
116     /// function can help callers write code to handle the errors appropriately.
117     ///
118     /// ### Examples
119     /// Since the following function returns a `Result` it has an `# Errors` section in
120     /// its doc comment:
121     ///
122     /// ```rust
123     ///# use std::io;
124     /// /// # Errors
125     /// ///
126     /// /// Will return `Err` if `filename` does not exist or the user does not have
127     /// /// permission to read it.
128     /// pub fn read(filename: String) -> io::Result<String> {
129     ///     unimplemented!();
130     /// }
131     /// ```
132     pub MISSING_ERRORS_DOC,
133     pedantic,
134     "`pub fn` returns `Result` without `# Errors` in doc comment"
135 }
136
137 declare_clippy_lint! {
138     /// ### What it does
139     /// Checks the doc comments of publicly visible functions that
140     /// may panic and warns if there is no `# Panics` section.
141     ///
142     /// ### Why is this bad?
143     /// Documenting the scenarios in which panicking occurs
144     /// can help callers who do not want to panic to avoid those situations.
145     ///
146     /// ### Examples
147     /// Since the following function may panic it has a `# Panics` section in
148     /// its doc comment:
149     ///
150     /// ```rust
151     /// /// # Panics
152     /// ///
153     /// /// Will panic if y is 0
154     /// pub fn divide_by(x: i32, y: i32) -> i32 {
155     ///     if y == 0 {
156     ///         panic!("Cannot divide by 0")
157     ///     } else {
158     ///         x / y
159     ///     }
160     /// }
161     /// ```
162     pub MISSING_PANICS_DOC,
163     pedantic,
164     "`pub fn` may panic without `# Panics` in doc comment"
165 }
166
167 declare_clippy_lint! {
168     /// ### What it does
169     /// Checks for `fn main() { .. }` in doctests
170     ///
171     /// ### Why is this bad?
172     /// The test can be shorter (and likely more readable)
173     /// if the `fn main()` is left implicit.
174     ///
175     /// ### Examples
176     /// ``````rust
177     /// /// An example of a doctest with a `main()` function
178     /// ///
179     /// /// # Examples
180     /// ///
181     /// /// ```
182     /// /// fn main() {
183     /// ///     // this needs not be in an `fn`
184     /// /// }
185     /// /// ```
186     /// fn needless_main() {
187     ///     unimplemented!();
188     /// }
189     /// ``````
190     pub NEEDLESS_DOCTEST_MAIN,
191     style,
192     "presence of `fn main() {` in code examples"
193 }
194
195 #[allow(clippy::module_name_repetitions)]
196 #[derive(Clone)]
197 pub struct DocMarkdown {
198     valid_idents: FxHashSet<String>,
199     in_trait_impl: bool,
200 }
201
202 impl DocMarkdown {
203     pub fn new(valid_idents: FxHashSet<String>) -> Self {
204         Self {
205             valid_idents,
206             in_trait_impl: false,
207         }
208     }
209 }
210
211 impl_lint_pass!(DocMarkdown =>
212     [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN]
213 );
214
215 impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
216     fn check_crate(&mut self, cx: &LateContext<'tcx>) {
217         let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
218         check_attrs(cx, &self.valid_idents, attrs);
219     }
220
221     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
222         let attrs = cx.tcx.hir().attrs(item.hir_id());
223         let headers = check_attrs(cx, &self.valid_idents, attrs);
224         match item.kind {
225             hir::ItemKind::Fn(ref sig, _, body_id) => {
226                 if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
227                     let body = cx.tcx.hir().body(body_id);
228                     let mut fpu = FindPanicUnwrap {
229                         cx,
230                         typeck_results: cx.tcx.typeck(item.def_id),
231                         panic_span: None,
232                     };
233                     fpu.visit_expr(&body.value);
234                     lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
235                 }
236             },
237             hir::ItemKind::Impl(ref impl_) => {
238                 self.in_trait_impl = impl_.of_trait.is_some();
239             },
240             hir::ItemKind::Trait(_, unsafety, ..) => {
241                 if !headers.safety && unsafety == hir::Unsafety::Unsafe {
242                     span_lint(
243                         cx,
244                         MISSING_SAFETY_DOC,
245                         item.span,
246                         "docs for unsafe trait missing `# Safety` section",
247                     );
248                 }
249             },
250             _ => (),
251         }
252     }
253
254     fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
255         if let hir::ItemKind::Impl { .. } = item.kind {
256             self.in_trait_impl = false;
257         }
258     }
259
260     fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
261         let attrs = cx.tcx.hir().attrs(item.hir_id());
262         let headers = check_attrs(cx, &self.valid_idents, attrs);
263         if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
264             if !in_external_macro(cx.tcx.sess, item.span) {
265                 lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, None, None);
266             }
267         }
268     }
269
270     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
271         let attrs = cx.tcx.hir().attrs(item.hir_id());
272         let headers = check_attrs(cx, &self.valid_idents, attrs);
273         if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
274             return;
275         }
276         if let hir::ImplItemKind::Fn(ref sig, body_id) = item.kind {
277             let body = cx.tcx.hir().body(body_id);
278             let mut fpu = FindPanicUnwrap {
279                 cx,
280                 typeck_results: cx.tcx.typeck(item.def_id),
281                 panic_span: None,
282             };
283             fpu.visit_expr(&body.value);
284             lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
285         }
286     }
287 }
288
289 fn lint_for_missing_headers<'tcx>(
290     cx: &LateContext<'tcx>,
291     def_id: LocalDefId,
292     span: impl Into<MultiSpan> + Copy,
293     sig: &hir::FnSig<'_>,
294     headers: DocHeaders,
295     body_id: Option<hir::BodyId>,
296     panic_span: Option<Span>,
297 ) {
298     if !cx.access_levels.is_exported(def_id) {
299         return; // Private functions do not require doc comments
300     }
301
302     // do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
303     if cx
304         .tcx
305         .hir()
306         .parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id))
307         .any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
308     {
309         return;
310     }
311
312     if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
313         span_lint(
314             cx,
315             MISSING_SAFETY_DOC,
316             span,
317             "unsafe function's docs miss `# Safety` section",
318         );
319     }
320     if !headers.panics && panic_span.is_some() {
321         span_lint_and_note(
322             cx,
323             MISSING_PANICS_DOC,
324             span,
325             "docs for function which may panic missing `# Panics` section",
326             panic_span,
327             "first possible panic found here",
328         );
329     }
330     if !headers.errors {
331         let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
332         if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) {
333             span_lint(
334                 cx,
335                 MISSING_ERRORS_DOC,
336                 span,
337                 "docs for function returning `Result` missing `# Errors` section",
338             );
339         } else {
340             if_chain! {
341                 if let Some(body_id) = body_id;
342                 if let Some(future) = cx.tcx.lang_items().future_trait();
343                 let typeck = cx.tcx.typeck_body(body_id);
344                 let body = cx.tcx.hir().body(body_id);
345                 let ret_ty = typeck.expr_ty(&body.value);
346                 if implements_trait(cx, ret_ty, future, &[]);
347                 if let ty::Opaque(_, subs) = ret_ty.kind();
348                 if let Some(gen) = subs.types().next();
349                 if let ty::Generator(_, subs, _) = gen.kind();
350                 if is_type_diagnostic_item(cx, subs.as_generator().return_ty(), sym::Result);
351                 then {
352                     span_lint(
353                         cx,
354                         MISSING_ERRORS_DOC,
355                         span,
356                         "docs for function returning `Result` missing `# Errors` section",
357                     );
358                 }
359             }
360         }
361     }
362 }
363
364 /// Cleanup documentation decoration.
365 ///
366 /// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
367 /// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
368 /// need to keep track of
369 /// the spans but this function is inspired from the later.
370 #[allow(clippy::cast_possible_truncation)]
371 #[must_use]
372 pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) {
373     // one-line comments lose their prefix
374     if comment_kind == CommentKind::Line {
375         let mut doc = doc.to_owned();
376         doc.push('\n');
377         let len = doc.len();
378         // +3 skips the opening delimiter
379         return (doc, vec![(len, span.with_lo(span.lo() + BytePos(3)))]);
380     }
381
382     let mut sizes = vec![];
383     let mut contains_initial_stars = false;
384     for line in doc.lines() {
385         let offset = line.as_ptr() as usize - doc.as_ptr() as usize;
386         debug_assert_eq!(offset as u32 as usize, offset);
387         contains_initial_stars |= line.trim_start().starts_with('*');
388         // +1 adds the newline, +3 skips the opening delimiter
389         sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + offset as u32))));
390     }
391     if !contains_initial_stars {
392         return (doc.to_string(), sizes);
393     }
394     // remove the initial '*'s if any
395     let mut no_stars = String::with_capacity(doc.len());
396     for line in doc.lines() {
397         let mut chars = line.chars();
398         for c in &mut chars {
399             if c.is_whitespace() {
400                 no_stars.push(c);
401             } else {
402                 no_stars.push(if c == '*' { ' ' } else { c });
403                 break;
404             }
405         }
406         no_stars.push_str(chars.as_str());
407         no_stars.push('\n');
408     }
409
410     (no_stars, sizes)
411 }
412
413 #[derive(Copy, Clone)]
414 struct DocHeaders {
415     safety: bool,
416     errors: bool,
417     panics: bool,
418 }
419
420 fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
421     use pulldown_cmark::{BrokenLink, CowStr, Options};
422     /// We don't want the parser to choke on intra doc links. Since we don't
423     /// actually care about rendering them, just pretend that all broken links are
424     /// point to a fake address.
425     #[allow(clippy::unnecessary_wraps)] // we're following a type signature
426     fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
427         Some(("fake".into(), "fake".into()))
428     }
429
430     let mut doc = String::new();
431     let mut spans = vec![];
432
433     for attr in attrs {
434         if let AttrKind::DocComment(comment_kind, comment) = attr.kind {
435             let (comment, current_spans) = strip_doc_comment_decoration(&comment.as_str(), comment_kind, attr.span);
436             spans.extend_from_slice(&current_spans);
437             doc.push_str(&comment);
438         } else if attr.has_name(sym::doc) {
439             // ignore mix of sugared and non-sugared doc
440             // don't trigger the safety or errors check
441             return DocHeaders {
442                 safety: true,
443                 errors: true,
444                 panics: true,
445             };
446         }
447     }
448
449     let mut current = 0;
450     for &mut (ref mut offset, _) in &mut spans {
451         let offset_copy = *offset;
452         *offset = current;
453         current += offset_copy;
454     }
455
456     if doc.is_empty() {
457         return DocHeaders {
458             safety: false,
459             errors: false,
460             panics: false,
461         };
462     }
463
464     let mut cb = fake_broken_link_callback;
465
466     let parser =
467         pulldown_cmark::Parser::new_with_broken_link_callback(&doc, Options::empty(), Some(&mut cb)).into_offset_iter();
468     // Iterate over all `Events` and combine consecutive events into one
469     let events = parser.coalesce(|previous, current| {
470         use pulldown_cmark::Event::Text;
471
472         let previous_range = previous.1;
473         let current_range = current.1;
474
475         match (previous.0, current.0) {
476             (Text(previous), Text(current)) => {
477                 let mut previous = previous.to_string();
478                 previous.push_str(&current);
479                 Ok((Text(previous.into()), previous_range))
480             },
481             (previous, current) => Err(((previous, previous_range), (current, current_range))),
482         }
483     });
484     check_doc(cx, valid_idents, events, &spans)
485 }
486
487 const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
488
489 fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
490     cx: &LateContext<'_>,
491     valid_idents: &FxHashSet<String>,
492     events: Events,
493     spans: &[(usize, Span)],
494 ) -> DocHeaders {
495     // true if a safety header was found
496     use pulldown_cmark::Event::{
497         Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
498     };
499     use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
500     use pulldown_cmark::{CodeBlockKind, CowStr};
501
502     let mut headers = DocHeaders {
503         safety: false,
504         errors: false,
505         panics: false,
506     };
507     let mut in_code = false;
508     let mut in_link = None;
509     let mut in_heading = false;
510     let mut is_rust = false;
511     let mut edition = None;
512     let mut ticks_unbalanced = false;
513     let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
514     let mut paragraph_span = spans.get(0).expect("function isn't called if doc comment is empty").1;
515     for (event, range) in events {
516         match event {
517             Start(CodeBlock(ref kind)) => {
518                 in_code = true;
519                 if let CodeBlockKind::Fenced(lang) = kind {
520                     for item in lang.split(',') {
521                         if item == "ignore" {
522                             is_rust = false;
523                             break;
524                         }
525                         if let Some(stripped) = item.strip_prefix("edition") {
526                             is_rust = true;
527                             edition = stripped.parse::<Edition>().ok();
528                         } else if item.is_empty() || RUST_CODE.contains(&item) {
529                             is_rust = true;
530                         }
531                     }
532                 }
533             },
534             End(CodeBlock(_)) => {
535                 in_code = false;
536                 is_rust = false;
537             },
538             Start(Link(_, url, _)) => in_link = Some(url),
539             End(Link(..)) => in_link = None,
540             Start(Heading(_) | Paragraph | Item) => {
541                 if let Start(Heading(_)) = event {
542                     in_heading = true;
543                 }
544                 ticks_unbalanced = false;
545                 let (_, span) = get_current_span(spans, range.start);
546                 paragraph_span = first_line_of_span(cx, span);
547             },
548             End(Heading(_) | Paragraph | Item) => {
549                 if let End(Heading(_)) = event {
550                     in_heading = false;
551                 }
552                 if ticks_unbalanced {
553                     span_lint_and_help(
554                         cx,
555                         DOC_MARKDOWN,
556                         paragraph_span,
557                         "backticks are unbalanced",
558                         None,
559                         "a backtick may be missing a pair",
560                     );
561                 } else {
562                     for (text, span) in text_to_check {
563                         check_text(cx, valid_idents, &text, span);
564                     }
565                 }
566                 text_to_check = Vec::new();
567             },
568             Start(_tag) | End(_tag) => (), // We don't care about other tags
569             Html(_html) => (),             // HTML is weird, just ignore it
570             SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
571             FootnoteReference(text) | Text(text) => {
572                 let (begin, span) = get_current_span(spans, range.start);
573                 paragraph_span = paragraph_span.with_hi(span.hi());
574                 ticks_unbalanced |= text.contains('`') && !in_code;
575                 if Some(&text) == in_link.as_ref() || ticks_unbalanced {
576                     // Probably a link of the form `<http://example.com>`
577                     // Which are represented as a link to "http://example.com" with
578                     // text "http://example.com" by pulldown-cmark
579                     continue;
580                 }
581                 let trimmed_text = text.trim();
582                 headers.safety |= in_heading && trimmed_text == "Safety";
583                 headers.safety |= in_heading && trimmed_text == "Implementation safety";
584                 headers.safety |= in_heading && trimmed_text == "Implementation Safety";
585                 headers.errors |= in_heading && trimmed_text == "Errors";
586                 headers.panics |= in_heading && trimmed_text == "Panics";
587                 if in_code {
588                     if is_rust {
589                         let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
590                         check_code(cx, &text, edition, span);
591                     }
592                 } else {
593                     // Adjust for the beginning of the current `Event`
594                     let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
595                     text_to_check.push((text, span));
596                 }
597             },
598         }
599     }
600     headers
601 }
602
603 fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
604     let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
605         Ok(o) => o,
606         Err(e) => e - 1,
607     };
608     spans[index]
609 }
610
611 fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
612     fn has_needless_main(code: String, edition: Edition) -> bool {
613         rustc_driver::catch_fatal_errors(|| {
614             rustc_span::create_session_globals_then(edition, || {
615                 let filename = FileName::anon_source_code(&code);
616
617                 let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
618                 let emitter = EmitterWriter::new(Box::new(io::sink()), None, false, false, false, None, false);
619                 let handler = Handler::with_emitter(false, None, Box::new(emitter));
620                 let sess = ParseSess::with_span_handler(handler, sm);
621
622                 let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
623                     Ok(p) => p,
624                     Err(errs) => {
625                         for mut err in errs {
626                             err.cancel();
627                         }
628                         return false;
629                     },
630                 };
631
632                 let mut relevant_main_found = false;
633                 loop {
634                     match parser.parse_item(ForceCollect::No) {
635                         Ok(Some(item)) => match &item.kind {
636                             // Tests with one of these items are ignored
637                             ItemKind::Static(..)
638                             | ItemKind::Const(..)
639                             | ItemKind::ExternCrate(..)
640                             | ItemKind::ForeignMod(..) => return false,
641                             // We found a main function ...
642                             ItemKind::Fn(box FnKind(_, sig, _, Some(block))) if item.ident.name == sym::main => {
643                                 let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
644                                 let returns_nothing = match &sig.decl.output {
645                                     FnRetTy::Default(..) => true,
646                                     FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
647                                     FnRetTy::Ty(_) => false,
648                                 };
649
650                                 if returns_nothing && !is_async && !block.stmts.is_empty() {
651                                     // This main function should be linted, but only if there are no other functions
652                                     relevant_main_found = true;
653                                 } else {
654                                     // This main function should not be linted, we're done
655                                     return false;
656                                 }
657                             },
658                             // Another function was found; this case is ignored too
659                             ItemKind::Fn(..) => return false,
660                             _ => {},
661                         },
662                         Ok(None) => break,
663                         Err(mut e) => {
664                             e.cancel();
665                             return false;
666                         },
667                     }
668                 }
669
670                 relevant_main_found
671             })
672         })
673         .ok()
674         .unwrap_or_default()
675     }
676
677     // Because of the global session, we need to create a new session in a different thread with
678     // the edition we need.
679     let text = text.to_owned();
680     if thread::spawn(move || has_needless_main(text, edition))
681         .join()
682         .expect("thread::spawn failed")
683     {
684         span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
685     }
686 }
687
688 fn check_text(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span) {
689     for word in text.split(|c: char| c.is_whitespace() || c == '\'') {
690         // Trim punctuation as in `some comment (see foo::bar).`
691         //                                                   ^^
692         // Or even as in `_foo bar_` which is emphasized.
693         let word = word.trim_matches(|c: char| !c.is_alphanumeric());
694
695         if valid_idents.contains(word) {
696             continue;
697         }
698
699         // Adjust for the current word
700         let offset = word.as_ptr() as usize - text.as_ptr() as usize;
701         let span = Span::new(
702             span.lo() + BytePos::from_usize(offset),
703             span.lo() + BytePos::from_usize(offset + word.len()),
704             span.ctxt(),
705             span.parent(),
706         );
707
708         check_word(cx, word, span);
709     }
710 }
711
712 fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
713     /// Checks if a string is camel-case, i.e., contains at least two uppercase
714     /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
715     /// Plurals are also excluded (`IDs` is ok).
716     fn is_camel_case(s: &str) -> bool {
717         if s.starts_with(|c: char| c.is_digit(10)) {
718             return false;
719         }
720
721         let s = s.strip_suffix('s').unwrap_or(s);
722
723         s.chars().all(char::is_alphanumeric)
724             && s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1
725             && s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0
726     }
727
728     fn has_underscore(s: &str) -> bool {
729         s != "_" && !s.contains("\\_") && s.contains('_')
730     }
731
732     fn has_hyphen(s: &str) -> bool {
733         s != "-" && s.contains('-')
734     }
735
736     if let Ok(url) = Url::parse(word) {
737         // try to get around the fact that `foo::bar` parses as a valid URL
738         if !url.cannot_be_a_base() {
739             span_lint(
740                 cx,
741                 DOC_MARKDOWN,
742                 span,
743                 "you should put bare URLs between `<`/`>` or make a proper Markdown link",
744             );
745
746             return;
747         }
748     }
749
750     // We assume that mixed-case words are not meant to be put inside bacticks. (Issue #2343)
751     if has_underscore(word) && has_hyphen(word) {
752         return;
753     }
754
755     if has_underscore(word) || word.contains("::") || is_camel_case(word) {
756         span_lint(
757             cx,
758             DOC_MARKDOWN,
759             span,
760             &format!("you should put `{}` between ticks in the documentation", word),
761         );
762     }
763 }
764
765 struct FindPanicUnwrap<'a, 'tcx> {
766     cx: &'a LateContext<'tcx>,
767     panic_span: Option<Span>,
768     typeck_results: &'tcx ty::TypeckResults<'tcx>,
769 }
770
771 impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
772     type Map = Map<'tcx>;
773
774     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
775         if self.panic_span.is_some() {
776             return;
777         }
778
779         // check for `begin_panic`
780         if_chain! {
781             if let ExprKind::Call(func_expr, _) = expr.kind;
782             if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
783             if let Some(path_def_id) = path.res.opt_def_id();
784             if match_panic_def_id(self.cx, path_def_id);
785             if is_expn_of(expr.span, "unreachable").is_none();
786             if !is_expn_of_debug_assertions(expr.span);
787             then {
788                 self.panic_span = Some(expr.span);
789             }
790         }
791
792         // check for `assert_eq` or `assert_ne`
793         if is_expn_of(expr.span, "assert_eq").is_some() || is_expn_of(expr.span, "assert_ne").is_some() {
794             self.panic_span = Some(expr.span);
795         }
796
797         // check for `unwrap`
798         if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
799             let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
800             if is_type_diagnostic_item(self.cx, reciever_ty, sym::Option)
801                 || is_type_diagnostic_item(self.cx, reciever_ty, sym::Result)
802             {
803                 self.panic_span = Some(expr.span);
804             }
805         }
806
807         // and check sub-expressions
808         intravisit::walk_expr(self, expr);
809     }
810
811     // Panics in const blocks will cause compilation to fail.
812     fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
813
814     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
815         NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
816     }
817 }
818
819 fn is_expn_of_debug_assertions(span: Span) -> bool {
820     const MACRO_NAMES: &[&str] = &["debug_assert", "debug_assert_eq", "debug_assert_ne"];
821     MACRO_NAMES.iter().any(|name| is_expn_of(span, name).is_some())
822 }