@@ -3,7 +3,7 @@ use aho_corasick::AhoCorasickBuilder;
3
3
use indoc:: writedoc;
4
4
use itertools:: Itertools ;
5
5
use rustc_lexer:: { tokenize, unescape, LiteralKind , TokenKind } ;
6
- use std:: collections:: { HashMap , HashSet } ;
6
+ use std:: collections:: { BTreeSet , HashMap , HashSet } ;
7
7
use std:: ffi:: OsStr ;
8
8
use std:: fmt:: Write ;
9
9
use std:: fs:: { self , OpenOptions } ;
@@ -124,6 +124,8 @@ fn generate_lint_files(
124
124
let content = gen_lint_group_list ( "all" , all_group_lints) ;
125
125
process_file ( "clippy_lints/src/lib.register_all.rs" , update_mode, & content) ;
126
126
127
+ update_docs ( update_mode, & usable_lints) ;
128
+
127
129
for ( lint_group, lints) in Lint :: by_lint_group ( usable_lints. into_iter ( ) . chain ( internal_lints) ) {
128
130
let content = gen_lint_group_list ( & lint_group, lints. iter ( ) ) ;
129
131
process_file (
@@ -140,6 +142,62 @@ fn generate_lint_files(
140
142
process_file ( "tests/ui/rename.rs" , update_mode, & content) ;
141
143
}
142
144
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
+
143
201
pub fn print_lints ( ) {
144
202
let ( lint_list, _, _) = gather_all ( ) ;
145
203
let usable_lints = Lint :: usable_lints ( & lint_list) ;
@@ -589,17 +647,26 @@ struct Lint {
589
647
desc : String ,
590
648
module : String ,
591
649
declaration_range : Range < usize > ,
650
+ documentation : String ,
592
651
}
593
652
594
653
impl Lint {
595
654
#[ 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 {
597
663
Self {
598
664
name : name. to_lowercase ( ) ,
599
665
group : group. into ( ) ,
600
666
desc : remove_line_splices ( desc) ,
601
667
module : module. into ( ) ,
602
668
declaration_range,
669
+ documentation,
603
670
}
604
671
}
605
672
@@ -852,27 +919,35 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
852
919
} | token_kind == & TokenKind :: Ident && * content == "declare_clippy_lint" ,
853
920
) {
854
921
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 ) ) ;
859
924
// matches `!{`
860
925
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
+ }
875
949
}
950
+ docs. pop ( ) ; // remove final newline
876
951
877
952
let ( name, group, desc) = match_tokens ! (
878
953
iter,
@@ -890,7 +965,7 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
890
965
..
891
966
} ) = iter. next ( )
892
967
{
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 ) ) ;
894
969
}
895
970
}
896
971
}
@@ -977,7 +1052,11 @@ fn remove_line_splices(s: &str) -> String {
977
1052
. and_then ( |s| s. strip_suffix ( '"' ) )
978
1053
. unwrap_or_else ( || panic ! ( "expected quoted string, found `{}`" , s) ) ;
979
1054
let mut res = String :: with_capacity ( s. len ( ) ) ;
980
- unescape:: unescape_literal ( s, unescape:: Mode :: Str , & mut |range, _| res. push_str ( & s[ range] ) ) ;
1055
+ unescape:: unescape_literal ( s, unescape:: Mode :: Str , & mut |range, ch| {
1056
+ if ch. is_ok ( ) {
1057
+ res. push_str ( & s[ range] ) ;
1058
+ }
1059
+ } ) ;
981
1060
res
982
1061
}
983
1062
@@ -1116,13 +1195,15 @@ mod tests {
1116
1195
"\" really long text\" " ,
1117
1196
"module_name" ,
1118
1197
Range :: default ( ) ,
1198
+ String :: new( ) ,
1119
1199
) ,
1120
1200
Lint :: new(
1121
1201
"doc_markdown" ,
1122
1202
"pedantic" ,
1123
1203
"\" single line\" " ,
1124
1204
"module_name" ,
1125
1205
Range :: default ( ) ,
1206
+ String :: new( ) ,
1126
1207
) ,
1127
1208
] ;
1128
1209
assert_eq ! ( expected, result) ;
@@ -1162,20 +1243,23 @@ mod tests {
1162
1243
"\" abc\" " ,
1163
1244
"module_name" ,
1164
1245
Range :: default ( ) ,
1246
+ String :: new( ) ,
1165
1247
) ,
1166
1248
Lint :: new(
1167
1249
"should_assert_eq2" ,
1168
1250
"internal" ,
1169
1251
"\" abc\" " ,
1170
1252
"module_name" ,
1171
1253
Range :: default ( ) ,
1254
+ String :: new( ) ,
1172
1255
) ,
1173
1256
Lint :: new(
1174
1257
"should_assert_eq2" ,
1175
1258
"internal_style" ,
1176
1259
"\" abc\" " ,
1177
1260
"module_name" ,
1178
1261
Range :: default ( ) ,
1262
+ String :: new( ) ,
1179
1263
) ,
1180
1264
] ;
1181
1265
let expected = vec ! [ Lint :: new(
@@ -1184,29 +1268,59 @@ mod tests {
1184
1268
"\" abc\" " ,
1185
1269
"module_name" ,
1186
1270
Range :: default ( ) ,
1271
+ String :: new( ) ,
1187
1272
) ] ;
1188
1273
assert_eq ! ( expected, Lint :: usable_lints( & lints) ) ;
1189
1274
}
1190
1275
1191
1276
#[ test]
1192
1277
fn test_by_lint_group ( ) {
1193
1278
let lints = vec ! [
1194
- 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
+ ) ,
1195
1287
Lint :: new(
1196
1288
"should_assert_eq2" ,
1197
1289
"group2" ,
1198
1290
"\" abc\" " ,
1199
1291
"module_name" ,
1200
1292
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( ) ,
1201
1302
) ,
1202
- Lint :: new( "incorrect_match" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1203
1303
] ;
1204
1304
let mut expected: HashMap < String , Vec < Lint > > = HashMap :: new ( ) ;
1205
1305
expected. insert (
1206
1306
"group1" . to_string ( ) ,
1207
1307
vec ! [
1208
- Lint :: new( "should_assert_eq" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1209
- 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
+ ) ,
1210
1324
] ,
1211
1325
) ;
1212
1326
expected. insert (
@@ -1217,6 +1331,7 @@ mod tests {
1217
1331
"\" abc\" " ,
1218
1332
"module_name" ,
1219
1333
Range :: default ( ) ,
1334
+ String :: new( ) ,
1220
1335
) ] ,
1221
1336
) ;
1222
1337
assert_eq ! ( expected, Lint :: by_lint_group( lints. into_iter( ) ) ) ;
@@ -1255,9 +1370,30 @@ mod tests {
1255
1370
#[ test]
1256
1371
fn test_gen_lint_group_list ( ) {
1257
1372
let lints = vec ! [
1258
- Lint :: new( "abc" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1259
- Lint :: new( "should_assert_eq" , "group1" , "\" abc\" " , "module_name" , Range :: default ( ) ) ,
1260
- 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
+ ) ,
1261
1397
] ;
1262
1398
let expected = GENERATED_FILE_COMMENT . to_string ( )
1263
1399
+ & [
0 commit comments