4
4
//! loading warnings from JSON files, and generating human-readable diffs
5
5
//! between different linting runs.
6
6
7
- use std:: fs ;
8
- use std:: path :: Path ;
7
+ use std:: path :: { Path , PathBuf } ;
8
+ use std:: { fmt , fs } ;
9
9
10
10
use itertools:: { EitherOrBoth , Itertools } ;
11
11
use serde:: { Deserialize , Serialize } ;
@@ -17,7 +17,6 @@ const DEFAULT_LIMIT_PER_LINT: usize = 300;
17
17
/// Target for total warnings to display across all lints when truncating output.
18
18
const TRUNCATION_TOTAL_TARGET : usize = 1000 ;
19
19
20
- /// Representation of a single Clippy warning for JSON serialization.
21
20
#[ derive( Debug , Deserialize , Serialize ) ]
22
21
struct LintJson {
23
22
/// The lint name e.g. `clippy::bytes_nth`
@@ -29,7 +28,6 @@ struct LintJson {
29
28
}
30
29
31
30
impl LintJson {
32
- /// Returns a tuple of name and `file_line` for sorting and comparison.
33
31
fn key ( & self ) -> impl Ord + ' _ {
34
32
( self . name . as_str ( ) , self . file_line . as_str ( ) )
35
33
}
@@ -40,6 +38,57 @@ impl LintJson {
40
38
}
41
39
}
42
40
41
+ #[ derive( Debug , Serialize ) ]
42
+ struct SummaryRow {
43
+ name : String ,
44
+ added : usize ,
45
+ removed : usize ,
46
+ changed : usize ,
47
+ }
48
+
49
+ #[ derive( Debug , Serialize ) ]
50
+ struct Summary ( Vec < SummaryRow > ) ;
51
+
52
+ impl fmt:: Display for Summary {
53
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
54
+ f. write_str (
55
+ "\
56
+ | Lint | Added | Removed | Changed |
57
+ | ---- | ----: | ------: | ------: |
58
+ " ,
59
+ ) ?;
60
+
61
+ for SummaryRow {
62
+ name,
63
+ added,
64
+ changed,
65
+ removed,
66
+ } in & self . 0
67
+ {
68
+ let html_id = to_html_id ( name) ;
69
+ writeln ! ( f, "| [`{name}`](#{html_id}) | {added} | {changed} | {removed} |" ) ?;
70
+ }
71
+
72
+ Ok ( ( ) )
73
+ }
74
+ }
75
+
76
+ impl Summary {
77
+ fn new ( lints : & [ LintWarnings ] ) -> Self {
78
+ Summary (
79
+ lints
80
+ . iter ( )
81
+ . map ( |lint| SummaryRow {
82
+ name : lint. name . clone ( ) ,
83
+ added : lint. added . len ( ) ,
84
+ removed : lint. removed . len ( ) ,
85
+ changed : lint. changed . len ( ) ,
86
+ } )
87
+ . collect ( ) ,
88
+ )
89
+ }
90
+ }
91
+
43
92
/// Creates the log file output for [`crate::config::OutputFormat::Json`]
44
93
pub ( crate ) fn output ( clippy_warnings : Vec < ClippyWarning > ) -> String {
45
94
let mut lints: Vec < LintJson > = clippy_warnings
@@ -74,7 +123,7 @@ fn load_warnings(path: &Path) -> Vec<LintJson> {
74
123
///
75
124
/// Compares warnings from `old_path` and `new_path`, then displays a summary table
76
125
/// and detailed information about added, removed, and changed warnings.
77
- pub ( crate ) fn diff ( old_path : & Path , new_path : & Path , truncate : bool ) {
126
+ pub ( crate ) fn diff ( old_path : & Path , new_path : & Path , truncate : bool , write_summary : Option < PathBuf > ) {
78
127
let old_warnings = load_warnings ( old_path) ;
79
128
let new_warnings = load_warnings ( new_path) ;
80
129
@@ -108,13 +157,16 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
108
157
}
109
158
}
110
159
111
- print_summary_table ( & lint_warnings) ;
112
- println ! ( ) ;
113
-
114
160
if lint_warnings. is_empty ( ) {
115
161
return ;
116
162
}
117
163
164
+ let summary = Summary :: new ( & lint_warnings) ;
165
+ if let Some ( path) = write_summary {
166
+ let json = serde_json:: to_string ( & summary) . unwrap ( ) ;
167
+ fs:: write ( path, json) . unwrap ( ) ;
168
+ }
169
+
118
170
let truncate_after = if truncate {
119
171
// Max 15 ensures that we at least have five messages per lint
120
172
DEFAULT_LIMIT_PER_LINT
@@ -126,6 +178,7 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
126
178
usize:: MAX
127
179
} ;
128
180
181
+ println ! ( "{summary}" ) ;
129
182
for lint in lint_warnings {
130
183
print_lint_warnings ( & lint, truncate_after) ;
131
184
}
@@ -140,13 +193,11 @@ struct LintWarnings {
140
193
changed : Vec < ( LintJson , LintJson ) > ,
141
194
}
142
195
143
- /// Prints a formatted report for a single lint type with its warnings.
144
196
fn print_lint_warnings ( lint : & LintWarnings , truncate_after : usize ) {
145
197
let name = & lint. name ;
146
198
let html_id = to_html_id ( name) ;
147
199
148
- // The additional anchor is added for non GH viewers that don't prefix ID's
149
- println ! ( r#"## `{name}` <a id="user-content-{html_id}"></a>"# ) ;
200
+ println ! ( r#"<h2 id="{html_id}"><code>{name}</code></h2>"# ) ;
150
201
println ! ( ) ;
151
202
152
203
print ! (
@@ -162,22 +213,6 @@ fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) {
162
213
print_changed_diff ( & lint. changed , truncate_after / 3 ) ;
163
214
}
164
215
165
- /// Prints a summary table of all lints with counts of added, removed, and changed warnings.
166
- fn print_summary_table ( lints : & [ LintWarnings ] ) {
167
- println ! ( "| Lint | Added | Removed | Changed |" ) ;
168
- println ! ( "| ------------------------------------------ | ------: | ------: | ------: |" ) ;
169
-
170
- for lint in lints {
171
- println ! (
172
- "| {:<62} | {:>7} | {:>7} | {:>7} |" ,
173
- format!( "[`{}`](#user-content-{})" , lint. name, to_html_id( & lint. name) ) ,
174
- lint. added. len( ) ,
175
- lint. removed. len( ) ,
176
- lint. changed. len( )
177
- ) ;
178
- }
179
- }
180
-
181
216
/// Prints a section of warnings with a header and formatted code blocks.
182
217
fn print_warnings ( title : & str , warnings : & [ LintJson ] , truncate_after : usize ) {
183
218
if warnings. is_empty ( ) {
@@ -248,17 +283,16 @@ fn truncate<T>(list: &[T], truncate_after: usize) -> &[T] {
248
283
}
249
284
}
250
285
251
- /// Prints a level 3 heading with an appropriate HTML ID for linking.
252
286
fn print_h3 ( lint : & str , title : & str ) {
253
287
let html_id = to_html_id ( lint) ;
254
- // We have to use HTML here to be able to manually add an id.
255
- println ! ( r#"### {title} <a id="user-content- {html_id}-{title}"></a >"# ) ;
288
+ // We have to use HTML here to be able to manually add an id, GitHub doesn't add them automatically
289
+ println ! ( r#"<h3 id="{html_id}-{title}">{title}</h3 >"# ) ;
256
290
}
257
291
258
- /// GitHub's markdown parsers doesn't like IDs with `:: ` and `_`. This simplifies
259
- /// the lint name for the HTML ID.
292
+ /// Creates a custom ID allowed by GitHub, they must start with `user-content- ` and cannot contain
293
+ /// `::`/`_`
260
294
fn to_html_id ( lint_name : & str ) -> String {
261
- lint_name. replace ( "clippy::" , "" ) . replace ( '_' , "-" )
295
+ lint_name. replace ( "clippy::" , "user-content- " ) . replace ( '_' , "-" )
262
296
}
263
297
264
298
/// This generates the `x added` string for the start of the job summery.
@@ -270,9 +304,6 @@ fn count_string(lint: &str, label: &str, count: usize) -> String {
270
304
format ! ( "0 {label}" )
271
305
} else {
272
306
let html_id = to_html_id ( lint) ;
273
- // GitHub's job summaries don't add HTML ids to headings. That's why we
274
- // manually have to add them. GitHub prefixes these manual ids with
275
- // `user-content-` and that's how we end up with these awesome links :D
276
- format ! ( "[{count} {label}](#user-content-{html_id}-{label})" )
307
+ format ! ( "[{count} {label}](#{html_id}-{label})" )
277
308
}
278
309
}
0 commit comments