1 use clippy_utils
::attrs
::is_doc_hidden
;
2 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_then}
;
3 use clippy_utils
::macros
::{is_panic, root_macro_call_first_node}
;
4 use clippy_utils
::source
::{first_line_of_span, snippet_with_applicability}
;
5 use clippy_utils
::ty
::{implements_trait, is_type_diagnostic_item}
;
6 use clippy_utils
::{is_entrypoint_fn, method_chain_args, return_ty}
;
7 use if_chain
::if_chain
;
8 use itertools
::Itertools
;
9 use rustc_ast
::ast
::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind}
;
10 use rustc_ast
::token
::CommentKind
;
11 use rustc_data_structures
::fx
::FxHashSet
;
12 use rustc_data_structures
::sync
::Lrc
;
13 use rustc_errors
::emitter
::EmitterWriter
;
14 use rustc_errors
::{Applicability, Handler, SuggestionStyle}
;
16 use rustc_hir
::intravisit
::{self, Visitor}
;
17 use rustc_hir
::{AnonConst, Expr}
;
18 use rustc_lint
::{LateContext, LateLintPass}
;
19 use rustc_middle
::hir
::nested_filter
;
20 use rustc_middle
::lint
::in_external_macro
;
22 use rustc_parse
::maybe_new_parser_from_source_str
;
23 use rustc_parse
::parser
::ForceCollect
;
24 use rustc_session
::parse
::ParseSess
;
25 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
26 use rustc_span
::def_id
::LocalDefId
;
27 use rustc_span
::edition
::Edition
;
28 use rustc_span
::source_map
::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span}
;
29 use rustc_span
::{sym, FileName, Pos}
;
35 declare_clippy_lint
! {
37 /// Checks for the presence of `_`, `::` or camel-case words
38 /// outside ticks in documentation.
40 /// ### Why is this bad?
41 /// *Rustdoc* supports markdown formatting, `_`, `::` and
42 /// camel-case probably indicates some code which should be included between
43 /// ticks. `_` can also be used for emphasis in markdown, this lint tries to
46 /// ### Known problems
47 /// Lots of bad docs won’t be fixed, what the lint checks
48 /// for is limited, and there are still false positives. HTML elements and their
49 /// content are not linted.
51 /// In addition, when writing documentation comments, including `[]` brackets
52 /// inside a link text would trip the parser. Therefore, documenting link with
53 /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec
58 /// /// Do something with the foo_bar parameter. See also
59 /// /// that::other::module::foo.
60 /// // ^ `foo_bar` and `that::other::module::foo` should be ticked.
61 /// fn doit(foo_bar: usize) {}
65 /// // Link text with `[]` brackets should be written as following:
66 /// /// Consume the array and return the inner
67 /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec].
68 /// /// [SmallVec]: SmallVec
71 #[clippy::version = "pre 1.29.0"]
74 "presence of `_`, `::` or camel-case outside backticks in documentation"
77 declare_clippy_lint
! {
79 /// Checks for the doc comments of publicly visible
80 /// unsafe functions and warns if there is no `# Safety` section.
82 /// ### Why is this bad?
83 /// Unsafe functions should document their safety
84 /// preconditions, so that users can be sure they are using them safely.
88 ///# type Universe = ();
89 /// /// This function should really be documented
90 /// pub unsafe fn start_apocalypse(u: &mut Universe) {
95 /// At least write a line about safety:
98 ///# type Universe = ();
101 /// /// This function should not be called before the horsemen are ready.
102 /// pub unsafe fn start_apocalypse(u: &mut Universe) {
103 /// unimplemented!();
106 #[clippy::version = "1.39.0"]
107 pub MISSING_SAFETY_DOC
,
109 "`pub unsafe fn` without `# Safety` docs"
112 declare_clippy_lint
! {
114 /// Checks the doc comments of publicly visible functions that
115 /// return a `Result` type and warns if there is no `# Errors` section.
117 /// ### Why is this bad?
118 /// Documenting the type of errors that can be returned from a
119 /// function can help callers write code to handle the errors appropriately.
122 /// Since the following function returns a `Result` it has an `# Errors` section in
129 /// /// Will return `Err` if `filename` does not exist or the user does not have
130 /// /// permission to read it.
131 /// pub fn read(filename: String) -> io::Result<String> {
132 /// unimplemented!();
135 #[clippy::version = "1.41.0"]
136 pub MISSING_ERRORS_DOC
,
138 "`pub fn` returns `Result` without `# Errors` in doc comment"
141 declare_clippy_lint
! {
143 /// Checks the doc comments of publicly visible functions that
144 /// may panic and warns if there is no `# Panics` section.
146 /// ### Why is this bad?
147 /// Documenting the scenarios in which panicking occurs
148 /// can help callers who do not want to panic to avoid those situations.
151 /// Since the following function may panic it has a `# Panics` section in
157 /// /// Will panic if y is 0
158 /// pub fn divide_by(x: i32, y: i32) -> i32 {
160 /// panic!("Cannot divide by 0")
166 #[clippy::version = "1.52.0"]
167 pub MISSING_PANICS_DOC
,
169 "`pub fn` may panic without `# Panics` in doc comment"
172 declare_clippy_lint
! {
174 /// Checks for `fn main() { .. }` in doctests
176 /// ### Why is this bad?
177 /// The test can be shorter (and likely more readable)
178 /// if the `fn main()` is left implicit.
182 /// /// An example of a doctest with a `main()` function
188 /// /// // this needs not be in an `fn`
191 /// fn needless_main() {
192 /// unimplemented!();
195 #[clippy::version = "1.40.0"]
196 pub NEEDLESS_DOCTEST_MAIN
,
198 "presence of `fn main() {` in code examples"
201 #[allow(clippy::module_name_repetitions)]
203 pub struct DocMarkdown
{
204 valid_idents
: FxHashSet
<String
>,
209 pub fn new(valid_idents
: FxHashSet
<String
>) -> Self {
212 in_trait_impl
: false,
217 impl_lint_pass
!(DocMarkdown
=>
218 [DOC_MARKDOWN
, MISSING_SAFETY_DOC
, MISSING_ERRORS_DOC
, MISSING_PANICS_DOC
, NEEDLESS_DOCTEST_MAIN
]
221 impl<'tcx
> LateLintPass
<'tcx
> for DocMarkdown
{
222 fn check_crate(&mut self, cx
: &LateContext
<'tcx
>) {
223 let attrs
= cx
.tcx
.hir().attrs(hir
::CRATE_HIR_ID
);
224 check_attrs(cx
, &self.valid_idents
, attrs
);
227 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
228 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
229 let headers
= check_attrs(cx
, &self.valid_idents
, attrs
);
231 hir
::ItemKind
::Fn(ref sig
, _
, body_id
) => {
232 if !(is_entrypoint_fn(cx
, item
.def_id
.to_def_id()) || in_external_macro(cx
.tcx
.sess
, item
.span
)) {
233 let body
= cx
.tcx
.hir().body(body_id
);
234 let mut fpu
= FindPanicUnwrap
{
236 typeck_results
: cx
.tcx
.typeck(item
.def_id
),
239 fpu
.visit_expr(&body
.value
);
240 lint_for_missing_headers(cx
, item
.def_id
, item
.span
, sig
, headers
, Some(body_id
), fpu
.panic_span
);
243 hir
::ItemKind
::Impl(ref impl_
) => {
244 self.in_trait_impl
= impl_
.of_trait
.is_some();
246 hir
::ItemKind
::Trait(_
, unsafety
, ..) => {
247 if !headers
.safety
&& unsafety
== hir
::Unsafety
::Unsafe
{
252 "docs for unsafe trait missing `# Safety` section",
260 fn check_item_post(&mut self, _cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
261 if let hir
::ItemKind
::Impl { .. }
= item
.kind
{
262 self.in_trait_impl
= false;
266 fn check_trait_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::TraitItem
<'_
>) {
267 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
268 let headers
= check_attrs(cx
, &self.valid_idents
, attrs
);
269 if let hir
::TraitItemKind
::Fn(ref sig
, ..) = item
.kind
{
270 if !in_external_macro(cx
.tcx
.sess
, item
.span
) {
271 lint_for_missing_headers(cx
, item
.def_id
, item
.span
, sig
, headers
, None
, None
);
276 fn check_impl_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::ImplItem
<'_
>) {
277 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
278 let headers
= check_attrs(cx
, &self.valid_idents
, attrs
);
279 if self.in_trait_impl
|| in_external_macro(cx
.tcx
.sess
, item
.span
) {
282 if let hir
::ImplItemKind
::Fn(ref sig
, body_id
) = item
.kind
{
283 let body
= cx
.tcx
.hir().body(body_id
);
284 let mut fpu
= FindPanicUnwrap
{
286 typeck_results
: cx
.tcx
.typeck(item
.def_id
),
289 fpu
.visit_expr(&body
.value
);
290 lint_for_missing_headers(cx
, item
.def_id
, item
.span
, sig
, headers
, Some(body_id
), fpu
.panic_span
);
295 fn lint_for_missing_headers
<'tcx
>(
296 cx
: &LateContext
<'tcx
>,
298 span
: impl Into
<MultiSpan
> + Copy
,
299 sig
: &hir
::FnSig
<'_
>,
301 body_id
: Option
<hir
::BodyId
>,
302 panic_span
: Option
<Span
>,
304 if !cx
.access_levels
.is_exported(def_id
) {
305 return; // Private functions do not require doc comments
308 // do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
312 .parent_iter(cx
.tcx
.hir().local_def_id_to_hir_id(def_id
))
313 .any(|(id
, _node
)| is_doc_hidden(cx
.tcx
.hir().attrs(id
)))
318 if !headers
.safety
&& sig
.header
.unsafety
== hir
::Unsafety
::Unsafe
{
323 "unsafe function's docs miss `# Safety` section",
326 if !headers
.panics
&& panic_span
.is_some() {
331 "docs for function which may panic missing `# Panics` section",
333 "first possible panic found here",
337 let hir_id
= cx
.tcx
.hir().local_def_id_to_hir_id(def_id
);
338 if is_type_diagnostic_item(cx
, return_ty(cx
, hir_id
), sym
::Result
) {
343 "docs for function returning `Result` missing `# Errors` section",
347 if let Some(body_id
) = body_id
;
348 if let Some(future
) = cx
.tcx
.lang_items().future_trait();
349 let typeck
= cx
.tcx
.typeck_body(body_id
);
350 let body
= cx
.tcx
.hir().body(body_id
);
351 let ret_ty
= typeck
.expr_ty(&body
.value
);
352 if implements_trait(cx
, ret_ty
, future
, &[]);
353 if let ty
::Opaque(_
, subs
) = ret_ty
.kind();
354 if let Some(gen
) = subs
.types().next();
355 if let ty
::Generator(_
, subs
, _
) = gen
.kind();
356 if is_type_diagnostic_item(cx
, subs
.as_generator().return_ty(), sym
::Result
);
362 "docs for function returning `Result` missing `# Errors` section",
370 /// Cleanup documentation decoration.
372 /// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
373 /// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
374 /// need to keep track of
375 /// the spans but this function is inspired from the later.
376 #[allow(clippy::cast_possible_truncation)]
378 pub fn strip_doc_comment_decoration(doc
: &str, comment_kind
: CommentKind
, span
: Span
) -> (String
, Vec
<(usize, Span
)>) {
379 // one-line comments lose their prefix
380 if comment_kind
== CommentKind
::Line
{
381 let mut doc
= doc
.to_owned();
384 // +3 skips the opening delimiter
385 return (doc
, vec
![(len
, span
.with_lo(span
.lo() + BytePos(3)))]);
388 let mut sizes
= vec
![];
389 let mut contains_initial_stars
= false;
390 for line
in doc
.lines() {
391 let offset
= line
.as_ptr() as usize - doc
.as_ptr() as usize;
392 debug_assert_eq
!(offset
as u32 as usize, offset
);
393 contains_initial_stars
|= line
.trim_start().starts_with('
*'
);
394 // +1 adds the newline, +3 skips the opening delimiter
395 sizes
.push((line
.len() + 1, span
.with_lo(span
.lo() + BytePos(3 + offset
as u32))));
397 if !contains_initial_stars
{
398 return (doc
.to_string(), sizes
);
400 // remove the initial '*'s if any
401 let mut no_stars
= String
::with_capacity(doc
.len());
402 for line
in doc
.lines() {
403 let mut chars
= line
.chars();
404 for c
in &mut chars
{
405 if c
.is_whitespace() {
408 no_stars
.push(if c
== '
*' { ' ' }
else { c }
);
412 no_stars
.push_str(chars
.as_str());
419 #[derive(Copy, Clone)]
426 fn check_attrs
<'a
>(cx
: &LateContext
<'_
>, valid_idents
: &FxHashSet
<String
>, attrs
: &'a
[Attribute
]) -> DocHeaders
{
427 use pulldown_cmark
::{BrokenLink, CowStr, Options}
;
428 /// We don't want the parser to choke on intra doc links. Since we don't
429 /// actually care about rendering them, just pretend that all broken links are
430 /// point to a fake address.
431 #[allow(clippy::unnecessary_wraps)] // we're following a type signature
432 fn fake_broken_link_callback
<'a
>(_
: BrokenLink
<'_
>) -> Option
<(CowStr
<'a
>, CowStr
<'a
>)> {
433 Some(("fake".into(), "fake".into()))
436 let mut doc
= String
::new();
437 let mut spans
= vec
![];
440 if let AttrKind
::DocComment(comment_kind
, comment
) = attr
.kind
{
441 let (comment
, current_spans
) = strip_doc_comment_decoration(comment
.as_str(), comment_kind
, attr
.span
);
442 spans
.extend_from_slice(¤t_spans
);
443 doc
.push_str(&comment
);
444 } else if attr
.has_name(sym
::doc
) {
445 // ignore mix of sugared and non-sugared doc
446 // don't trigger the safety or errors check
456 for &mut (ref mut offset
, _
) in &mut spans
{
457 let offset_copy
= *offset
;
459 current
+= offset_copy
;
470 let mut cb
= fake_broken_link_callback
;
473 pulldown_cmark
::Parser
::new_with_broken_link_callback(&doc
, Options
::empty(), Some(&mut cb
)).into_offset_iter();
474 // Iterate over all `Events` and combine consecutive events into one
475 let events
= parser
.coalesce(|previous
, current
| {
476 use pulldown_cmark
::Event
::Text
;
478 let previous_range
= previous
.1;
479 let current_range
= current
.1;
481 match (previous
.0, current
.0) {
482 (Text(previous
), Text(current
)) => {
483 let mut previous
= previous
.to_string();
484 previous
.push_str(¤t
);
485 Ok((Text(previous
.into()), previous_range
))
487 (previous
, current
) => Err(((previous
, previous_range
), (current
, current_range
))),
490 check_doc(cx
, valid_idents
, events
, &spans
)
493 const RUST_CODE
: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
495 fn check_doc
<'a
, Events
: Iterator
<Item
= (pulldown_cmark
::Event
<'a
>, Range
<usize>)>>(
496 cx
: &LateContext
<'_
>,
497 valid_idents
: &FxHashSet
<String
>,
499 spans
: &[(usize, Span
)],
501 // true if a safety header was found
502 use pulldown_cmark
::Event
::{
503 Code
, End
, FootnoteReference
, HardBreak
, Html
, Rule
, SoftBreak
, Start
, TaskListMarker
, Text
,
505 use pulldown_cmark
::Tag
::{CodeBlock, Heading, Item, Link, Paragraph}
;
506 use pulldown_cmark
::{CodeBlockKind, CowStr}
;
508 let mut headers
= DocHeaders
{
513 let mut in_code
= false;
514 let mut in_link
= None
;
515 let mut in_heading
= false;
516 let mut is_rust
= false;
517 let mut edition
= None
;
518 let mut ticks_unbalanced
= false;
519 let mut text_to_check
: Vec
<(CowStr
<'_
>, Span
)> = Vec
::new();
520 let mut paragraph_span
= spans
.get(0).expect("function isn't called if doc comment is empty").1;
521 for (event
, range
) in events
{
523 Start(CodeBlock(ref kind
)) => {
525 if let CodeBlockKind
::Fenced(lang
) = kind
{
526 for item
in lang
.split('
,'
) {
527 if item
== "ignore" {
531 if let Some(stripped
) = item
.strip_prefix("edition") {
533 edition
= stripped
.parse
::<Edition
>().ok();
534 } else if item
.is_empty() || RUST_CODE
.contains(&item
) {
540 End(CodeBlock(_
)) => {
544 Start(Link(_
, url
, _
)) => in_link
= Some(url
),
545 End(Link(..)) => in_link
= None
,
546 Start(Heading(_
, _
, _
) | Paragraph
| Item
) => {
547 if let Start(Heading(_
, _
, _
)) = event
{
550 ticks_unbalanced
= false;
551 let (_
, span
) = get_current_span(spans
, range
.start
);
552 paragraph_span
= first_line_of_span(cx
, span
);
554 End(Heading(_
, _
, _
) | Paragraph
| Item
) => {
555 if let End(Heading(_
, _
, _
)) = event
{
558 if ticks_unbalanced
{
563 "backticks are unbalanced",
565 "a backtick may be missing a pair",
568 for (text
, span
) in text_to_check
{
569 check_text(cx
, valid_idents
, &text
, span
);
572 text_to_check
= Vec
::new();
574 Start(_tag
) | End(_tag
) => (), // We don't care about other tags
575 Html(_html
) => (), // HTML is weird, just ignore it
576 SoftBreak
| HardBreak
| TaskListMarker(_
) | Code(_
) | Rule
=> (),
577 FootnoteReference(text
) | Text(text
) => {
578 let (begin
, span
) = get_current_span(spans
, range
.start
);
579 paragraph_span
= paragraph_span
.with_hi(span
.hi());
580 ticks_unbalanced
|= text
.contains('`'
) && !in_code
;
581 if Some(&text
) == in_link
.as_ref() || ticks_unbalanced
{
582 // Probably a link of the form `<http://example.com>`
583 // Which are represented as a link to "http://example.com" with
584 // text "http://example.com" by pulldown-cmark
587 let trimmed_text
= text
.trim();
588 headers
.safety
|= in_heading
&& trimmed_text
== "Safety";
589 headers
.safety
|= in_heading
&& trimmed_text
== "Implementation safety";
590 headers
.safety
|= in_heading
&& trimmed_text
== "Implementation Safety";
591 headers
.errors
|= in_heading
&& trimmed_text
== "Errors";
592 headers
.panics
|= in_heading
&& trimmed_text
== "Panics";
595 let edition
= edition
.unwrap_or_else(|| cx
.tcx
.sess
.edition());
596 check_code(cx
, &text
, edition
, span
);
599 // Adjust for the beginning of the current `Event`
600 let span
= span
.with_lo(span
.lo() + BytePos
::from_usize(range
.start
- begin
));
601 text_to_check
.push((text
, span
));
609 fn get_current_span(spans
: &[(usize, Span
)], idx
: usize) -> (usize, Span
) {
610 let index
= match spans
.binary_search_by(|c
| c
.0.cmp(&idx
)) {
617 fn check_code(cx
: &LateContext
<'_
>, text
: &str, edition
: Edition
, span
: Span
) {
618 fn has_needless_main(code
: String
, edition
: Edition
) -> bool
{
619 rustc_driver
::catch_fatal_errors(|| {
620 rustc_span
::create_session_globals_then(edition
, || {
621 let filename
= FileName
::anon_source_code(&code
);
623 let sm
= Lrc
::new(SourceMap
::new(FilePathMapping
::empty()));
624 let emitter
= EmitterWriter
::new(Box
::new(io
::sink()), None
, false, false, false, None
, false);
625 let handler
= Handler
::with_emitter(false, None
, Box
::new(emitter
));
626 let sess
= ParseSess
::with_span_handler(handler
, sm
);
628 let mut parser
= match maybe_new_parser_from_source_str(&sess
, filename
, code
) {
636 let mut relevant_main_found
= false;
638 match parser
.parse_item(ForceCollect
::No
) {
639 Ok(Some(item
)) => match &item
.kind
{
640 ItemKind
::Fn(box Fn
{
641 sig
, body
: Some(block
), ..
642 }) 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,
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;
654 // This main function should not be linted, we're done
658 // Tests with one of these items are ignored
660 | ItemKind
::Const(..)
661 | ItemKind
::ExternCrate(..)
662 | ItemKind
::ForeignMod(..)
663 // Another function was found; this case is ignored
664 | ItemKind
::Fn(..) => return false,
682 // Because of the global session, we need to create a new session in a different thread with
683 // the edition we need.
684 let text
= text
.to_owned();
685 if thread
::spawn(move || has_needless_main(text
, edition
))
687 .expect("thread::spawn failed")
689 span_lint(cx
, NEEDLESS_DOCTEST_MAIN
, span
, "needless `fn main` in doctest");
693 fn check_text(cx
: &LateContext
<'_
>, valid_idents
: &FxHashSet
<String
>, text
: &str, span
: Span
) {
694 for word
in text
.split(|c
: char| c
.is_whitespace() || c
== '
\''
) {
695 // Trim punctuation as in `some comment (see foo::bar).`
697 // Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
698 let mut word
= word
.trim_matches(|c
: char| !c
.is_alphanumeric() && c
!= '
:'
);
700 // Remove leading or trailing single `:` which may be part of a sentence.
701 if word
.starts_with('
:'
) && !word
.starts_with("::") {
702 word
= word
.trim_start_matches('
:'
);
704 if word
.ends_with('
:'
) && !word
.ends_with("::") {
705 word
= word
.trim_end_matches('
:'
);
708 if valid_idents
.contains(word
) || word
.chars().all(|c
| c
== '
:'
) {
712 // Adjust for the current word
713 let offset
= word
.as_ptr() as usize - text
.as_ptr() as usize;
714 let span
= Span
::new(
715 span
.lo() + BytePos
::from_usize(offset
),
716 span
.lo() + BytePos
::from_usize(offset
+ word
.len()),
721 check_word(cx
, word
, span
);
725 fn check_word(cx
: &LateContext
<'_
>, word
: &str, span
: Span
) {
726 /// Checks if a string is camel-case, i.e., contains at least two uppercase
727 /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
728 /// Plurals are also excluded (`IDs` is ok).
729 fn is_camel_case(s
: &str) -> bool
{
730 if s
.starts_with(|c
: char| c
.is_digit(10)) {
734 let s
= s
.strip_suffix('s'
).unwrap_or(s
);
736 s
.chars().all(char::is_alphanumeric
)
737 && s
.chars().filter(|&c
| c
.is_uppercase()).take(2).count() > 1
738 && s
.chars().filter(|&c
| c
.is_lowercase()).take(1).count() > 0
741 fn has_underscore(s
: &str) -> bool
{
742 s
!= "_" && !s
.contains("\\_") && s
.contains('_'
)
745 fn has_hyphen(s
: &str) -> bool
{
746 s
!= "-" && s
.contains('
-'
)
749 if let Ok(url
) = Url
::parse(word
) {
750 // try to get around the fact that `foo::bar` parses as a valid URL
751 if !url
.cannot_be_a_base() {
756 "you should put bare URLs between `<`/`>` or make a proper Markdown link",
763 // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
764 if has_underscore(word
) && has_hyphen(word
) {
768 if has_underscore(word
) || word
.contains("::") || is_camel_case(word
) {
769 let mut applicability
= Applicability
::MachineApplicable
;
775 "item in documentation is missing backticks",
777 let snippet
= snippet_with_applicability(cx
, span
, "..", &mut applicability
);
778 diag
.span_suggestion_with_style(
781 format
!("`{}`", snippet
),
783 // always show the suggestion in a separate line, since the
784 // inline presentation adds another pair of backticks
785 SuggestionStyle
::ShowAlways
,
792 struct FindPanicUnwrap
<'a
, 'tcx
> {
793 cx
: &'a LateContext
<'tcx
>,
794 panic_span
: Option
<Span
>,
795 typeck_results
: &'tcx ty
::TypeckResults
<'tcx
>,
798 impl<'a
, 'tcx
> Visitor
<'tcx
> for FindPanicUnwrap
<'a
, 'tcx
> {
799 type NestedFilter
= nested_filter
::OnlyBodies
;
801 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
802 if self.panic_span
.is_some() {
806 if let Some(macro_call
) = root_macro_call_first_node(self.cx
, expr
) {
807 if is_panic(self.cx
, macro_call
.def_id
)
809 self.cx
.tcx
.item_name(macro_call
.def_id
).as_str(),
810 "assert" | "assert_eq" | "assert_ne" | "todo"
813 self.panic_span
= Some(macro_call
.span
);
817 // check for `unwrap`
818 if let Some(arglists
) = method_chain_args(expr
, &["unwrap"]) {
819 let receiver_ty
= self.typeck_results
.expr_ty(&arglists
[0][0]).peel_refs();
820 if is_type_diagnostic_item(self.cx
, receiver_ty
, sym
::Option
)
821 || is_type_diagnostic_item(self.cx
, receiver_ty
, sym
::Result
)
823 self.panic_span
= Some(expr
.span
);
827 // and check sub-expressions
828 intravisit
::walk_expr(self, expr
);
831 // Panics in const blocks will cause compilation to fail.
832 fn visit_anon_const(&mut self, _
: &'tcx AnonConst
) {}
834 fn nested_visit_map(&mut self) -> Self::Map
{