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
::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 pulldown_cmark
::Event
::{
9 Code
, End
, FootnoteReference
, HardBreak
, Html
, Rule
, SoftBreak
, Start
, TaskListMarker
, Text
,
11 use pulldown_cmark
::Tag
::{CodeBlock, Heading, Item, Link, Paragraph}
;
12 use pulldown_cmark
::{BrokenLink, CodeBlockKind, CowStr, Options}
;
13 use rustc_ast
::ast
::{Async, Attribute, Fn, FnRetTy, ItemKind}
;
14 use rustc_data_structures
::fx
::FxHashSet
;
15 use rustc_data_structures
::sync
::Lrc
;
16 use rustc_errors
::emitter
::EmitterWriter
;
17 use rustc_errors
::{Applicability, Handler, SuggestionStyle}
;
19 use rustc_hir
::intravisit
::{self, Visitor}
;
20 use rustc_hir
::{AnonConst, Expr}
;
21 use rustc_lint
::{LateContext, LateLintPass}
;
22 use rustc_middle
::hir
::nested_filter
;
23 use rustc_middle
::lint
::in_external_macro
;
25 use rustc_parse
::maybe_new_parser_from_source_str
;
26 use rustc_parse
::parser
::ForceCollect
;
27 use rustc_resolve
::rustdoc
::{
28 add_doc_fragment
, attrs_to_doc_fragments
, main_body_opts
, source_span_for_markdown_range
, DocFragment
,
30 use rustc_session
::parse
::ParseSess
;
31 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
32 use rustc_span
::edition
::Edition
;
33 use rustc_span
::source_map
::{BytePos, FilePathMapping, SourceMap, Span}
;
34 use rustc_span
::{sym, FileName, Pos}
;
36 use std
::{io, thread}
;
39 declare_clippy_lint
! {
41 /// Checks for the presence of `_`, `::` or camel-case words
42 /// outside ticks in documentation.
44 /// ### Why is this bad?
45 /// *Rustdoc* supports markdown formatting, `_`, `::` and
46 /// camel-case probably indicates some code which should be included between
47 /// ticks. `_` can also be used for emphasis in markdown, this lint tries to
50 /// ### Known problems
51 /// Lots of bad docs won’t be fixed, what the lint checks
52 /// for is limited, and there are still false positives. HTML elements and their
53 /// content are not linted.
55 /// In addition, when writing documentation comments, including `[]` brackets
56 /// inside a link text would trip the parser. Therefore, documenting link with
57 /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec
62 /// /// Do something with the foo_bar parameter. See also
63 /// /// that::other::module::foo.
64 /// // ^ `foo_bar` and `that::other::module::foo` should be ticked.
65 /// fn doit(foo_bar: usize) {}
69 /// // Link text with `[]` brackets should be written as following:
70 /// /// Consume the array and return the inner
71 /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec].
72 /// /// [SmallVec]: SmallVec
75 #[clippy::version = "pre 1.29.0"]
78 "presence of `_`, `::` or camel-case outside backticks in documentation"
81 declare_clippy_lint
! {
83 /// Checks for the doc comments of publicly visible
84 /// unsafe functions and warns if there is no `# Safety` section.
86 /// ### Why is this bad?
87 /// Unsafe functions should document their safety
88 /// preconditions, so that users can be sure they are using them safely.
92 ///# type Universe = ();
93 /// /// This function should really be documented
94 /// pub unsafe fn start_apocalypse(u: &mut Universe) {
99 /// At least write a line about safety:
102 ///# type Universe = ();
105 /// /// This function should not be called before the horsemen are ready.
106 /// pub unsafe fn start_apocalypse(u: &mut Universe) {
107 /// unimplemented!();
110 #[clippy::version = "1.39.0"]
111 pub MISSING_SAFETY_DOC
,
113 "`pub unsafe fn` without `# Safety` docs"
116 declare_clippy_lint
! {
118 /// Checks the doc comments of publicly visible functions that
119 /// return a `Result` type and warns if there is no `# Errors` section.
121 /// ### Why is this bad?
122 /// Documenting the type of errors that can be returned from a
123 /// function can help callers write code to handle the errors appropriately.
126 /// Since the following function returns a `Result` it has an `# Errors` section in
133 /// /// Will return `Err` if `filename` does not exist or the user does not have
134 /// /// permission to read it.
135 /// pub fn read(filename: String) -> io::Result<String> {
136 /// unimplemented!();
139 #[clippy::version = "1.41.0"]
140 pub MISSING_ERRORS_DOC
,
142 "`pub fn` returns `Result` without `# Errors` in doc comment"
145 declare_clippy_lint
! {
147 /// Checks the doc comments of publicly visible functions that
148 /// may panic and warns if there is no `# Panics` section.
150 /// ### Why is this bad?
151 /// Documenting the scenarios in which panicking occurs
152 /// can help callers who do not want to panic to avoid those situations.
155 /// Since the following function may panic it has a `# Panics` section in
161 /// /// Will panic if y is 0
162 /// pub fn divide_by(x: i32, y: i32) -> i32 {
164 /// panic!("Cannot divide by 0")
170 #[clippy::version = "1.51.0"]
171 pub MISSING_PANICS_DOC
,
173 "`pub fn` may panic without `# Panics` in doc comment"
176 declare_clippy_lint
! {
178 /// Checks for `fn main() { .. }` in doctests
180 /// ### Why is this bad?
181 /// The test can be shorter (and likely more readable)
182 /// if the `fn main()` is left implicit.
186 /// /// An example of a doctest with a `main()` function
192 /// /// // this needs not be in an `fn`
195 /// fn needless_main() {
196 /// unimplemented!();
199 #[clippy::version = "1.40.0"]
200 pub NEEDLESS_DOCTEST_MAIN
,
202 "presence of `fn main() {` in code examples"
205 declare_clippy_lint
! {
207 /// Detects the syntax `['foo']` in documentation comments (notice quotes instead of backticks)
208 /// outside of code blocks
209 /// ### Why is this bad?
210 /// It is likely a typo when defining an intra-doc link
214 /// /// See also: ['foo']
219 /// /// See also: [`foo`]
222 #[clippy::version = "1.63.0"]
223 pub DOC_LINK_WITH_QUOTES
,
225 "possible typo for an intra-doc link"
228 declare_clippy_lint
! {
230 /// Checks for the doc comments of publicly visible
231 /// safe functions and traits and warns if there is a `# Safety` section.
233 /// ### Why is this bad?
234 /// Safe functions and traits are safe to implement and therefore do not
235 /// need to describe safety preconditions that users are required to uphold.
239 ///# type Universe = ();
242 /// /// This function should not be called before the horsemen are ready.
243 /// pub fn start_apocalypse_but_safely(u: &mut Universe) {
244 /// unimplemented!();
248 /// The function is safe, so there shouldn't be any preconditions
249 /// that have to be explained for safety reasons.
252 ///# type Universe = ();
253 /// /// This function should really be documented
254 /// pub fn start_apocalypse(u: &mut Universe) {
255 /// unimplemented!();
258 #[clippy::version = "1.67.0"]
259 pub UNNECESSARY_SAFETY_DOC
,
261 "`pub fn` or `pub trait` with `# Safety` docs"
264 #[expect(clippy::module_name_repetitions)]
266 pub struct DocMarkdown
{
267 valid_idents
: FxHashSet
<String
>,
272 pub fn new(valid_idents
: FxHashSet
<String
>) -> Self {
275 in_trait_impl
: false,
280 impl_lint_pass
!(DocMarkdown
=> [
281 DOC_LINK_WITH_QUOTES
,
286 NEEDLESS_DOCTEST_MAIN
,
287 UNNECESSARY_SAFETY_DOC
,
290 impl<'tcx
> LateLintPass
<'tcx
> for DocMarkdown
{
291 fn check_crate(&mut self, cx
: &LateContext
<'tcx
>) {
292 let attrs
= cx
.tcx
.hir().attrs(hir
::CRATE_HIR_ID
);
293 check_attrs(cx
, &self.valid_idents
, attrs
);
296 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
297 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
298 let Some(headers
) = check_attrs(cx
, &self.valid_idents
, attrs
) else {
302 hir
::ItemKind
::Fn(ref sig
, _
, body_id
) => {
303 if !(is_entrypoint_fn(cx
, item
.owner_id
.to_def_id()) || in_external_macro(cx
.tcx
.sess
, item
.span
)) {
304 let body
= cx
.tcx
.hir().body(body_id
);
305 let mut fpu
= FindPanicUnwrap
{
307 typeck_results
: cx
.tcx
.typeck(item
.owner_id
.def_id
),
310 fpu
.visit_expr(body
.value
);
311 lint_for_missing_headers(cx
, item
.owner_id
, sig
, headers
, Some(body_id
), fpu
.panic_span
);
314 hir
::ItemKind
::Impl(impl_
) => {
315 self.in_trait_impl
= impl_
.of_trait
.is_some();
317 hir
::ItemKind
::Trait(_
, unsafety
, ..) => match (headers
.safety
, unsafety
) {
318 (false, hir
::Unsafety
::Unsafe
) => span_lint(
321 cx
.tcx
.def_span(item
.owner_id
),
322 "docs for unsafe trait missing `# Safety` section",
324 (true, hir
::Unsafety
::Normal
) => span_lint(
326 UNNECESSARY_SAFETY_DOC
,
327 cx
.tcx
.def_span(item
.owner_id
),
328 "docs for safe trait have unnecessary `# Safety` section",
336 fn check_item_post(&mut self, _cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
337 if let hir
::ItemKind
::Impl { .. }
= item
.kind
{
338 self.in_trait_impl
= false;
342 fn check_trait_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::TraitItem
<'_
>) {
343 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
344 let Some(headers
) = check_attrs(cx
, &self.valid_idents
, attrs
) else {
347 if let hir
::TraitItemKind
::Fn(ref sig
, ..) = item
.kind
{
348 if !in_external_macro(cx
.tcx
.sess
, item
.span
) {
349 lint_for_missing_headers(cx
, item
.owner_id
, sig
, headers
, None
, None
);
354 fn check_impl_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::ImplItem
<'_
>) {
355 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
356 let Some(headers
) = check_attrs(cx
, &self.valid_idents
, attrs
) else {
359 if self.in_trait_impl
|| in_external_macro(cx
.tcx
.sess
, item
.span
) {
362 if let hir
::ImplItemKind
::Fn(ref sig
, body_id
) = item
.kind
{
363 let body
= cx
.tcx
.hir().body(body_id
);
364 let mut fpu
= FindPanicUnwrap
{
366 typeck_results
: cx
.tcx
.typeck(item
.owner_id
.def_id
),
369 fpu
.visit_expr(body
.value
);
370 lint_for_missing_headers(cx
, item
.owner_id
, sig
, headers
, Some(body_id
), fpu
.panic_span
);
375 fn lint_for_missing_headers(
376 cx
: &LateContext
<'_
>,
377 owner_id
: hir
::OwnerId
,
378 sig
: &hir
::FnSig
<'_
>,
380 body_id
: Option
<hir
::BodyId
>,
381 panic_span
: Option
<Span
>,
383 if !cx
.effective_visibilities
.is_exported(owner_id
.def_id
) {
384 return; // Private functions do not require doc comments
387 // do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
391 .parent_iter(owner_id
.into())
392 .any(|(id
, _node
)| is_doc_hidden(cx
.tcx
.hir().attrs(id
)))
397 let span
= cx
.tcx
.def_span(owner_id
);
398 match (headers
.safety
, sig
.header
.unsafety
) {
399 (false, hir
::Unsafety
::Unsafe
) => span_lint(
403 "unsafe function's docs miss `# Safety` section",
405 (true, hir
::Unsafety
::Normal
) => span_lint(
407 UNNECESSARY_SAFETY_DOC
,
409 "safe function's docs have unnecessary `# Safety` section",
413 if !headers
.panics
&& panic_span
.is_some() {
418 "docs for function which may panic missing `# Panics` section",
420 "first possible panic found here",
424 if is_type_diagnostic_item(cx
, return_ty(cx
, owner_id
), sym
::Result
) {
429 "docs for function returning `Result` missing `# Errors` section",
433 if let Some(body_id
) = body_id
;
434 if let Some(future
) = cx
.tcx
.lang_items().future_trait();
435 let typeck
= cx
.tcx
.typeck_body(body_id
);
436 let body
= cx
.tcx
.hir().body(body_id
);
437 let ret_ty
= typeck
.expr_ty(body
.value
);
438 if implements_trait(cx
, ret_ty
, future
, &[]);
439 if let ty
::Generator(_
, subs
, _
) = ret_ty
.kind();
440 if is_type_diagnostic_item(cx
, subs
.as_generator().return_ty(), sym
::Result
);
446 "docs for function returning `Result` missing `# Errors` section",
454 #[derive(Copy, Clone)]
455 struct Fragments
<'a
> {
457 fragments
: &'a
[DocFragment
],
461 fn span(self, cx
: &LateContext
<'_
>, range
: Range
<usize>) -> Option
<Span
> {
462 source_span_for_markdown_range(cx
.tcx
, self.doc
, &range
, self.fragments
)
466 #[derive(Copy, Clone, Default)]
473 fn check_attrs(cx
: &LateContext
<'_
>, valid_idents
: &FxHashSet
<String
>, attrs
: &[Attribute
]) -> Option
<DocHeaders
> {
474 /// We don't want the parser to choke on intra doc links. Since we don't
475 /// actually care about rendering them, just pretend that all broken links
476 /// point to a fake address.
477 #[expect(clippy::unnecessary_wraps)] // we're following a type signature
478 fn fake_broken_link_callback
<'a
>(_
: BrokenLink
<'_
>) -> Option
<(CowStr
<'a
>, CowStr
<'a
>)> {
479 Some(("fake".into(), "fake".into()))
482 if is_doc_hidden(attrs
) {
486 let (fragments
, _
) = attrs_to_doc_fragments(attrs
.iter().map(|attr
| (attr
, None
)), true);
487 let mut doc
= String
::new();
488 for fragment
in &fragments
{
489 add_doc_fragment(&mut doc
, fragment
);
494 return Some(DocHeaders
::default());
497 let mut cb
= fake_broken_link_callback
;
499 // disable smart punctuation to pick up ['link'] more easily
500 let opts
= main_body_opts() - Options
::ENABLE_SMART_PUNCTUATION
;
501 let parser
= pulldown_cmark
::Parser
::new_with_broken_link_callback(&doc
, opts
, Some(&mut cb
));
506 parser
.into_offset_iter(),
508 fragments
: &fragments
,
514 const RUST_CODE
: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
516 #[allow(clippy::too_many_lines)] // Only a big match statement
517 fn check_doc
<'a
, Events
: Iterator
<Item
= (pulldown_cmark
::Event
<'a
>, Range
<usize>)>>(
518 cx
: &LateContext
<'_
>,
519 valid_idents
: &FxHashSet
<String
>,
521 fragments
: Fragments
<'_
>,
523 // true if a safety header was found
524 let mut headers
= DocHeaders
::default();
525 let mut in_code
= false;
526 let mut in_link
= None
;
527 let mut in_heading
= false;
528 let mut is_rust
= false;
529 let mut no_test
= false;
530 let mut edition
= None
;
531 let mut ticks_unbalanced
= false;
532 let mut text_to_check
: Vec
<(CowStr
<'_
>, Range
<usize>)> = Vec
::new();
533 let mut paragraph_range
= 0..0;
534 for (event
, range
) in events
{
536 Start(CodeBlock(ref kind
)) => {
538 if let CodeBlockKind
::Fenced(lang
) = kind
{
539 for item
in lang
.split('
,'
) {
540 if item
== "ignore" {
543 } else if item
== "no_test" {
546 if let Some(stripped
) = item
.strip_prefix("edition") {
548 edition
= stripped
.parse
::<Edition
>().ok();
549 } else if item
.is_empty() || RUST_CODE
.contains(&item
) {
555 End(CodeBlock(_
)) => {
559 Start(Link(_
, url
, _
)) => in_link
= Some(url
),
560 End(Link(..)) => in_link
= None
,
561 Start(Heading(_
, _
, _
) | Paragraph
| Item
) => {
562 if let Start(Heading(_
, _
, _
)) = event
{
565 ticks_unbalanced
= false;
566 paragraph_range
= range
;
568 End(Heading(_
, _
, _
) | Paragraph
| Item
) => {
569 if let End(Heading(_
, _
, _
)) = event
{
573 && let Some(span
) = fragments
.span(cx
, paragraph_range
.clone())
579 "backticks are unbalanced",
581 "a backtick may be missing a pair",
584 for (text
, range
) in text_to_check
{
585 if let Some(span
) = fragments
.span(cx
, range
) {
586 check_text(cx
, valid_idents
, &text
, span
);
590 text_to_check
= Vec
::new();
592 Start(_tag
) | End(_tag
) => (), // We don't care about other tags
593 Html(_html
) => (), // HTML is weird, just ignore it
594 SoftBreak
| HardBreak
| TaskListMarker(_
) | Code(_
) | Rule
=> (),
595 FootnoteReference(text
) | Text(text
) => {
596 paragraph_range
.end
= range
.end
;
597 ticks_unbalanced
|= text
.contains('`'
) && !in_code
;
598 if Some(&text
) == in_link
.as_ref() || ticks_unbalanced
{
599 // Probably a link of the form `<http://example.com>`
600 // Which are represented as a link to "http://example.com" with
601 // text "http://example.com" by pulldown-cmark
604 let trimmed_text
= text
.trim();
605 headers
.safety
|= in_heading
&& trimmed_text
== "Safety";
606 headers
.safety
|= in_heading
&& trimmed_text
== "Implementation safety";
607 headers
.safety
|= in_heading
&& trimmed_text
== "Implementation Safety";
608 headers
.errors
|= in_heading
&& trimmed_text
== "Errors";
609 headers
.panics
|= in_heading
&& trimmed_text
== "Panics";
611 if is_rust
&& !no_test
{
612 let edition
= edition
.unwrap_or_else(|| cx
.tcx
.sess
.edition());
613 check_code(cx
, &text
, edition
, range
.clone(), fragments
);
616 if in_link
.is_some() {
617 check_link_quotes(cx
, trimmed_text
, range
.clone(), fragments
);
619 if let Some(link
) = in_link
.as_ref()
620 && let Ok(url
) = Url
::parse(link
)
621 && (url
.scheme() == "https" || url
.scheme() == "http") {
622 // Don't check the text associated with external URLs
625 text_to_check
.push((text
, range
));
633 fn check_link_quotes(cx
: &LateContext
<'_
>, trimmed_text
: &str, range
: Range
<usize>, fragments
: Fragments
<'_
>) {
634 if trimmed_text
.starts_with('
\''
)
635 && trimmed_text
.ends_with('
\''
)
636 && let Some(span
) = fragments
.span(cx
, range
)
640 DOC_LINK_WITH_QUOTES
,
642 "possible intra-doc link using quotes instead of backticks",
647 fn check_code(cx
: &LateContext
<'_
>, text
: &str, edition
: Edition
, range
: Range
<usize>, fragments
: Fragments
<'_
>) {
648 fn has_needless_main(code
: String
, edition
: Edition
) -> bool
{
649 rustc_driver
::catch_fatal_errors(|| {
650 rustc_span
::create_session_globals_then(edition
, || {
651 let filename
= FileName
::anon_source_code(&code
);
653 let sm
= Lrc
::new(SourceMap
::new(FilePathMapping
::empty()));
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 let sess
= ParseSess
::with_span_handler(handler
, sm
);
660 let mut parser
= match maybe_new_parser_from_source_str(&sess
, filename
, code
) {
668 let mut relevant_main_found
= false;
670 match parser
.parse_item(ForceCollect
::No
) {
671 Ok(Some(item
)) => match &item
.kind
{
672 ItemKind
::Fn(box Fn
{
673 sig
, body
: Some(block
), ..
674 }) if item
.ident
.name
== sym
::main
=> {
675 let is_async
= matches
!(sig
.header
.asyncness
, Async
::Yes { .. }
);
676 let returns_nothing
= match &sig
.decl
.output
{
677 FnRetTy
::Default(..) => true,
678 FnRetTy
::Ty(ty
) if ty
.kind
.is_unit() => true,
679 FnRetTy
::Ty(_
) => false,
682 if returns_nothing
&& !is_async
&& !block
.stmts
.is_empty() {
683 // This main function should be linted, but only if there are no other functions
684 relevant_main_found
= true;
686 // This main function should not be linted, we're done
690 // Tests with one of these items are ignored
692 | ItemKind
::Const(..)
693 | ItemKind
::ExternCrate(..)
694 | ItemKind
::ForeignMod(..)
695 // Another function was found; this case is ignored
696 | ItemKind
::Fn(..) => return false,
714 let trailing_whitespace
= text
.len() - text
.trim_end().len();
716 // Because of the global session, we need to create a new session in a different thread with
717 // the edition we need.
718 let text
= text
.to_owned();
719 if thread
::spawn(move || has_needless_main(text
, edition
)).join().expect("thread::spawn failed")
720 && let Some(span
) = fragments
.span(cx
, range
.start
..range
.end
- trailing_whitespace
)
722 span_lint(cx
, NEEDLESS_DOCTEST_MAIN
, span
, "needless `fn main` in doctest");
726 fn check_text(cx
: &LateContext
<'_
>, valid_idents
: &FxHashSet
<String
>, text
: &str, span
: Span
) {
727 for word
in text
.split(|c
: char| c
.is_whitespace() || c
== '
\''
) {
728 // Trim punctuation as in `some comment (see foo::bar).`
730 // Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
731 let mut word
= word
.trim_matches(|c
: char| !c
.is_alphanumeric() && c
!= '
:'
);
733 // Remove leading or trailing single `:` which may be part of a sentence.
734 if word
.starts_with('
:'
) && !word
.starts_with("::") {
735 word
= word
.trim_start_matches('
:'
);
737 if word
.ends_with('
:'
) && !word
.ends_with("::") {
738 word
= word
.trim_end_matches('
:'
);
741 if valid_idents
.contains(word
) || word
.chars().all(|c
| c
== '
:'
) {
745 // Adjust for the current word
746 let offset
= word
.as_ptr() as usize - text
.as_ptr() as usize;
747 let span
= Span
::new(
748 span
.lo() + BytePos
::from_usize(offset
),
749 span
.lo() + BytePos
::from_usize(offset
+ word
.len()),
754 check_word(cx
, word
, span
);
758 fn check_word(cx
: &LateContext
<'_
>, word
: &str, span
: Span
) {
759 /// Checks if a string is camel-case, i.e., contains at least two uppercase
760 /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
761 /// Plurals are also excluded (`IDs` is ok).
762 fn is_camel_case(s
: &str) -> bool
{
763 if s
.starts_with(|c
: char| c
.is_ascii_digit()) {
767 let s
= s
.strip_suffix('s'
).unwrap_or(s
);
769 s
.chars().all(char::is_alphanumeric
)
770 && s
.chars().filter(|&c
| c
.is_uppercase()).take(2).count() > 1
771 && s
.chars().filter(|&c
| c
.is_lowercase()).take(1).count() > 0
774 fn has_underscore(s
: &str) -> bool
{
775 s
!= "_" && !s
.contains("\\_") && s
.contains('_'
)
778 fn has_hyphen(s
: &str) -> bool
{
779 s
!= "-" && s
.contains('
-'
)
782 if let Ok(url
) = Url
::parse(word
) {
783 // try to get around the fact that `foo::bar` parses as a valid URL
784 if !url
.cannot_be_a_base() {
789 "you should put bare URLs between `<`/`>` or make a proper Markdown link",
796 // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
797 if has_underscore(word
) && has_hyphen(word
) {
801 if has_underscore(word
) || word
.contains("::") || is_camel_case(word
) {
802 let mut applicability
= Applicability
::MachineApplicable
;
808 "item in documentation is missing backticks",
810 let snippet
= snippet_with_applicability(cx
, span
, "..", &mut applicability
);
811 diag
.span_suggestion_with_style(
814 format
!("`{snippet}`"),
816 // always show the suggestion in a separate line, since the
817 // inline presentation adds another pair of backticks
818 SuggestionStyle
::ShowAlways
,
825 struct FindPanicUnwrap
<'a
, 'tcx
> {
826 cx
: &'a LateContext
<'tcx
>,
827 panic_span
: Option
<Span
>,
828 typeck_results
: &'tcx ty
::TypeckResults
<'tcx
>,
831 impl<'a
, 'tcx
> Visitor
<'tcx
> for FindPanicUnwrap
<'a
, 'tcx
> {
832 type NestedFilter
= nested_filter
::OnlyBodies
;
834 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
835 if self.panic_span
.is_some() {
839 if let Some(macro_call
) = root_macro_call_first_node(self.cx
, expr
) {
840 if is_panic(self.cx
, macro_call
.def_id
)
842 self.cx
.tcx
.item_name(macro_call
.def_id
).as_str(),
843 "assert" | "assert_eq" | "assert_ne"
846 self.panic_span
= Some(macro_call
.span
);
850 // check for `unwrap` and `expect` for both `Option` and `Result`
851 if let Some(arglists
) = method_chain_args(expr
, &["unwrap"]).or(method_chain_args(expr
, &["expect"])) {
852 let receiver_ty
= self.typeck_results
.expr_ty(arglists
[0].0).peel_refs();
853 if is_type_diagnostic_item(self.cx
, receiver_ty
, sym
::Option
)
854 || is_type_diagnostic_item(self.cx
, receiver_ty
, sym
::Result
)
856 self.panic_span
= Some(expr
.span
);
860 // and check sub-expressions
861 intravisit
::walk_expr(self, expr
);
864 // Panics in const blocks will cause compilation to fail.
865 fn visit_anon_const(&mut self, _
: &'tcx AnonConst
) {}
867 fn nested_visit_map(&mut self) -> Self::Map
{