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 crate :: {
15+ clang_tools:: make_patch, cli:: ClangParams , common_fs:: FileObj , error:: ClangCaptureError ,
16+ } ;
1817
1918/// A struct to hold clang-format advice for a single file.
2019#[ derive( Debug , Clone , PartialEq , Eq , Default ) ]
2120pub struct FormatAdvice {
2221 /// A list of line ranges that clang-format wants to replace.
2322 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- }
3723}
3824
3925/// Get a string that summarizes the given `--style`
@@ -82,14 +68,7 @@ pub fn run_clang_format(
8268 for range in & ranges {
8369 cmd. arg ( format ! ( "--lines={}:{}" , range. start( ) , range. end( ) ) ) ;
8470 }
85- 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 ) ?;
71+ let cache_path = clang_params. get_cache_path ( ) ;
9372 let file_name = file. name . to_string_lossy ( ) . to_string ( ) ;
9473 cmd. arg ( file. name . to_path_buf ( ) . as_os_str ( ) ) ;
9574 logs. push ( (
@@ -109,12 +88,6 @@ pub fn run_clang_format(
10988 task : format ! ( "get fixes from clang-format {file_name}" ) ,
11089 source : e,
11190 } ) ?;
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- } ) ?;
11891
11992 if !output. stderr . is_empty ( ) || !output. status . success ( ) {
12093 logs. push ( (
@@ -140,9 +113,7 @@ pub fn run_clang_format(
140113 source : e,
141114 }
142115 } ) ?;
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) ;
116+ let ( diff, _) = make_patch ( & patched_contents, & original_contents) ;
146117 let format_advice = FormatAdvice {
147118 replacements : diff
148119 . hunks ( )
@@ -166,8 +137,86 @@ pub fn run_clang_format(
166137 }
167138 } )
168139 . collect ( ) ,
169- patched : cache_format_fixes,
170140 } ;
141+
142+ // if a clang-tidy patched file exists in cache,
143+ // get the diff between it and the original file,
144+ // then format both clang-tidy fixes and any other changes by clang-format fixes.
145+ if let Some ( patched_path) = & file. patched_path
146+ && patched_path. exists ( )
147+ {
148+ let tidy_patch_contents =
149+ fs:: read_to_string ( patched_path) . map_err ( |e| ClangCaptureError :: ReadFileFailed {
150+ file_name : patched_path. to_string_lossy ( ) . to_string ( ) ,
151+ source : e,
152+ } ) ?;
153+ let ( tidy_diff, _) = make_patch ( & tidy_patch_contents, & original_contents) ;
154+ let mut cmd = Command :: new ( cmd_path) ;
155+ cmd. current_dir ( & cache_path) ;
156+ // edit the clang-tody patched file in-place (`-i`)
157+ cmd. args ( [ "--style" , & clang_params. style , "-i" ] ) ;
158+ // if ranges is empty, then we're just formatting the entire file.
159+ if !ranges. is_empty ( ) {
160+ // We're concerned about formatting what clang-tidy changed (tidy_diff.hunks().after),
161+ // but we also want to include any clang-format changes that do not overlap clang-tidy fixes.
162+ let mut joint_ranges = tidy_diff
163+ . hunks ( )
164+ // hunk is partially inclusive: [start, end),
165+ // but clang-format expects fully inclusive line ranges.
166+ // subtract 1 from hunk.after.end
167+ . map ( |hunk| RangeInclusive :: new ( hunk. after . start , hunk. after . end . saturating_sub ( 1 ) ) )
168+ . collect :: < Vec < _ > > ( ) ;
169+ for range in & ranges {
170+ let mut contained = false ;
171+ for hunk in tidy_diff. hunks ( ) {
172+ if hunk. after . contains ( range. start ( ) ) && hunk. after . contains ( range. end ( ) ) {
173+ contained = true ;
174+ break ;
175+ }
176+ }
177+ if !contained {
178+ joint_ranges. push ( range. clone ( ) ) ;
179+ }
180+ }
181+ for range in & joint_ranges {
182+ cmd. arg ( format ! ( "--lines={}:{}" , range. start( ) , range. end( ) ) . as_str ( ) ) ;
183+ }
184+ }
185+ cmd. arg ( & file_name) ;
186+ let output = cmd
187+ . output ( )
188+ . map_err ( |e| ClangCaptureError :: FailedToRunCommand {
189+ task : format ! ( "apply clang-format to clang-tidy fixes ({file_name})" ) ,
190+ source : e,
191+ } ) ?;
192+ if !output. stderr . is_empty ( ) || !output. status . success ( ) {
193+ logs. push ( (
194+ log:: Level :: Debug ,
195+ format ! (
196+ "clang-format raised the follow errors about clang-tidy fixes:\n {}" ,
197+ String :: from_utf8_lossy( & output. stderr)
198+ ) ,
199+ ) ) ;
200+ }
201+ } else {
202+ // clang-tidy was not run on this file,
203+ // so just use the clang-format fixes as the patched content.
204+ let cache_format_fixes = cache_path. join ( & file. name ) ;
205+ fs:: create_dir_all (
206+ cache_format_fixes
207+ . parent ( )
208+ . ok_or ( ClangCaptureError :: UnknownCacheParentPath ) ?,
209+ )
210+ . map_err ( ClangCaptureError :: MkDirFailed ) ?;
211+ fs:: write ( & cache_format_fixes, & output. stdout ) . map_err ( |e| {
212+ ClangCaptureError :: WriteFileFailed {
213+ file_name : cache_format_fixes. to_string_lossy ( ) . to_string ( ) ,
214+ source : e,
215+ }
216+ } ) ?;
217+ file. patched_path = Some ( cache_format_fixes) ;
218+ }
219+
171220 file. format_advice = Some ( format_advice) ;
172221 Ok ( logs)
173222}
0 commit comments