@@ -16,7 +16,9 @@ use serde::Deserialize;
1616
1717// project-specific modules/crates
1818use super :: MakeSuggestions ;
19- use crate :: { cli:: ClangParams , common_fs:: FileObj , error:: ClangCaptureError } ;
19+ use crate :: {
20+ clang_tools:: CACHE_DIR , cli:: ClangParams , common_fs:: FileObj , error:: ClangCaptureError ,
21+ } ;
2022
2123/// Used to deserialize a json compilation database's translation unit.
2224///
@@ -103,8 +105,8 @@ pub struct TidyAdvice {
103105 /// A list of notifications parsed from clang-tidy stdout.
104106 pub notes : Vec < TidyNotification > ,
105107
106- /// A buffer to hold the contents of the file after applying clang-tidy fixes.
107- pub patched : Option < Vec < u8 > > ,
108+ /// A path to the cached contents of the file after applying clang-tidy fixes.
109+ pub patched : PathBuf ,
108110}
109111
110112impl MakeSuggestions for TidyAdvice {
@@ -148,7 +150,7 @@ fn parse_tidy_output(
148150 tidy_stdout : & [ u8 ] ,
149151 database_json : & Option < Vec < CompilationUnit > > ,
150152 repo_root : & Path ,
151- ) -> Result < TidyAdvice , ClangCaptureError > {
153+ ) -> Result < Vec < TidyNotification > , ClangCaptureError > {
152154 let note_header = Regex :: new ( NOTE_HEADER ) ?;
153155 let fixed_note = Regex :: new ( r"^.+:(\d+):\d+:\snote: FIX-IT applied suggested code changes$" ) ?;
154156 let mut found_fix = false ;
@@ -243,10 +245,7 @@ fn parse_tidy_output(
243245 if let Some ( note) = notification {
244246 result. push ( note) ;
245247 }
246- Ok ( TidyAdvice {
247- notes : result,
248- patched : None ,
249- } )
248+ Ok ( result)
250249}
251250
252251/// Get a total count of clang-tidy advice from the given list of [FileObj]s.
@@ -266,6 +265,26 @@ pub fn tally_tidy_advice(files: &[Arc<Mutex<FileObj>>]) -> Result<u64, String> {
266265 Ok ( total)
267266}
268267
268+ /// RAII guard that restores a file's original content on drop.
269+ ///
270+ /// This is a best-effort when an error gets propagated before
271+ /// we can explicitly restore a file's contents.
272+ struct RestoreOnDrop < ' a > {
273+ path : & ' a Path ,
274+ content : String ,
275+ // lock:
276+ armed : bool ,
277+ }
278+
279+ impl Drop for RestoreOnDrop < ' _ > {
280+ fn drop ( & mut self ) {
281+ if self . armed {
282+ // We have to ignore any error here and hope for the best.
283+ let _ = fs:: write ( self . path , & self . content ) ;
284+ }
285+ }
286+ }
287+
269288/// Run clang-tidy, then parse and return it's output.
270289pub fn run_clang_tidy (
271290 file : & mut MutexGuard < FileObj > ,
@@ -301,17 +320,7 @@ pub fn run_clang_tidy(
301320 cmd. args ( [ "--line-filter" , filter. as_str ( ) ] ) ;
302321 }
303322 let repo_file_path = clang_params. repo_root . join ( & file. name ) ;
304- let original_content = if !clang_params. tidy_review {
305- None
306- } else {
307- cmd. arg ( "--fix-errors" ) ;
308- Some ( fs:: read_to_string ( & repo_file_path) . map_err ( |e| {
309- ClangCaptureError :: ReadFileFailed {
310- file_name : file_name. clone ( ) ,
311- source : e,
312- }
313- } ) ?)
314- } ;
323+ cmd. arg ( "--fix-errors" ) ;
315324 if !clang_params. style . is_empty ( ) {
316325 cmd. args ( [ "--format-style" , clang_params. style . as_str ( ) ] ) ;
317326 }
@@ -327,12 +336,50 @@ pub fn run_clang_tidy(
327336 . join( " " )
328337 ) ,
329338 ) ) ;
339+ let cache_patch_path = clang_params
340+ . repo_root
341+ . join ( CACHE_DIR )
342+ . join ( file. name . with_added_extension ( "tidy" ) ) ;
343+ fs:: create_dir_all (
344+ cache_patch_path
345+ . parent ( )
346+ . ok_or ( ClangCaptureError :: UnknownCacheParentPath ) ?,
347+ )
348+ . map_err ( ClangCaptureError :: MkDirFailed ) ?;
349+ let mut drop_guard = RestoreOnDrop {
350+ content : fs:: read_to_string ( & repo_file_path) . map_err ( |e| {
351+ ClangCaptureError :: ReadFileFailed {
352+ file_name : file_name. clone ( ) ,
353+ source : e,
354+ }
355+ } ) ?,
356+ armed : true ,
357+ path : repo_file_path. as_path ( ) ,
358+ } ;
359+ // run clang-tidy to patch the file in-place.
330360 let output = cmd
331361 . output ( )
332362 . map_err ( |e| ClangCaptureError :: FailedToRunCommand {
333363 task : format ! ( "execute clang-tidy on file {file_name}" ) ,
334364 source : e,
335365 } ) ?;
366+ // move the patched file content into cache
367+ fs:: copy ( & repo_file_path, & cache_patch_path) . map_err ( |e| {
368+ ClangCaptureError :: WriteFileFailed {
369+ file_name : cache_patch_path. to_string_lossy ( ) . to_string ( ) ,
370+ source : e,
371+ }
372+ } ) ?;
373+ // restore the original file content to avoid unexpected behavior in later steps of dev workflow
374+ fs:: write ( & repo_file_path, & drop_guard. content ) . map_err ( |e| {
375+ ClangCaptureError :: WriteFileFailed {
376+ file_name : file_name. clone ( ) ,
377+ source : e,
378+ }
379+ } ) ?;
380+ // disarm the drop guard since we've already restored the original content
381+ drop_guard. armed = false ;
382+
336383 logs. push ( (
337384 log:: Level :: Debug ,
338385 format ! (
@@ -349,32 +396,18 @@ pub fn run_clang_tidy(
349396 ) ,
350397 ) ) ;
351398 }
352- file. tidy_advice = Some ( parse_tidy_output (
399+
400+ let notes = parse_tidy_output (
353401 & output. stdout ,
354402 & clang_params. database_json ,
355403 & clang_params. repo_root ,
356- ) ?) ;
357- if clang_params. tidy_review
358- && let Some ( original_content) = & original_content
359- {
360- if let Some ( tidy_advice) = & mut file. tidy_advice {
361- // cache file changes in a buffer and restore the original contents for further analysis
362- tidy_advice. patched =
363- Some (
364- fs:: read ( & repo_file_path) . map_err ( |e| ClangCaptureError :: ReadFileFailed {
365- file_name : file_name. clone ( ) ,
366- source : e,
367- } ) ?,
368- ) ;
369- }
370- // original_content is guaranteed to be Some() value at this point
371- fs:: write ( & repo_file_path, original_content) . map_err ( |e| {
372- ClangCaptureError :: WriteFileFailed {
373- file_name,
374- source : e,
375- }
376- } ) ?;
377- }
404+ ) ?;
405+
406+ let tidy_advice = TidyAdvice {
407+ notes,
408+ patched : cache_patch_path. to_path_buf ( ) ,
409+ } ;
410+ file. tidy_advice = Some ( tidy_advice) ;
378411 Ok ( logs)
379412}
380413
@@ -383,7 +416,7 @@ mod test {
383416 #![ allow( clippy:: unwrap_used, clippy:: expect_used) ]
384417
385418 use std:: {
386- env,
419+ env, fs ,
387420 path:: PathBuf ,
388421 str:: FromStr ,
389422 sync:: { Arc , Mutex } ,
@@ -398,7 +431,7 @@ mod test {
398431 common_fs:: FileObj ,
399432 } ;
400433
401- use super :: { NOTE_HEADER , TidyNotification , run_clang_tidy} ;
434+ use super :: { NOTE_HEADER , RestoreOnDrop , TidyNotification , run_clang_tidy} ;
402435
403436 #[ test]
404437 fn clang_diagnostic_link ( ) {
@@ -486,7 +519,8 @@ mod test {
486519 . unwrap ( ) ,
487520 )
488521 . unwrap ( ) ;
489- let file = FileObj :: new ( PathBuf :: from ( "tests/demo/demo.cpp" ) ) ;
522+ let tmp_workspace = crate :: test_common:: setup_tmp_workspace ( ) ;
523+ let file = FileObj :: new ( PathBuf :: from ( "demo/demo.cpp" ) ) ;
490524 let arc_file = Arc :: new ( Mutex :: new ( file) ) ;
491525 let extra_args = vec ! [ "-std=c++17" . to_string( ) , "-Wall" . to_string( ) ] ;
492526 let clang_params = ClangParams {
@@ -502,7 +536,7 @@ mod test {
502536 format_review : false ,
503537 clang_tidy_command : Some ( exe_path) ,
504538 clang_format_command : None ,
505- repo_root : PathBuf :: from ( "." ) ,
539+ repo_root : tmp_workspace . path ( ) . to_path_buf ( ) ,
506540 } ;
507541 let mut file_lock = arc_file. lock ( ) . unwrap ( ) ;
508542 let logs = run_clang_tidy ( & mut file_lock, & clang_params)
@@ -571,10 +605,32 @@ TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48:
571605 | ^
572606"# ;
573607 let advice = parse_tidy_output ( tidy_out. as_bytes ( ) , & None , & PathBuf :: from ( "." ) ) . unwrap ( ) ;
574- assert_eq ! ( advice. notes . len( ) , 4 ) ;
575- for note in advice. notes {
608+ assert_eq ! ( advice. len( ) , 4 ) ;
609+ for note in advice {
576610 assert ! ( note. diagnostic. contains( '-' ) ) ;
577611 assert ! ( !note. diagnostic. contains( ' ' ) ) ;
578612 }
579613 }
614+
615+ #[ test]
616+ fn restore_on_drop_fires ( ) {
617+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
618+ let file_path = tmp. path ( ) . join ( "test_file.txt" ) ;
619+ let original = "original content" ;
620+ fs:: write ( & file_path, original) . unwrap ( ) ;
621+
622+ {
623+ let guard = RestoreOnDrop {
624+ path : & file_path,
625+ content : original. to_string ( ) ,
626+ armed : true ,
627+ } ;
628+ // Simulate clang-tidy mutating the file
629+ fs:: write ( & file_path, "patched content" ) . unwrap ( ) ;
630+ // Explicit drop triggers restoration
631+ drop ( guard) ;
632+ }
633+
634+ assert_eq ! ( fs:: read_to_string( & file_path) . unwrap( ) , original) ;
635+ }
580636}
0 commit comments