2
2
//! output.
3
3
4
4
use std:: {
5
+ fs,
5
6
process:: Command ,
6
7
sync:: { Arc , Mutex , MutexGuard } ,
7
8
} ;
8
9
9
10
use anyhow:: { Context , Result } ;
10
11
use log:: Level ;
11
- // non-std crates
12
12
use serde:: Deserialize ;
13
- use serde_xml_rs:: de:: Deserializer ;
14
13
15
14
// project-specific crates/modules
16
15
use super :: MakeSuggestions ;
17
16
use crate :: {
18
17
cli:: ClangParams ,
19
- common_fs:: { get_line_cols_from_offset , FileObj } ,
18
+ common_fs:: { get_line_count_from_offset , FileObj } ,
20
19
} ;
21
20
22
- /// A Structure used to deserialize clang-format's XML output.
23
- #[ derive( Debug , Deserialize , PartialEq , Clone ) ]
24
- #[ serde( rename = "replacements" ) ]
21
+ #[ derive( Debug , Clone , Deserialize , PartialEq , Eq ) ]
25
22
pub struct FormatAdvice {
26
23
/// A list of [`Replacement`]s that clang-tidy wants to make.
27
- #[ serde( rename = "$value" ) ]
24
+ #[ serde( rename( deserialize = "replacement" ) ) ]
28
25
pub replacements : Vec < Replacement > ,
29
26
30
27
pub patched : Option < Vec < u8 > > ,
@@ -41,43 +38,18 @@ impl MakeSuggestions for FormatAdvice {
41
38
}
42
39
43
40
/// A single replacement that clang-format wants to make.
44
- #[ derive( Debug , Deserialize , PartialEq ) ]
41
+ #[ derive( Debug , PartialEq , Eq , Default , Clone , Copy , Deserialize ) ]
45
42
pub struct Replacement {
46
43
/// The byte offset where the replacement will start.
47
- pub offset : usize ,
48
-
49
- /// The amount of bytes that will be removed.
50
- pub length : usize ,
51
-
52
- /// The bytes (UTF-8 encoded) that will be added at the [`Replacement::offset`] position.
53
- #[ serde( rename = "$value" ) ]
54
- pub value : Option < String > ,
44
+ #[ serde( rename = "@offset" ) ]
45
+ pub offset : u32 ,
55
46
56
47
/// The line number described by the [`Replacement::offset`].
57
48
///
58
49
/// This value is not provided by the XML output, but we calculate it after
59
50
/// deserialization.
60
51
#[ serde( default ) ]
61
- pub line : usize ,
62
-
63
- /// The column number on the line described by the [`Replacement::offset`].
64
- ///
65
- /// This value is not provided by the XML output, but we calculate it after
66
- /// deserialization.
67
- #[ serde( default ) ]
68
- pub cols : usize ,
69
- }
70
-
71
- impl Clone for Replacement {
72
- fn clone ( & self ) -> Self {
73
- Replacement {
74
- offset : self . offset ,
75
- length : self . length ,
76
- value : self . value . clone ( ) ,
77
- line : self . line ,
78
- cols : self . cols ,
79
- }
80
- }
52
+ pub line : u32 ,
81
53
}
82
54
83
55
/// Get a string that summarizes the given `--style`
@@ -122,8 +94,9 @@ pub fn run_clang_format(
122
94
}
123
95
let file_name = file. name . to_string_lossy ( ) . to_string ( ) ;
124
96
cmd. arg ( file. name . to_path_buf ( ) . as_os_str ( ) ) ;
125
- let mut patched = None ;
126
- if clang_params. format_review {
97
+ let patched = if !clang_params. format_review {
98
+ None
99
+ } else {
127
100
logs. push ( (
128
101
Level :: Info ,
129
102
format ! (
@@ -140,12 +113,12 @@ pub fn run_clang_format(
140
113
. join( " " )
141
114
) ,
142
115
) ) ;
143
- patched = Some (
116
+ Some (
144
117
cmd. output ( )
145
118
. with_context ( || format ! ( "Failed to get fixes from clang-format: {file_name}" ) ) ?
146
119
. stdout ,
147
- ) ;
148
- }
120
+ )
121
+ } ;
149
122
cmd. arg ( "--output-replacements-xml" ) ;
150
123
logs. push ( (
151
124
log:: Level :: Info ,
@@ -170,41 +143,40 @@ pub fn run_clang_format(
170
143
) ,
171
144
) ) ;
172
145
}
173
- if output. stdout . is_empty ( ) {
174
- return Ok ( logs) ;
175
- }
176
- let xml = String :: from_utf8 ( output. stdout )
177
- . with_context ( || format ! ( "stdout from clang-format was not UTF-8 encoded: {file_name}" ) ) ?
178
- . lines ( )
179
- . collect :: < Vec < & str > > ( )
180
- . join ( "" ) ;
181
- let config = serde_xml_rs:: ParserConfig :: new ( )
182
- . trim_whitespace ( false )
183
- . whitespace_to_characters ( true )
184
- . ignore_root_level_whitespace ( true ) ;
185
- let event_reader = serde_xml_rs:: EventReader :: new_with_config ( xml. as_bytes ( ) , config) ;
186
- let mut format_advice = FormatAdvice :: deserialize ( & mut Deserializer :: new ( event_reader) )
187
- . unwrap_or ( FormatAdvice {
146
+ let mut format_advice = if !output. stdout . is_empty ( ) {
147
+ let xml = String :: from_utf8 ( output. stdout ) . with_context ( || {
148
+ format ! ( "XML output from clang-format was not UTF-8 encoded: {file_name}" )
149
+ } ) ?;
150
+ quick_xml:: de:: from_str :: < FormatAdvice > ( & xml) . with_context ( || {
151
+ format ! ( "Failed to parse XML output from clang-format for {file_name}" )
152
+ } ) ?
153
+ } else {
154
+ FormatAdvice {
188
155
replacements : vec ! [ ] ,
189
156
patched : None ,
190
- } ) ;
157
+ }
158
+ } ;
191
159
format_advice. patched = patched;
192
160
if !format_advice. replacements . is_empty ( ) {
161
+ let original_contents = fs:: read ( & file. name ) . with_context ( || {
162
+ format ! (
163
+ "Failed to read file's original content before translating byte offsets: {file_name}" ,
164
+ )
165
+ } ) ?;
193
166
// get line and column numbers from format_advice.offset
194
167
let mut filtered_replacements = Vec :: new ( ) ;
195
168
for replacement in & mut format_advice. replacements {
196
- let ( line_number, columns ) = get_line_cols_from_offset ( & file . name , replacement. offset ) ;
169
+ let line_number = get_line_count_from_offset ( & original_contents , replacement. offset ) ;
197
170
replacement. line = line_number;
198
- replacement. cols = columns;
199
171
for range in & ranges {
200
- if range. contains ( & line_number. try_into ( ) . unwrap_or ( 0 ) ) {
201
- filtered_replacements. push ( replacement. clone ( ) ) ;
172
+ if range. contains ( & line_number) {
173
+ filtered_replacements. push ( * replacement) ;
202
174
break ;
203
175
}
204
176
}
205
177
if ranges. is_empty ( ) {
206
178
// lines_changed_only is disabled
207
- filtered_replacements. push ( replacement. clone ( ) ) ;
179
+ filtered_replacements. push ( * replacement) ;
208
180
}
209
181
}
210
182
format_advice. replacements = filtered_replacements;
@@ -216,7 +188,13 @@ pub fn run_clang_format(
216
188
#[ cfg( test) ]
217
189
mod tests {
218
190
use super :: { summarize_style, FormatAdvice , Replacement } ;
219
- use serde:: Deserialize ;
191
+
192
+ #[ test]
193
+ fn parse_blank_xml ( ) {
194
+ let xml = String :: new ( ) ;
195
+ let result = quick_xml:: de:: from_str :: < FormatAdvice > ( & xml) ;
196
+ assert ! ( result. is_err( ) ) ;
197
+ }
220
198
221
199
#[ test]
222
200
fn parse_xml ( ) {
@@ -226,51 +204,24 @@ mod tests {
226
204
<replacement offset='147' length='0'> </replacement>
227
205
<replacement offset='161' length='0'></replacement>
228
206
<replacement offset='165' length='19'> </replacement>
229
- </replacements>"# ;
230
- //since whitespace is part of the elements' body, we need to remove the LFs first
231
- let xml = xml_raw . lines ( ) . collect :: < Vec < & str > > ( ) . join ( "" ) ;
207
+ </replacements>"#
208
+ . as_bytes ( )
209
+ . to_vec ( ) ;
232
210
233
211
let expected = FormatAdvice {
234
- replacements : vec ! [
235
- Replacement {
236
- offset: 113 ,
237
- length: 5 ,
238
- value: Some ( String :: from( "\n " ) ) ,
239
- line: 0 ,
240
- cols: 0 ,
241
- } ,
242
- Replacement {
243
- offset: 147 ,
244
- length: 0 ,
245
- value: Some ( String :: from( " " ) ) ,
246
- line: 0 ,
247
- cols: 0 ,
248
- } ,
249
- Replacement {
250
- offset: 161 ,
251
- length: 0 ,
252
- value: None ,
253
- line: 0 ,
254
- cols: 0 ,
255
- } ,
256
- Replacement {
257
- offset: 165 ,
258
- length: 19 ,
259
- value: Some ( String :: from( "\n \n " ) ) ,
260
- line: 0 ,
261
- cols: 0 ,
262
- } ,
263
- ] ,
212
+ replacements : [ 113 , 147 , 161 , 165 ]
213
+ . iter ( )
214
+ . map ( |offset| Replacement {
215
+ offset : * offset,
216
+ ..Default :: default ( )
217
+ } )
218
+ . collect ( ) ,
264
219
patched : None ,
265
220
} ;
266
- let config = serde_xml_rs:: ParserConfig :: new ( )
267
- . trim_whitespace ( false )
268
- . whitespace_to_characters ( true )
269
- . ignore_root_level_whitespace ( true ) ;
270
- let event_reader = serde_xml_rs:: EventReader :: new_with_config ( xml. as_bytes ( ) , config) ;
271
- let document =
272
- FormatAdvice :: deserialize ( & mut serde_xml_rs:: de:: Deserializer :: new ( event_reader) )
273
- . unwrap ( ) ;
221
+
222
+ let xml = String :: from_utf8 ( xml_raw) . unwrap ( ) ;
223
+
224
+ let document = quick_xml:: de:: from_str :: < FormatAdvice > ( & xml) . unwrap ( ) ;
274
225
assert_eq ! ( expected, document) ;
275
226
}
276
227
0 commit comments