11use std:: fs;
22use std:: path:: Path ;
33
4- use itertools:: EitherOrBoth ;
4+ use itertools:: { EitherOrBoth , Itertools } ;
55use serde:: { Deserialize , Serialize } ;
66
77use crate :: ClippyWarning ;
88
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 ) ]
1014struct LintJson {
1115 lint : String ,
1216 krate : String ,
@@ -18,7 +22,7 @@ struct LintJson {
1822
1923impl LintJson {
2024 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 )
2226 }
2327
2428 fn info_text ( & self , action : & str ) -> String {
@@ -52,14 +56,117 @@ fn load_warnings(path: &Path) -> Vec<LintJson> {
5256 serde_json:: from_slice ( & file) . unwrap_or_else ( |e| panic ! ( "failed to deserialize {}: {e}" , path. display( ) ) )
5357}
5458
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 ) {
56161 if warnings. is_empty ( ) {
57162 return ;
58163 }
59164
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) ;
62166 println ! ( ) ;
167+
168+ let warnings = truncate ( warnings, truncate_after) ;
169+
63170 for warning in warnings {
64171 println ! ( "{}" , warning. info_text( title) ) ;
65172 println ! ( ) ;
@@ -70,14 +177,16 @@ fn print_warnings(title: &str, warnings: &[LintJson]) {
70177 }
71178}
72179
73- fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] ) {
180+ fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] , truncate_after : usize ) {
74181 if changed. is_empty ( ) {
75182 return ;
76183 }
77184
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" ) ;
80186 println ! ( ) ;
187+
188+ let changed = truncate ( changed, truncate_after) ;
189+
81190 for ( old, new) in changed {
82191 println ! ( "{}" , new. info_text( "Changed" ) ) ;
83192 println ! ( ) ;
@@ -101,51 +210,44 @@ fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
101210 }
102211}
103212
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 ! ( ) ;
107220
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
122224 }
225+ }
123226
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+ }
132232
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 ( '_' , "-" )
136237}
137238
138239/// This generates the `x added` string for the start of the job summery.
139240/// 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 {
141242 // Headlines are only added, if anything will be displayed under the headline.
142243 // We therefore only want to add links to them if they exist
143244 if count == 0 {
144245 format ! ( "0 {label}" )
145246 } else {
247+ let html_id = to_html_id ( lint) ;
146248 // GitHub's job summaries don't add HTML ids to headings. That's why we
147249 // manually have to add them. GitHub prefixes these manual ids with
148250 // `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})" )
150252 }
151253}
0 commit comments