1
+ #![ expect( unused) ]
2
+
3
+ use std:: collections:: BTreeSet ;
4
+ use std:: fmt:: Write ;
1
5
use std:: fs;
2
6
use std:: path:: Path ;
3
7
@@ -6,7 +10,10 @@ use serde::{Deserialize, Serialize};
6
10
7
11
use crate :: ClippyWarning ;
8
12
9
- #[ derive( Deserialize , Serialize ) ]
13
+ const DEFAULT_LIMIT_PER_LINT : usize = 300 ;
14
+ const TRUNCATION_TOTAL_TARGET : usize = 1000 ;
15
+
16
+ #[ derive( Debug , Deserialize , Serialize ) ]
10
17
struct LintJson {
11
18
lint : String ,
12
19
krate : String ,
@@ -52,14 +59,16 @@ fn load_warnings(path: &Path) -> Vec<LintJson> {
52
59
serde_json:: from_slice ( & file) . unwrap_or_else ( |e| panic ! ( "failed to deserialize {}: {e}" , path. display( ) ) )
53
60
}
54
61
55
- fn print_warnings ( title : & str , warnings : & [ LintJson ] ) {
62
+ fn print_warnings ( title : & str , warnings : & [ LintJson ] , truncate_after : usize ) {
56
63
if warnings. is_empty ( ) {
57
64
return ;
58
65
}
59
66
60
- //We have to use HTML here to be able to manually add an id.
61
- println ! ( r#"<h3 id="{title}">{title}</h3>"# ) ;
67
+ print_h3 ( & warnings[ 0 ] . lint , title) ;
62
68
println ! ( ) ;
69
+
70
+ let warnings = truncate ( warnings, truncate_after) ;
71
+
63
72
for warning in warnings {
64
73
println ! ( "{}" , warning. info_text( title) ) ;
65
74
println ! ( ) ;
@@ -70,14 +79,16 @@ fn print_warnings(title: &str, warnings: &[LintJson]) {
70
79
}
71
80
}
72
81
73
- fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] ) {
82
+ fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] , truncate_after : usize ) {
74
83
if changed. is_empty ( ) {
75
84
return ;
76
85
}
77
86
78
- //We have to use HTML here to be able to manually add an id.
79
- println ! ( r#"<h3 id="changed">Changed</h3>"# ) ;
87
+ print_h3 ( & changed[ 0 ] . 0 . lint , "Changed" ) ;
80
88
println ! ( ) ;
89
+
90
+ let changes = truncate ( changed, truncate_after) ;
91
+
81
92
for ( old, new) in changed {
82
93
println ! ( "{}" , new. info_text( "Changed" ) ) ;
83
94
println ! ( ) ;
@@ -101,7 +112,25 @@ fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
101
112
}
102
113
}
103
114
104
- pub ( crate ) fn diff ( old_path : & Path , new_path : & Path ) {
115
+ fn print_h3 ( lint : & str , title : & str ) {
116
+ let html_id = to_html_id ( lint) ;
117
+ // We have to use HTML here to be able to manually add an id.
118
+ println ! ( r#"<h3 id="user-content-{html_id}-{title}">{title}</h3>"# ) ;
119
+ }
120
+
121
+ fn truncate < T > ( list : & [ T ] , truncate_after : usize ) -> & [ T ] {
122
+ if list. len ( ) > truncate_after {
123
+ println ! ( "{} warnings have been truncated for this summary." , list. len( ) - truncate_after) ;
124
+ println ! ( ) ;
125
+
126
+ list. split_at ( truncate_after) . 0
127
+ } else {
128
+ list
129
+ }
130
+
131
+ }
132
+
133
+ pub ( crate ) fn diff ( old_path : & Path , new_path : & Path , truncate : bool ) {
105
134
let old_warnings = load_warnings ( old_path) ;
106
135
let new_warnings = load_warnings ( new_path) ;
107
136
@@ -121,31 +150,141 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path) {
121
150
}
122
151
}
123
152
153
+ let lint_warnings = group_by_lint ( added, removed, changed) ;
154
+
155
+ print_summary_table ( & lint_warnings) ;
156
+ println ! ( ) ;
157
+
158
+ let truncate_after = if truncate {
159
+ // Max 9 ensures that we at least have three messages per lint
160
+ DEFAULT_LIMIT_PER_LINT . min ( TRUNCATION_TOTAL_TARGET / lint_warnings. len ( ) ) . max ( 9 )
161
+ } else {
162
+ // No lint should ever each this number of lint emissions, so this is equivialent to
163
+ // No truncation
164
+ usize:: MAX
165
+ } ;
166
+
167
+ for lint in lint_warnings {
168
+ print_lint_warnings ( & lint, truncate_after)
169
+ }
170
+ }
171
+
172
+ fn print_lint_warnings ( lint : & LintWarnings , truncate_after : usize ) {
173
+ let name = & lint. name ;
174
+ let html_id = to_html_id ( name) ;
175
+
176
+ // The additional anchor is added for non GH viewers that don't prefix ID's
177
+ println ! ( r#"<h2 id="user-content-{html_id}">{name}</h2>"# ) ;
178
+ println ! ( ) ;
179
+
124
180
print ! (
125
181
r##"{}, {}, {}"## ,
126
- count_string( "added" , added. len( ) ) ,
127
- count_string( "removed" , removed. len( ) ) ,
128
- count_string( "changed" , changed. len( ) ) ,
182
+ count_string( name , "added" , lint . added. len( ) ) ,
183
+ count_string( name , "removed" , lint . removed. len( ) ) ,
184
+ count_string( name , "changed" , lint . changed. len( ) ) ,
129
185
) ;
130
186
println ! ( ) ;
131
- println ! ( ) ;
132
187
133
- print_warnings ( "Added" , & added) ;
134
- print_warnings ( "Removed" , & removed) ;
135
- print_changed_diff ( & changed) ;
188
+ print_warnings ( "Added" , & lint. added , truncate_after / 3 ) ;
189
+ print_warnings ( "Removed" , & lint. removed , truncate_after / 3 ) ;
190
+ print_changed_diff ( & lint. changed , truncate_after / 3 ) ;
191
+ }
192
+
193
+ #[ derive( Debug ) ]
194
+ struct LintWarnings {
195
+ name : String ,
196
+ added : Vec < LintJson > ,
197
+ removed : Vec < LintJson > ,
198
+ changed : Vec < ( LintJson , LintJson ) > ,
199
+ }
200
+
201
+ fn group_by_lint (
202
+ mut added : Vec < LintJson > ,
203
+ mut removed : Vec < LintJson > ,
204
+ mut changed : Vec < ( LintJson , LintJson ) > ,
205
+ ) -> Vec < LintWarnings > {
206
+ /// Collects items from an iterator while the condition is met
207
+ fn collect_while < T , F > ( iter : & mut std:: iter:: Peekable < impl Iterator < Item = T > > , mut condition : F ) -> Vec < T >
208
+ where
209
+ F : FnMut ( & T ) -> bool ,
210
+ {
211
+ let mut items = vec ! [ ] ;
212
+ while iter. peek ( ) . map_or ( false , |item| condition ( item) ) {
213
+ items. push ( iter. next ( ) . unwrap ( ) ) ;
214
+ }
215
+ items
216
+ }
217
+
218
+ // Sort
219
+ added. sort_unstable_by ( |a, b| a. lint . cmp ( & b. lint ) ) ;
220
+ removed. sort_unstable_by ( |a, b| a. lint . cmp ( & b. lint ) ) ;
221
+ changed. sort_unstable_by ( |( a, _) , ( b, _) | a. lint . cmp ( & b. lint ) ) ;
222
+
223
+ // Collect lint names
224
+ let lint_names: BTreeSet < _ > = added
225
+ . iter ( )
226
+ . chain ( removed. iter ( ) )
227
+ . chain ( changed. iter ( ) . map ( |( a, _) | a) )
228
+ . map ( |warning| & warning. lint )
229
+ . cloned ( )
230
+ . collect ( ) ;
231
+
232
+ let mut added_iter = added. into_iter ( ) . peekable ( ) ;
233
+ let mut removed_iter = removed. into_iter ( ) . peekable ( ) ;
234
+ let mut changed_iter = changed. into_iter ( ) . peekable ( ) ;
235
+
236
+ let mut lints = vec ! [ ] ;
237
+ for name in lint_names. into_iter ( ) {
238
+ lints. push ( LintWarnings {
239
+ added : collect_while ( & mut added_iter, |warning| warning. lint == name) ,
240
+ removed : collect_while ( & mut removed_iter, |warning| warning. lint == name) ,
241
+ changed : collect_while ( & mut changed_iter, |( warning, _) | warning. lint == name) ,
242
+ name,
243
+ } ) ;
244
+ }
245
+
246
+ lints
247
+ }
248
+
249
+ // This function limits the number of lints in the collection if needed.
250
+ //
251
+ // It's honestly amazing that a function like this is needed. But some restriction
252
+ // lints (Looking at you `implicit_return` and `if_then_some_else_none`) trigger a lot
253
+ // like 60'000+ times in our CI.
254
+ //
255
+ // Each lint in the list will be limited to 200 messages
256
+
257
+ fn print_summary_table ( lints : & [ LintWarnings ] ) {
258
+ println ! ( "| Lint | Added | Removed | Changed |" ) ;
259
+ println ! ( "| ------------------------------------------ | ------: | ------: | ------: |" ) ;
260
+
261
+ for lint in lints {
262
+ println ! (
263
+ "| {:<62} | {:>7} | {:>7} | {:>7} |" ,
264
+ format!( "[`{}`](#user-content-{})" , lint. name, to_html_id( & lint. name) ) ,
265
+ lint. added. len( ) ,
266
+ lint. removed. len( ) ,
267
+ lint. changed. len( )
268
+ ) ;
269
+ }
270
+ }
271
+
272
+ fn to_html_id ( lint_name : & str ) -> String {
273
+ lint_name. replace ( "clippy::" , "" ) . replace ( "_" , "-" )
136
274
}
137
275
138
276
/// This generates the `x added` string for the start of the job summery.
139
277
/// It linkifies them if possible to jump to the respective heading.
140
- fn count_string ( label : & str , count : usize ) -> String {
278
+ fn count_string ( lint : & str , label : & str , count : usize ) -> String {
141
279
// Headlines are only added, if anything will be displayed under the headline.
142
280
// We therefore only want to add links to them if they exist
143
281
if count == 0 {
144
282
format ! ( "0 {label}" )
145
283
} else {
284
+ let lint_id = to_html_id ( lint) ;
146
285
// GitHub's job summaries don't add HTML ids to headings. That's why we
147
286
// manually have to add them. GitHub prefixes these manual ids with
148
287
// `user-content-` and that's how we end up with these awesome links :D
149
- format ! ( "[{count} {label}](#user-content-{label})" )
288
+ format ! ( "[{count} {label}](#user-content-{lint_id}-{ label})" )
150
289
}
151
290
}
0 commit comments