@@ -655,6 +655,205 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
655655 headers
656656}
657657
658+ <<<<<<< HEAD : src/tools/clippy/clippy_lints/src/doc/mod . rs
659+ =======
660+ fn check_link_quotes ( cx : & LateContext < ' _ > , trimmed_text : & str , range : Range < usize > , fragments : Fragments < ' _ > ) {
661+ if trimmed_text. starts_with ( '\'' )
662+ && trimmed_text. ends_with( '\'' )
663+ && let Some ( span) = fragments. span( cx, range)
664+ {
665+ span_lint(
666+ cx,
667+ DOC_LINK_WITH_QUOTES ,
668+ span,
669+ "possible intra-doc link using quotes instead of backticks" ,
670+ ) ;
671+ }
672+ }
673+
674+ fn check_code ( cx : & LateContext < ' _ > , text : & str , edition : Edition , range : Range < usize > , fragments : Fragments < ' _ > ) {
675+ fn has_needless_main ( code : String , edition : Edition ) -> bool {
676+ rustc_driver:: catch_fatal_errors ( || {
677+ rustc_span:: create_session_globals_then( edition, || {
678+ let filename = FileName :: anon_source_code( & code) ;
679+
680+ let fallback_bundle =
681+ rustc_errors:: fallback_fluent_bundle( rustc_driver:: DEFAULT_LOCALE_RESOURCES . to_vec( ) , false ) ;
682+ let emitter = EmitterWriter :: new( Box :: new( io:: sink( ) ) , fallback_bundle) ;
683+ let handler = Handler :: with_emitter( Box :: new( emitter) ) . disable_warnings( ) ;
684+ #[ expect( clippy:: arc_with_non_send_sync) ] // `Lrc` is expected by with_span_handler
685+ let sm = Lrc :: new( SourceMap :: new( FilePathMapping :: empty( ) ) ) ;
686+ let sess = ParseSess :: with_span_handler( handler, sm) ;
687+
688+ let mut parser = match maybe_new_parser_from_source_str( & sess, filename, code) {
689+ Ok ( p) => p,
690+ Err ( errs) => {
691+ drop( errs) ;
692+ return false ;
693+ } ,
694+ } ;
695+
696+ let mut relevant_main_found = false ;
697+ loop {
698+ match parser. parse_item( ForceCollect :: No ) {
699+ Ok ( Some ( item) ) => match & item. kind {
700+ ItemKind : : Fn ( box Fn {
701+ sig, body : Some ( block) , ..
702+ } ) if item. ident. name == sym:: main => {
703+ let is_async = sig. header. coro_kind. is_async( ) ;
704+ let returns_nothing = match & sig. decl. output {
705+ FnRetTy : : Default ( ..) => true ,
706+ FnRetTy : : Ty ( ty) if ty. kind. is_unit( ) => true ,
707+ FnRetTy : : Ty ( _) => false ,
708+ } ;
709+
710+ if returns_nothing && !is_async && !block. stmts. is_empty( ) {
711+ // This main function should be linted, but only if there are no other functions
712+ relevant_main_found = true ;
713+ } else {
714+ // This main function should not be linted, we're done
715+ return false ;
716+ }
717+ } ,
718+ // Tests with one of these items are ignored
719+ ItemKind :: Static ( ..)
720+ | ItemKind :: Const ( ..)
721+ | ItemKind :: ExternCrate ( ..)
722+ | ItemKind :: ForeignMod ( ..)
723+ // Another function was found; this case is ignored
724+ | ItemKind :: Fn ( ..) => return false,
725+ _ => { } ,
726+ } ,
727+ Ok ( None ) => break ,
728+ Err ( e) => {
729+ e. cancel( ) ;
730+ return false ;
731+ } ,
732+ }
733+ }
734+
735+ relevant_main_found
736+ } )
737+ } )
738+ . ok( )
739+ . unwrap_or_default( )
740+ }
741+
742+ let trailing_whitespace = text. len( ) - text. trim_end( ) . len( ) ;
743+
744+ // Because of the global session, we need to create a new session in a different thread with
745+ // the edition we need.
746+ let text = text. to_owned( ) ;
747+ if thread:: spawn( move || has_needless_main( text, edition) )
748+ . join( )
749+ . expect( "thread:: spawn failed")
750+ && let Some ( span) = fragments. span( cx, range. start..range. end - trailing_whitespace)
751+ {
752+ span_lint( cx, NEEDLESS_DOCTEST_MAIN , span, "needless `fn main` in doctest") ;
753+ }
754+ }
755+
756+ fn check_text( cx: & LateContext < ' _ > , valid_idents : & FxHashSet < String > , text : & str , span : Span ) {
757+ for word in text. split( |c: char | c. is_whitespace( ) || c == '\'' ) {
758+ // Trim punctuation as in `some comment (see foo::bar).`
759+ // ^^
760+ // Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
761+ let mut word = word. trim_matches( |c: char | !c. is_alphanumeric( ) && c != ':' ) ;
762+
763+ // Remove leading or trailing single `:` which may be part of a sentence.
764+ if word. starts_with( ':' ) && !word. starts_with( ":: ") {
765+ word = word. trim_start_matches( ':' ) ;
766+ }
767+ if word. ends_with( ':' ) && !word. ends_with( ":: ") {
768+ word = word. trim_end_matches( ':' ) ;
769+ }
770+
771+ if valid_idents. contains( word) || word. chars( ) . all( |c| c == ':' ) {
772+ continue ;
773+ }
774+
775+ // Adjust for the current word
776+ let offset = word. as_ptr( ) as usize - text. as_ptr ( ) as usize ;
777+ let span = Span :: new (
778+ span . lo( ) + BytePos :: from_usize ( offset ) ,
779+ span . lo( ) + BytePos :: from_usize ( offset + word . len( ) ) ,
780+ span. ctxt ( ) ,
781+ span. parent ( ) ,
782+ ) ;
783+
784+ check_word ( cx , word , span ) ;
785+ }
786+ }
787+
788+ fn check_word ( cx : & LateContext < ' _ > , word : & str , span : Span ) {
789+ /// Checks if a string is upper-camel-case, i.e., starts with an uppercase and
790+ /// contains at least two uppercase letters (`Clippy` is ok) and one lower-case
791+ /// letter (`NASA` is ok).
792+ /// Plurals are also excluded (`IDs` is ok).
793+ fn is_camel_case ( s : & str ) -> bool {
794+ if s. starts_with( |c: char | c. is_ascii_digit( ) | c. is_ascii_lowercase( ) ) {
795+ return false ;
796+ }
797+
798+ let s = s. strip_suffix( 's' ) . unwrap_or( s) ;
799+
800+ s. chars( ) . all ( char:: is_alphanumeric )
801+ && s. chars( ) . filter( |& c| c. is_uppercase( ) ) . take( 2 ) . count( ) > 1
802+ && s. chars( ) . filter( |& c| c. is_lowercase( ) ) . take( 1 ) . count( ) > 0
803+ }
804+
805+ fn has_underscore ( s : & str ) -> bool {
806+ s != "_" && !s. contains( "\\ _" ) && s. contains( '_' )
807+ }
808+
809+ fn has_hyphen ( s : & str ) -> bool {
810+ s != "-" && s. contains( '-' )
811+ }
812+
813+ if let Ok ( url) = Url :: parse ( word ) {
814+ // try to get around the fact that `foo::bar` parses as a valid URL
815+ if !url. cannot_be_a_base( ) {
816+ span_lint(
817+ cx,
818+ DOC_MARKDOWN ,
819+ span,
820+ "you should put bare URLs between `<`/`>` or make a proper Markdown link" ,
821+ ) ;
822+
823+ return ;
824+ }
825+ }
826+
827+ // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
828+ if has_underscore( word) && has_hyphen( word) {
829+ return ;
830+ }
831+
832+ if has_underscore( word) || word. contains( "::" ) || is_camel_case( word) {
833+ let mut applicability = Applicability :: MachineApplicable ;
834+
835+ span_lint_and_then(
836+ cx,
837+ DOC_MARKDOWN ,
838+ span,
839+ "item in documentation is missing backticks" ,
840+ |diag| {
841+ let snippet = snippet_with_applicability( cx, span, ".." , & mut applicability) ;
842+ diag. span_suggestion_with_style(
843+ span,
844+ "try" ,
845+ format ! ( "`{snippet}`" ) ,
846+ applicability,
847+ // always show the suggestion in a separate line, since the
848+ // inline presentation adds another pair of backticks
849+ SuggestionStyle :: ShowAlways ,
850+ ) ;
851+ } ,
852+ ) ;
853+ }
854+ }
855+
856+ >>>>>>> d116f1718f1 ( Merge Async and Gen into CoroutineKind ) : src/tools/clippy/clippy_lints/src/doc. rs
658857struct FindPanicUnwrap < ' a , ' tcx > {
659858 cx : & ' a LateContext < ' tcx > ,
660859 panic_span : Option < Span > ,
0 commit comments