@@ -3,7 +3,7 @@ use aho_corasick::AhoCorasickBuilder;
33use indoc:: writedoc;
44use itertools:: Itertools ;
55use rustc_lexer:: { tokenize, unescape, LiteralKind , TokenKind } ;
6- use std:: collections:: { HashMap , HashSet } ;
6+ use std:: collections:: { BTreeSet , HashMap , HashSet } ;
77use std:: ffi:: OsStr ;
88use std:: fmt:: Write ;
99use std:: fs:: { self , OpenOptions } ;
@@ -124,6 +124,8 @@ fn generate_lint_files(
124124 let content = gen_lint_group_list ( "all" , all_group_lints) ;
125125 process_file ( "clippy_lints/src/lib.register_all.rs" , update_mode, & content) ;
126126
127+ update_docs ( update_mode, & usable_lints) ;
128+
127129 for ( lint_group, lints) in Lint :: by_lint_group ( usable_lints. into_iter ( ) . chain ( internal_lints) ) {
128130 let content = gen_lint_group_list ( & lint_group, lints. iter ( ) ) ;
129131 process_file (
@@ -140,6 +142,62 @@ fn generate_lint_files(
140142 process_file ( "tests/ui/rename.rs" , update_mode, & content) ;
141143}
142144
145+ fn update_docs ( update_mode : UpdateMode , usable_lints : & [ Lint ] ) {
146+ replace_region_in_file ( update_mode, Path :: new ( "src/docs.rs" ) , "docs! {\n " , "\n }\n " , |res| {
147+ for name in usable_lints. iter ( ) . map ( |lint| lint. name . clone ( ) ) . sorted ( ) {
148+ writeln ! ( res, r#" "{name}","# ) . unwrap ( ) ;
149+ }
150+ } ) ;
151+
152+ if update_mode == UpdateMode :: Check {
153+ let mut extra = BTreeSet :: new ( ) ;
154+ let mut lint_names = usable_lints
155+ . iter ( )
156+ . map ( |lint| lint. name . clone ( ) )
157+ . collect :: < BTreeSet < _ > > ( ) ;
158+ for file in std:: fs:: read_dir ( "src/docs" ) . unwrap ( ) {
159+ let filename = file. unwrap ( ) . file_name ( ) . into_string ( ) . unwrap ( ) ;
160+ if let Some ( name) = filename. strip_suffix ( ".txt" ) {
161+ if !lint_names. remove ( name) {
162+ extra. insert ( name. to_string ( ) ) ;
163+ }
164+ }
165+ }
166+
167+ let failed = print_lint_names ( "extra lint docs:" , & extra) | print_lint_names ( "missing lint docs:" , & lint_names) ;
168+
169+ if failed {
170+ exit_with_failure ( ) ;
171+ }
172+ } else {
173+ if std:: fs:: remove_dir_all ( "src/docs" ) . is_err ( ) {
174+ eprintln ! ( "could not remove src/docs directory" ) ;
175+ }
176+ if std:: fs:: create_dir ( "src/docs" ) . is_err ( ) {
177+ eprintln ! ( "could not recreate src/docs directory" ) ;
178+ }
179+ }
180+ for lint in usable_lints {
181+ process_file (
182+ Path :: new ( "src/docs" ) . join ( lint. name . clone ( ) + ".txt" ) ,
183+ update_mode,
184+ & lint. documentation ,
185+ ) ;
186+ }
187+ }
188+
189+ fn print_lint_names ( header : & str , lints : & BTreeSet < String > ) -> bool {
190+ if lints. is_empty ( ) {
191+ return false ;
192+ }
193+ println ! ( "{}" , header) ;
194+ for lint in lints. iter ( ) . sorted ( ) {
195+ println ! ( " {}" , lint) ;
196+ }
197+ println ! ( ) ;
198+ true
199+ }
200+
143201pub fn print_lints ( ) {
144202 let ( lint_list, _, _) = gather_all ( ) ;
145203 let usable_lints = Lint :: usable_lints ( & lint_list) ;
@@ -589,17 +647,26 @@ struct Lint {
589647 desc : String ,
590648 module : String ,
591649 declaration_range : Range < usize > ,
650+ documentation : String ,
592651}
593652
594653impl Lint {
595654 #[ must_use]
596- fn new ( name : & str , group : & str , desc : & str , module : & str , declaration_range : Range < usize > ) -> Self {
655+ fn new (
656+ name : & str ,
657+ group : & str ,
658+ desc : & str ,
659+ module : & str ,
660+ declaration_range : Range < usize > ,
661+ documentation : String ,
662+ ) -> Self {
597663 Self {
598664 name : name. to_lowercase ( ) ,
599665 group : group. into ( ) ,
600666 desc : remove_line_splices ( desc) ,
601667 module : module. into ( ) ,
602668 declaration_range,
669+ documentation,
603670 }
604671 }
605672
@@ -852,27 +919,35 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
852919 } | token_kind == & TokenKind :: Ident && * content == "declare_clippy_lint" ,
853920 ) {
854921 let start = range. start ;
855-
856- let mut iter = iter
857- . by_ref ( )
858- . filter ( |t| !matches ! ( t. token_kind, TokenKind :: Whitespace | TokenKind :: LineComment { .. } ) ) ;
922+ let mut docs = String :: with_capacity ( 128 ) ;
923+ let mut iter = iter. by_ref ( ) . filter ( |t| !matches ! ( t. token_kind, TokenKind :: Whitespace ) ) ;
859924 // matches `!{`
860925 match_tokens ! ( iter, Bang OpenBrace ) ;
861- match iter. next ( ) {
862- // #[clippy::version = "version"] pub
863- Some ( LintDeclSearchResult {
864- token_kind : TokenKind :: Pound ,
865- ..
866- } ) => {
867- match_tokens ! ( iter, OpenBracket Ident Colon Colon Ident Eq Literal { ..} CloseBracket Ident ) ;
868- } ,
869- // pub
870- Some ( LintDeclSearchResult {
871- token_kind : TokenKind :: Ident ,
872- ..
873- } ) => ( ) ,
874- _ => continue ,
926+ let mut in_code = false ;
927+ while let Some ( t) = iter. next ( ) {
928+ match t. token_kind {
929+ TokenKind :: LineComment { .. } => {
930+ if let Some ( line) = t. content . strip_prefix ( "/// " ) . or_else ( || t. content . strip_prefix ( "///" ) ) {
931+ if line. starts_with ( "```" ) {
932+ docs += "```\n " ;
933+ in_code = !in_code;
934+ } else if !( in_code && line. starts_with ( "# " ) ) {
935+ docs += line;
936+ docs. push ( '\n' ) ;
937+ }
938+ }
939+ } ,
940+ TokenKind :: Pound => {
941+ match_tokens ! ( iter, OpenBracket Ident Colon Colon Ident Eq Literal { ..} CloseBracket Ident ) ;
942+ break ;
943+ } ,
944+ TokenKind :: Ident => {
945+ break ;
946+ } ,
947+ _ => { } ,
948+ }
875949 }
950+ docs. pop ( ) ; // remove final newline
876951
877952 let ( name, group, desc) = match_tokens ! (
878953 iter,
@@ -890,7 +965,7 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
890965 ..
891966 } ) = iter. next ( )
892967 {
893- lints. push ( Lint :: new ( name, group, desc, module, start..range. end ) ) ;
968+ lints. push ( Lint :: new ( name, group, desc, module, start..range. end , docs ) ) ;
894969 }
895970 }
896971}
@@ -1120,13 +1195,15 @@ mod tests {
11201195 "\" really long text\" " ,
11211196 "module_name" ,
11221197 Range :: default ( ) ,
1198+ String :: new( ) ,
11231199 ) ,
11241200 Lint :: new(
11251201 "doc_markdown" ,
11261202 "pedantic" ,
11271203 "\" single line\" " ,
11281204 "module_name" ,
11291205 Range :: default ( ) ,
1206+ String :: new( ) ,
11301207 ) ,
11311208 ] ;
11321209 assert_eq ! ( expected, result) ;
@@ -1166,20 +1243,23 @@ mod tests {
11661243 "\" abc\" " ,
11671244 "module_name" ,
11681245 Range :: default ( ) ,
1246+ String :: new( ) ,
11691247 ) ,
11701248 Lint :: new(
11711249 "should_assert_eq2" ,
11721250 "internal" ,
11731251 "\" abc\" " ,
11741252 "module_name" ,
11751253 Range :: default ( ) ,
1254+ String :: new( ) ,
11761255 ) ,
11771256 Lint :: new(
11781257 "should_assert_eq2" ,
11791258 "internal_style" ,
11801259 "\" abc\" " ,
11811260 "module_name" ,
11821261 Range :: default ( ) ,
1262+ String :: new( ) ,
11831263 ) ,
11841264 ] ;
11851265 let expected = vec ! [ Lint :: new(
@@ -1188,29 +1268,59 @@ mod tests {
11881268 "\" abc\" " ,
11891269 "module_name" ,
11901270 Range :: default ( ) ,
1271+ String :: new( ) ,
11911272 ) ] ;
11921273 assert_eq ! ( expected, Lint :: usable_lints( & lints) ) ;
11931274 }
11941275
11951276 #[ test]
11961277 fn test_by_lint_group ( ) {
11971278 let lints = vec ! [
1198- Lint :: new( "should_assert_eq" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1279+ Lint :: new(
1280+ "should_assert_eq" ,
1281+ "group1" ,
1282+ "\" abc\" " ,
1283+ "module_name" ,
1284+ Range :: default ( ) ,
1285+ String :: new( ) ,
1286+ ) ,
11991287 Lint :: new(
12001288 "should_assert_eq2" ,
12011289 "group2" ,
12021290 "\" abc\" " ,
12031291 "module_name" ,
12041292 Range :: default ( ) ,
1293+ String :: new( ) ,
1294+ ) ,
1295+ Lint :: new(
1296+ "incorrect_match" ,
1297+ "group1" ,
1298+ "\" abc\" " ,
1299+ "module_name" ,
1300+ Range :: default ( ) ,
1301+ String :: new( ) ,
12051302 ) ,
1206- Lint :: new( "incorrect_match" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
12071303 ] ;
12081304 let mut expected: HashMap < String , Vec < Lint > > = HashMap :: new ( ) ;
12091305 expected. insert (
12101306 "group1" . to_string ( ) ,
12111307 vec ! [
1212- Lint :: new( "should_assert_eq" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1213- Lint :: new( "incorrect_match" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1308+ Lint :: new(
1309+ "should_assert_eq" ,
1310+ "group1" ,
1311+ "\" abc\" " ,
1312+ "module_name" ,
1313+ Range :: default ( ) ,
1314+ String :: new( ) ,
1315+ ) ,
1316+ Lint :: new(
1317+ "incorrect_match" ,
1318+ "group1" ,
1319+ "\" abc\" " ,
1320+ "module_name" ,
1321+ Range :: default ( ) ,
1322+ String :: new( ) ,
1323+ ) ,
12141324 ] ,
12151325 ) ;
12161326 expected. insert (
@@ -1221,6 +1331,7 @@ mod tests {
12211331 "\" abc\" " ,
12221332 "module_name" ,
12231333 Range :: default ( ) ,
1334+ String :: new( ) ,
12241335 ) ] ,
12251336 ) ;
12261337 assert_eq ! ( expected, Lint :: by_lint_group( lints. into_iter( ) ) ) ;
@@ -1259,9 +1370,30 @@ mod tests {
12591370 #[ test]
12601371 fn test_gen_lint_group_list ( ) {
12611372 let lints = vec ! [
1262- Lint :: new( "abc" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1263- Lint :: new( "should_assert_eq" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1264- Lint :: new( "internal" , "internal_style" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1373+ Lint :: new(
1374+ "abc" ,
1375+ "group1" ,
1376+ "\" abc\" " ,
1377+ "module_name" ,
1378+ Range :: default ( ) ,
1379+ String :: new( ) ,
1380+ ) ,
1381+ Lint :: new(
1382+ "should_assert_eq" ,
1383+ "group1" ,
1384+ "\" abc\" " ,
1385+ "module_name" ,
1386+ Range :: default ( ) ,
1387+ String :: new( ) ,
1388+ ) ,
1389+ Lint :: new(
1390+ "internal" ,
1391+ "internal_style" ,
1392+ "\" abc\" " ,
1393+ "module_name" ,
1394+ Range :: default ( ) ,
1395+ String :: new( ) ,
1396+ ) ,
12651397 ] ;
12661398 let expected = GENERATED_FILE_COMMENT . to_string ( )
12671399 + & [
0 commit comments