1
- use std:: collections:: HashMap ;
2
- use std:: fmt:: Write ;
3
1
use std:: fs;
4
- use std:: hash:: Hash ;
5
2
use std:: path:: Path ;
6
3
4
+ use itertools:: EitherOrBoth ;
5
+ use serde:: { Deserialize , Serialize } ;
6
+
7
7
use crate :: ClippyWarning ;
8
8
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 ,
12
15
}
13
16
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
+ }
18
21
}
19
22
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
+ }
27
40
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 ( ) ) ) ;
30
43
31
- map
44
+ serde_json :: from_slice ( & file ) . unwrap_or_else ( |e| panic ! ( "failed to deserialize {}: {e}" , path . display ( ) ) )
32
45
}
33
46
34
- fn print_warnings ( title : & str , warnings : & [ & ClippyWarning ] ) {
47
+ fn print_warnings ( title : & str , warnings : & [ LintJson ] ) {
35
48
if warnings. is_empty ( ) {
36
49
return ;
37
50
}
38
51
39
52
println ! ( "### {title}" ) ;
40
53
println ! ( "```" ) ;
41
54
for warning in warnings {
42
- print ! ( "{}" , warning. diag ) ;
55
+ print ! ( "{}" , warning. rendered ) ;
43
56
}
44
57
println ! ( "```" ) ;
45
58
}
46
59
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 ) ] ) {
56
61
if changed. is_empty ( ) {
57
62
return ;
58
63
}
59
64
60
65
println ! ( "### Changed" ) ;
61
66
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 ) {
67
69
use diff:: Result :: { Both , Left , Right } ;
68
70
69
71
match change {
@@ -86,26 +88,19 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path) {
86
88
let old_warnings = load_warnings ( old_path) ;
87
89
let new_warnings = load_warnings ( new_path) ;
88
90
89
- let old_map = create_map ( & old_warnings) ;
90
- let new_map = create_map ( & new_warnings) ;
91
-
92
91
let mut added = Vec :: new ( ) ;
93
92
let mut removed = Vec :: new ( ) ;
94
93
let mut changed = Vec :: new ( ) ;
95
94
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) ,
109
104
}
110
105
}
111
106
0 commit comments