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,88 @@ 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().before),
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.before.end
167+ . map ( |hunk| {
168+ RangeInclusive :: new ( hunk. before . start , hunk. before . end . saturating_sub ( 1 ) )
169+ } )
170+ . collect :: < Vec < _ > > ( ) ;
171+ for range in & ranges {
172+ let mut contained = false ;
173+ for hunk in tidy_diff. hunks ( ) {
174+ if hunk. before . contains ( range. start ( ) ) && hunk. before . contains ( range. end ( ) ) {
175+ contained = true ;
176+ break ;
177+ }
178+ }
179+ if !contained {
180+ joint_ranges. push ( range. clone ( ) ) ;
181+ }
182+ }
183+ for range in & joint_ranges {
184+ cmd. arg ( format ! ( "--lines={}:{}" , range. start( ) , range. end( ) ) . as_str ( ) ) ;
185+ }
186+ }
187+ cmd. arg ( & file_name) ;
188+ let output = cmd
189+ . output ( )
190+ . map_err ( |e| ClangCaptureError :: FailedToRunCommand {
191+ task : format ! ( "apply clang-format to clang-tidy fixes ({file_name})" ) ,
192+ source : e,
193+ } ) ?;
194+ if !output. stderr . is_empty ( ) || !output. status . success ( ) {
195+ logs. push ( (
196+ log:: Level :: Debug ,
197+ format ! (
198+ "clang-format raised the follow errors about clang-tidy fixes:\n {}" ,
199+ String :: from_utf8_lossy( & output. stderr)
200+ ) ,
201+ ) ) ;
202+ }
203+ } else {
204+ // clang-tidy was not run on this file,
205+ // so just use the clang-format fixes as the patched content.
206+ let cache_format_fixes = cache_path. join ( & file. name ) ;
207+ fs:: create_dir_all (
208+ cache_format_fixes
209+ . parent ( )
210+ . ok_or ( ClangCaptureError :: UnknownCacheParentPath ) ?,
211+ )
212+ . map_err ( ClangCaptureError :: MkDirFailed ) ?;
213+ fs:: write ( & cache_format_fixes, & output. stdout ) . map_err ( |e| {
214+ ClangCaptureError :: WriteFileFailed {
215+ file_name : cache_format_fixes. to_string_lossy ( ) . to_string ( ) ,
216+ source : e,
217+ }
218+ } ) ?;
219+ file. patched_path = Some ( cache_format_fixes) ;
220+ }
221+
171222 file. format_advice = Some ( format_advice) ;
172223 Ok ( logs)
173224}
0 commit comments