44use std:: {
55 fs,
66 ops:: RangeInclusive ,
7- path:: PathBuf ,
87 process:: Command ,
98 sync:: { Arc , Mutex , MutexGuard } ,
109} ;
1110
12- use gix_imara_diff:: { Diff , InternedInput } ;
1311use log:: Level ;
1412
1513// project-specific crates/modules
16- use super :: { CACHE_DIR , MakeSuggestions } ;
17- use crate :: { cli:: ClangParams , common_fs:: FileObj , error:: ClangCaptureError } ;
14+ use super :: CACHE_DIR ;
15+ use crate :: {
16+ clang_tools:: make_patch, cli:: ClangParams , common_fs:: FileObj , error:: ClangCaptureError ,
17+ } ;
1818
1919/// A struct to hold clang-format advice for a single file.
2020#[ derive( Debug , Clone , PartialEq , Eq , Default ) ]
2121pub struct FormatAdvice {
2222 /// A list of line ranges that clang-format wants to replace.
2323 pub replacements : Vec < RangeInclusive < u32 > > ,
24-
25- /// A path to a cached file containing the full contents of the file after applying clang-format fixes.
26- pub patched : PathBuf ,
27- }
28-
29- impl MakeSuggestions for FormatAdvice {
30- fn get_suggestion_help ( & self , _start_line : u32 , _end_line : u32 ) -> String {
31- String :: from ( "### clang-format suggestions\n " )
32- }
33-
34- fn get_tool_name ( & self ) -> String {
35- "clang-format" . to_string ( )
36- }
3724}
3825
3926/// Get a string that summarizes the given `--style`
@@ -83,13 +70,6 @@ pub fn run_clang_format(
8370 cmd. arg ( format ! ( "--lines={}:{}" , range. start( ) , range. end( ) ) ) ;
8471 }
8572 let cache_path = clang_params. repo_root . join ( CACHE_DIR ) . join ( "patches" ) ;
86- let cache_format_fixes = cache_path. join ( file. name . with_added_extension ( "format" ) ) ;
87- fs:: create_dir_all (
88- cache_format_fixes
89- . parent ( )
90- . ok_or ( ClangCaptureError :: UnknownCacheParentPath ) ?,
91- )
92- . map_err ( ClangCaptureError :: MkDirFailed ) ?;
9373 let file_name = file. name . to_string_lossy ( ) . to_string ( ) ;
9474 cmd. arg ( file. name . to_path_buf ( ) . as_os_str ( ) ) ;
9575 logs. push ( (
@@ -109,12 +89,6 @@ pub fn run_clang_format(
10989 task : format ! ( "get fixes from clang-format {file_name}" ) ,
11090 source : e,
11191 } ) ?;
112- fs:: write ( & cache_format_fixes, & output. stdout ) . map_err ( |e| {
113- ClangCaptureError :: WriteFileFailed {
114- file_name : cache_format_fixes. to_string_lossy ( ) . to_string ( ) ,
115- source : e,
116- }
117- } ) ?;
11892
11993 if !output. stderr . is_empty ( ) || !output. status . success ( ) {
12094 logs. push ( (
@@ -140,9 +114,7 @@ pub fn run_clang_format(
140114 source : e,
141115 }
142116 } ) ?;
143- let input = InternedInput :: new ( original_contents. as_str ( ) , patched_contents. as_str ( ) ) ;
144- let mut diff = Diff :: compute ( gix_imara_diff:: Algorithm :: Histogram , & input) ;
145- diff. postprocess_lines ( & input) ;
117+ let ( diff, _) = make_patch ( & patched_contents, & original_contents) ;
146118 let format_advice = FormatAdvice {
147119 replacements : diff
148120 . hunks ( )
@@ -166,8 +138,86 @@ pub fn run_clang_format(
166138 }
167139 } )
168140 . collect ( ) ,
169- patched : cache_format_fixes,
170141 } ;
142+
143+ // if a clang-tidy patched file exists in cache,
144+ // get the diff between it and the original file,
145+ // then format both clang-tidy fixes and any other changes by clang-format fixes.
146+ if let Some ( patched_path) = & file. patched_path
147+ && patched_path. exists ( )
148+ {
149+ let tidy_patch_contents =
150+ fs:: read_to_string ( patched_path) . map_err ( |e| ClangCaptureError :: ReadFileFailed {
151+ file_name : patched_path. to_string_lossy ( ) . to_string ( ) ,
152+ source : e,
153+ } ) ?;
154+ let ( tidy_diff, _) = make_patch ( & tidy_patch_contents, & original_contents) ;
155+ let mut cmd = Command :: new ( cmd_path) ;
156+ cmd. current_dir ( & cache_path) ;
157+ // edit the clang-tody patched file in-place (`-i`)
158+ cmd. args ( [ "--style" , & clang_params. style , "-i" ] ) ;
159+ // if ranges is empty, then we're just formatting the entire file.
160+ if !ranges. is_empty ( ) {
161+ // We're concerned about formatting what clang-tidy changed (tidy_diff.hunks().after),
162+ // but we also want to include any clang-format changes that do not overlap clang-tidy fixes.
163+ let mut joint_ranges = tidy_diff
164+ . hunks ( )
165+ // hunk is partially inclusive: [start, end),
166+ // but clang-format expects fully inclusive line ranges.
167+ // subtract 1 from hunk.after.end
168+ . map ( |hunk| RangeInclusive :: new ( hunk. after . start , hunk. after . end . saturating_sub ( 1 ) ) )
169+ . collect :: < Vec < _ > > ( ) ;
170+ for range in & ranges {
171+ let mut contained = false ;
172+ for hunk in tidy_diff. hunks ( ) {
173+ if hunk. after . contains ( range. start ( ) ) && hunk. after . contains ( range. end ( ) ) {
174+ contained = true ;
175+ break ;
176+ }
177+ }
178+ if !contained {
179+ joint_ranges. push ( range. clone ( ) ) ;
180+ }
181+ }
182+ for range in & joint_ranges {
183+ cmd. arg ( format ! ( "--lines={}:{}" , range. start( ) , range. end( ) ) . as_str ( ) ) ;
184+ }
185+ }
186+ cmd. arg ( & file_name) ;
187+ let output = cmd
188+ . output ( )
189+ . map_err ( |e| ClangCaptureError :: FailedToRunCommand {
190+ task : format ! ( "apply clang-format to clang-tidy fixes ({file_name})" ) ,
191+ source : e,
192+ } ) ?;
193+ if !output. stderr . is_empty ( ) || !output. status . success ( ) {
194+ logs. push ( (
195+ log:: Level :: Debug ,
196+ format ! (
197+ "clang-format raised the follow errors about clang-tidy fixes:\n {}" ,
198+ String :: from_utf8_lossy( & output. stderr)
199+ ) ,
200+ ) ) ;
201+ }
202+ } else {
203+ // clang-tidy was not run on this file,
204+ // so just use the clang-format fixes as the patched content.
205+ let cache_format_fixes = cache_path. join ( & file. name ) ;
206+ fs:: create_dir_all (
207+ cache_format_fixes
208+ . parent ( )
209+ . ok_or ( ClangCaptureError :: UnknownCacheParentPath ) ?,
210+ )
211+ . map_err ( ClangCaptureError :: MkDirFailed ) ?;
212+ fs:: write ( & cache_format_fixes, & output. stdout ) . map_err ( |e| {
213+ ClangCaptureError :: WriteFileFailed {
214+ file_name : cache_format_fixes. to_string_lossy ( ) . to_string ( ) ,
215+ source : e,
216+ }
217+ } ) ?;
218+ file. patched_path = Some ( cache_format_fixes) ;
219+ }
220+
171221 file. format_advice = Some ( format_advice) ;
172222 Ok ( logs)
173223}
0 commit comments