1- use std:: collections:: HashMap ;
2- use std:: fmt:: Write ;
31use std:: fs;
4- use std:: hash:: Hash ;
52use std:: path:: Path ;
63
4+ use itertools:: EitherOrBoth ;
5+ use serde:: { Deserialize , Serialize } ;
6+
77use crate :: ClippyWarning ;
88
9- /// Creates the log file output for [`crate::config::OutputFormat::Json`]
10- pub ( crate ) fn output ( clippy_warnings : & [ ClippyWarning ] ) -> String {
11- serde_json:: to_string ( & clippy_warnings) . unwrap ( )
9+ #[ derive( Deserialize , Serialize ) ]
10+ struct LintJson {
11+ lint : String ,
12+ file_name : String ,
13+ byte_pos : ( u32 , u32 ) ,
14+ rendered : String ,
1215}
1316
14- fn load_warnings ( path : & Path ) -> Vec < ClippyWarning > {
15- let file = fs :: read ( path ) . unwrap_or_else ( |e| panic ! ( "failed to read {}: {e}" , path . display ( ) ) ) ;
16-
17- serde_json :: from_slice ( & file ) . unwrap_or_else ( |e| panic ! ( "failed to deserialize {}: {e}" , path . display ( ) ) )
17+ impl LintJson {
18+ fn key ( & self ) -> impl Ord + ' _ {
19+ ( self . file_name . as_str ( ) , self . byte_pos , self . lint . as_str ( ) )
20+ }
1821}
1922
20- /// Group warnings by their primary span location + lint name
21- fn create_map ( warnings : & [ ClippyWarning ] ) -> HashMap < impl Eq + Hash + ' _ , Vec < & ClippyWarning > > {
22- let mut map = HashMap :: < _ , Vec < _ > > :: with_capacity ( warnings. len ( ) ) ;
23-
24- for warning in warnings {
25- let span = warning. span ( ) ;
26- let key = ( & warning. lint_type , & span. file_name , span. byte_start , span. byte_end ) ;
23+ /// Creates the log file output for [`crate::config::OutputFormat::Json`]
24+ pub ( crate ) fn output ( clippy_warnings : Vec < ClippyWarning > ) -> String {
25+ let mut lints: Vec < LintJson > = clippy_warnings
26+ . into_iter ( )
27+ . map ( |warning| {
28+ let span = warning. span ( ) ;
29+ LintJson {
30+ file_name : span. file_name . clone ( ) ,
31+ byte_pos : ( span. byte_start , span. byte_end ) ,
32+ lint : warning. lint ,
33+ rendered : warning. diag . rendered . unwrap ( ) ,
34+ }
35+ } )
36+ . collect ( ) ;
37+ lints. sort_by ( |a, b| a. key ( ) . cmp ( & b. key ( ) ) ) ;
38+ serde_json:: to_string ( & lints) . unwrap ( )
39+ }
2740
28- map . entry ( key ) . or_default ( ) . push ( warning ) ;
29- }
41+ fn load_warnings ( path : & Path ) -> Vec < LintJson > {
42+ let file = fs :: read ( path ) . unwrap_or_else ( |e| panic ! ( "failed to read {}: {e}" , path . display ( ) ) ) ;
3043
31- map
44+ serde_json :: from_slice ( & file ) . unwrap_or_else ( |e| panic ! ( "failed to deserialize {}: {e}" , path . display ( ) ) )
3245}
3346
34- fn print_warnings ( title : & str , warnings : & [ & ClippyWarning ] ) {
47+ fn print_warnings ( title : & str , warnings : & [ LintJson ] ) {
3548 if warnings. is_empty ( ) {
3649 return ;
3750 }
3851
3952 println ! ( "### {title}" ) ;
4053 println ! ( "```" ) ;
4154 for warning in warnings {
42- print ! ( "{}" , warning. diag ) ;
55+ print ! ( "{}" , warning. rendered ) ;
4356 }
4457 println ! ( "```" ) ;
4558}
4659
47- fn print_changed_diff ( changed : & [ ( & [ & ClippyWarning ] , & [ & ClippyWarning ] ) ] ) {
48- fn render ( warnings : & [ & ClippyWarning ] ) -> String {
49- let mut rendered = String :: new ( ) ;
50- for warning in warnings {
51- write ! ( & mut rendered, "{}" , warning. diag) . unwrap ( ) ;
52- }
53- rendered
54- }
55-
60+ fn print_changed_diff ( changed : & [ ( LintJson , LintJson ) ] ) {
5661 if changed. is_empty ( ) {
5762 return ;
5863 }
5964
6065 println ! ( "### Changed" ) ;
6166 println ! ( "```diff" ) ;
62- for & ( old, new) in changed {
63- let old_rendered = render ( old) ;
64- let new_rendered = render ( new) ;
65-
66- for change in diff:: lines ( & old_rendered, & new_rendered) {
67+ for ( old, new) in changed {
68+ for change in diff:: lines ( & old. rendered , & new. rendered ) {
6769 use diff:: Result :: { Both , Left , Right } ;
6870
6971 match change {
@@ -86,26 +88,19 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path) {
8688 let old_warnings = load_warnings ( old_path) ;
8789 let new_warnings = load_warnings ( new_path) ;
8890
89- let old_map = create_map ( & old_warnings) ;
90- let new_map = create_map ( & new_warnings) ;
91-
9291 let mut added = Vec :: new ( ) ;
9392 let mut removed = Vec :: new ( ) ;
9493 let mut changed = Vec :: new ( ) ;
9594
96- for ( key, new) in & new_map {
97- if let Some ( old) = old_map. get ( key) {
98- if old != new {
99- changed. push ( ( old. as_slice ( ) , new. as_slice ( ) ) ) ;
100- }
101- } else {
102- added. extend ( new) ;
103- }
104- }
105-
106- for ( key, old) in & old_map {
107- if !new_map. contains_key ( key) {
108- removed. extend ( old) ;
95+ for change in itertools:: merge_join_by ( old_warnings, new_warnings, |old, new| old. key ( ) . cmp ( & new. key ( ) ) ) {
96+ match change {
97+ EitherOrBoth :: Both ( old, new) => {
98+ if old. rendered != new. rendered {
99+ changed. push ( ( old, new) ) ;
100+ }
101+ } ,
102+ EitherOrBoth :: Left ( old) => removed. push ( old) ,
103+ EitherOrBoth :: Right ( new) => added. push ( new) ,
109104 }
110105 }
111106
0 commit comments