1
1
use std:: fs;
2
2
use std:: path:: Path ;
3
3
4
- use itertools:: EitherOrBoth ;
4
+ use itertools:: { EitherOrBoth , Itertools } ;
5
5
use serde:: { Deserialize , Serialize } ;
6
6
7
7
use crate :: ClippyWarning ;
8
8
9
- #[ derive( Deserialize , Serialize ) ]
9
+ /// This is the total number. 300 warnings results in 100 messages per section.
10
+ const DEFAULT_LIMIT_PER_LINT : usize = 300 ;
11
+ const TRUNCATION_TOTAL_TARGET : usize = 1000 ;
12
+
13
+ #[ derive( Debug , Deserialize , Serialize ) ]
10
14
struct LintJson {
11
15
lint : String ,
12
16
krate : String ,
@@ -18,7 +22,7 @@ struct LintJson {
18
22
19
23
impl LintJson {
20
24
fn key ( & self ) -> impl Ord + ' _ {
21
- ( self . file_name . as_str ( ) , self . byte_pos , self . lint . as_str ( ) )
25
+ ( self . lint . as_str ( ) , self . file_name . as_str ( ) , self . byte_pos )
22
26
}
23
27
24
28
fn info_text ( & self , action : & str ) -> String {
@@ -52,14 +56,117 @@ fn load_warnings(path: &Path) -> Vec<LintJson> {
52
56
serde_json:: from_slice ( & file) . unwrap_or_else ( |e| panic ! ( "failed to deserialize {}: {e}" , path. display( ) ) )
53
57
}
54
58
55
- fn print_warnings ( title : & str , warnings : & [ LintJson ] ) {
59
+ pub ( crate ) fn diff ( old_path : & Path , new_path : & Path , truncate : bool ) {
60
+ let old_warnings = load_warnings ( old_path) ;
61
+ let new_warnings = load_warnings ( new_path) ;
62
+
63
+ let mut lint_warnings = vec ! [ ] ;
64
+
65
+ for ( name, changes) in & itertools:: merge_join_by ( old_warnings, new_warnings, |old, new| old. key ( ) . cmp ( & new. key ( ) ) )
66
+ . chunk_by ( |change| change. as_ref ( ) . into_left ( ) . lint . to_string ( ) )
67
+ {
68
+ let mut added = Vec :: new ( ) ;
69
+ let mut removed = Vec :: new ( ) ;
70
+ let mut changed = Vec :: new ( ) ;
71
+ for change in changes {
72
+ match change {
73
+ EitherOrBoth :: Both ( old, new) => {
74
+ if old. rendered != new. rendered {
75
+ changed. push ( ( old, new) ) ;
76
+ }
77
+ } ,
78
+ EitherOrBoth :: Left ( old) => removed. push ( old) ,
79
+ EitherOrBoth :: Right ( new) => added. push ( new) ,
80
+ }
81
+ }
82
+
83
+ if !added. is_empty ( ) || !removed. is_empty ( ) || !changed. is_empty ( ) {
84
+ lint_warnings. push ( LintWarnings {
85
+ name,
86
+ added,
87
+ removed,
88
+ changed,
89
+ } ) ;
90
+ }
91
+ }
92
+
93
+ print_summary_table ( & lint_warnings) ;
94
+ println ! ( ) ;
95
+
96
+ if lint_warnings. is_empty ( ) {
97
+ return ;
98
+ }
99
+
100
+ let truncate_after = if truncate {
101
+ // Max 15 ensures that we at least have five messages per lint
102
+ DEFAULT_LIMIT_PER_LINT
103
+ . min ( TRUNCATION_TOTAL_TARGET / lint_warnings. len ( ) )
104
+ . max ( 15 )
105
+ } else {
106
+ // No lint should ever each this number of lint emissions, so this is equivialent to
107
+ // No truncation
108
+ usize:: MAX
109
+ } ;
110
+
111
+ for lint in lint_warnings {
112
+ print_lint_warnings ( & lint, truncate_after) ;
113
+ }
114
+ }
115
+
116
+ #[ derive( Debug ) ]
117
+ struct LintWarnings {
118
+ name : String ,
119
+ added : Vec < LintJson > ,
120
+ removed : Vec < LintJson > ,
121
+ changed : Vec < ( LintJson , LintJson ) > ,
122
+ }
123
+
124
+ fn print_lint_warnings ( lint : & LintWarnings , truncate_after : usize ) {
125
+ let name = & lint. name ;
126
+ let html_id = to_html_id ( name) ;
127
+
128
+ // The additional anchor is added for non GH viewers that don't prefix ID's
129
+ println ! ( r#"## `{name}` <a id="user-content-{html_id}"></a>"# ) ;
130
+ println ! ( ) ;
131
+
132
+ print ! (
133
+ r##"{}, {}, {}"## ,
134
+ count_string( name, "added" , lint. added. len( ) ) ,
135
+ count_string( name, "removed" , lint. removed. len( ) ) ,
136
+ count_string( name, "changed" , lint. changed. len( ) ) ,
137
+ ) ;
138
+ println ! ( ) ;
139
+
140
+ print_warnings ( "Added" , & lint. added , truncate_after / 3 ) ;
141
+ print_warnings ( "Removed" , & lint. removed , truncate_after / 3 ) ;
142
+ print_changed_diff ( & lint. changed , truncate_after / 3 ) ;
143
+ }
144
+
145
+ fn print_summary_table ( lints : & [ LintWarnings ] ) {
146
+ println ! ( "| Lint | Added | Removed | Changed |" ) ;
147
+ println ! ( "| ------------------------------------------ | ------: | ------: | ------: |" ) ;
148
+
149
+ for lint in lints {
150
+ println ! (
151
+ "| {:<62} | {:>7} | {:>7} | {:>7} |" ,
152
+ format!( "[`{}`](#user-content-{})" , lint. name, to_html_id( & lint. name) ) ,
153
+ lint. added. len( ) ,
154
+ lint. removed. len( ) ,
155
+ lint. changed. len( )
156
+ ) ;
157
+ }
158
+ }
159
+
160
+ fn print_warnings ( title : & str , warnings : & [ LintJson ] , truncate_after : usize ) {
56
161
if warnings. is_empty ( ) {
57
162
return ;
58
163
}
59
164
60
- //We have to use HTML here to be able to manually add an id.
61
- println ! ( r#"<h3 id="{title}">{title}</h3>"# ) ;
165
+ print_h3 ( & warnings[ 0 ] . lint , title) ;
62
166
println ! ( ) ;
167
+
168
+ let warnings = truncate ( warnings, truncate_after) ;
169
+
63
170
for warning in warnings {
64
171
println ! ( "{}" , warning. info_text( title) ) ;
65
172
println ! ( ) ;
@@ -70,14 +177,16 @@ fn print_warnings(title: &str, warnings: &[LintJson]) {
70
177
}
71
178
}
72
179
73
- fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] ) {
180
+ fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] , truncate_after : usize ) {
74
181
if changed. is_empty ( ) {
75
182
return ;
76
183
}
77
184
78
- //We have to use HTML here to be able to manually add an id.
79
- println ! ( r#"<h3 id="changed">Changed</h3>"# ) ;
185
+ print_h3 ( & changed[ 0 ] . 0 . lint , "Changed" ) ;
80
186
println ! ( ) ;
187
+
188
+ let changed = truncate ( changed, truncate_after) ;
189
+
81
190
for ( old, new) in changed {
82
191
println ! ( "{}" , new. info_text( "Changed" ) ) ;
83
192
println ! ( ) ;
@@ -101,51 +210,44 @@ fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
101
210
}
102
211
}
103
212
104
- pub ( crate ) fn diff ( old_path : & Path , new_path : & Path ) {
105
- let old_warnings = load_warnings ( old_path) ;
106
- let new_warnings = load_warnings ( new_path) ;
213
+ fn truncate < T > ( list : & [ T ] , truncate_after : usize ) -> & [ T ] {
214
+ if list. len ( ) > truncate_after {
215
+ println ! (
216
+ "{} warnings have been truncated for this summary." ,
217
+ list. len( ) - truncate_after
218
+ ) ;
219
+ println ! ( ) ;
107
220
108
- let mut added = Vec :: new ( ) ;
109
- let mut removed = Vec :: new ( ) ;
110
- let mut changed = Vec :: new ( ) ;
111
-
112
- for change in itertools:: merge_join_by ( old_warnings, new_warnings, |old, new| old. key ( ) . cmp ( & new. key ( ) ) ) {
113
- match change {
114
- EitherOrBoth :: Both ( old, new) => {
115
- if old. rendered != new. rendered {
116
- changed. push ( ( old, new) ) ;
117
- }
118
- } ,
119
- EitherOrBoth :: Left ( old) => removed. push ( old) ,
120
- EitherOrBoth :: Right ( new) => added. push ( new) ,
121
- }
221
+ list. split_at ( truncate_after) . 0
222
+ } else {
223
+ list
122
224
}
225
+ }
123
226
124
- print ! (
125
- r##"{}, {}, {}"## ,
126
- count_string( "added" , added. len( ) ) ,
127
- count_string( "removed" , removed. len( ) ) ,
128
- count_string( "changed" , changed. len( ) ) ,
129
- ) ;
130
- println ! ( ) ;
131
- println ! ( ) ;
227
+ fn print_h3 ( lint : & str , title : & str ) {
228
+ let html_id = to_html_id ( lint) ;
229
+ // We have to use HTML here to be able to manually add an id.
230
+ println ! ( r#"### {title} <a id="user-content-{html_id}-{title}"></a>"# ) ;
231
+ }
132
232
133
- print_warnings ( "Added" , & added) ;
134
- print_warnings ( "Removed" , & removed) ;
135
- print_changed_diff ( & changed) ;
233
+ /// GitHub's markdown parsers doesn't like IDs with `::` and `_`. This simplifies
234
+ /// the lint name for the HTML ID.
235
+ fn to_html_id ( lint_name : & str ) -> String {
236
+ lint_name. replace ( "clippy::" , "" ) . replace ( '_' , "-" )
136
237
}
137
238
138
239
/// This generates the `x added` string for the start of the job summery.
139
240
/// It linkifies them if possible to jump to the respective heading.
140
- fn count_string ( label : & str , count : usize ) -> String {
241
+ fn count_string ( lint : & str , label : & str , count : usize ) -> String {
141
242
// Headlines are only added, if anything will be displayed under the headline.
142
243
// We therefore only want to add links to them if they exist
143
244
if count == 0 {
144
245
format ! ( "0 {label}" )
145
246
} else {
247
+ let html_id = to_html_id ( lint) ;
146
248
// GitHub's job summaries don't add HTML ids to headings. That's why we
147
249
// manually have to add them. GitHub prefixes these manual ids with
148
250
// `user-content-` and that's how we end up with these awesome links :D
149
- format ! ( "[{count} {label}](#user-content-{label})" )
251
+ format ! ( "[{count} {label}](#user-content-{html_id}-{ label})" )
150
252
}
151
253
}
0 commit comments