@@ -11,15 +11,17 @@ use std::{
1111// non-std crates
1212use anyhow:: { Context , Result , anyhow} ;
1313use clang_installer:: { ClangTool , RequestedVersion } ;
14+ use git_bot_feedback:: ReviewComment ;
1415use git2:: { DiffOptions , Patch } ;
1516use semver:: Version ;
1617use tokio:: task:: JoinSet ;
1718
1819// project-specific modules/crates
1920use super :: common_fs:: FileObj ;
21+ use crate :: error:: SuggestionError ;
2022use crate :: {
2123 cli:: ClangParams ,
22- rest_api :: { COMMENT_MARKER , RestApiClient , USER_OUTREACH } ,
24+ rest_client :: { RestClient , USER_OUTREACH } ,
2325} ;
2426pub mod clang_format;
2527use clang_format:: run_clang_format;
@@ -46,7 +48,7 @@ fn analyze_single_file(
4648 if clang_params
4749 . format_filter
4850 . as_ref ( )
49- . is_some_and ( |f| f. is_source_or_ignored ( file. name . as_path ( ) ) )
51+ . is_some_and ( |f| f. is_qualified ( file. name . as_path ( ) ) )
5052 || clang_params. format_filter . is_none ( )
5153 {
5254 let format_result = run_clang_format ( & mut file, & clang_params) ?;
@@ -65,7 +67,7 @@ fn analyze_single_file(
6567 if clang_params
6668 . tidy_filter
6769 . as_ref ( )
68- . is_some_and ( |f| f. is_source_or_ignored ( file. name . as_path ( ) ) )
70+ . is_some_and ( |f| f. is_qualified ( file. name . as_path ( ) ) )
6971 || clang_params. tidy_filter . is_none ( )
7072 {
7173 let tidy_result = run_clang_tidy ( & mut file, & clang_params) ?;
@@ -101,7 +103,7 @@ pub async fn capture_clang_tools_output(
101103 files : & [ Arc < Mutex < FileObj > > ] ,
102104 version : & RequestedVersion ,
103105 mut clang_params : ClangParams ,
104- rest_api_client : & impl RestApiClient ,
106+ rest_api_client : & RestClient ,
105107) -> Result < ClangVersions > {
106108 let mut clang_versions = ClangVersions :: default ( ) ;
107109 // find the executable paths for clang-tidy and/or clang-format and show version
@@ -148,11 +150,12 @@ pub async fn capture_clang_tools_output(
148150 // This includes any `spawn()` error and any `analyze_single_file()` error.
149151 // Any unresolved tasks are aborted and dropped when an error is returned here.
150152 let ( file_name, logs) = output??;
151- rest_api_client. start_log_group ( format ! ( "Analyzing {}" , file_name. to_string_lossy( ) ) ) ;
153+ let log_group_name = format ! ( "Analyzing {}" , file_name. to_string_lossy( ) ) ;
154+ rest_api_client. start_log_group ( & log_group_name) ;
152155 for ( level, msg) in logs {
153156 log:: log!( level, "{}" , msg) ;
154157 }
155- rest_api_client. end_log_group ( ) ;
158+ rest_api_client. end_log_group ( & log_group_name ) ;
156159 }
157160 Ok ( clang_versions)
158161}
@@ -169,6 +172,17 @@ pub struct Suggestion {
169172 pub path : String ,
170173}
171174
175+ impl Suggestion {
176+ pub ( crate ) fn as_review_comment ( & self ) -> ReviewComment {
177+ ReviewComment {
178+ line_start : Some ( self . line_start ) ,
179+ line_end : self . line_end ,
180+ comment : self . suggestion . clone ( ) ,
181+ path : self . path . clone ( ) ,
182+ }
183+ }
184+ }
185+
172186/// A struct to describe the Pull Request review suggestions.
173187#[ derive( Default ) ]
174188pub struct ReviewComments {
@@ -189,8 +203,12 @@ pub struct ReviewComments {
189203}
190204
191205impl ReviewComments {
192- pub fn summarize ( & self , clang_versions : & ClangVersions ) -> String {
193- let mut body = format ! ( "{COMMENT_MARKER}## Cpp-linter Review\n " ) ;
206+ pub fn summarize (
207+ & self ,
208+ clang_versions : & ClangVersions ,
209+ comments : & Vec < ReviewComment > ,
210+ ) -> String {
211+ let mut body = String :: from ( "## Cpp-linter Review\n " ) ;
194212 for t in 0_usize ..=1 {
195213 let mut total = 0 ;
196214 let ( tool_name, tool_version) = if t == 0 {
@@ -209,9 +227,9 @@ impl ReviewComments {
209227 if let Some ( ver_str) = tool_version {
210228 body. push_str ( format ! ( "\n ### Used {tool_name} v{ver_str}\n " ) . as_str ( ) ) ;
211229 }
212- for comment in & self . comments {
230+ for comment in comments {
213231 if comment
214- . suggestion
232+ . comment
215233 . contains ( format ! ( "### {tool_name}" ) . as_str ( ) )
216234 {
217235 total += 1 ;
@@ -266,24 +284,17 @@ pub fn make_patch<'buffer>(
266284 path : & Path ,
267285 patched : & ' buffer [ u8 ] ,
268286 original_content : & ' buffer [ u8 ] ,
269- ) -> Result < Patch < ' buffer > > {
287+ ) -> Result < Patch < ' buffer > , git2 :: Error > {
270288 let mut diff_opts = & mut DiffOptions :: new ( ) ;
271289 diff_opts = diff_opts. indent_heuristic ( true ) ;
272290 diff_opts = diff_opts. context_lines ( 0 ) ;
273- let patch = Patch :: from_buffers (
291+ Patch :: from_buffers (
274292 original_content,
275293 Some ( path) ,
276294 patched,
277295 Some ( path) ,
278296 Some ( diff_opts) ,
279297 )
280- . with_context ( || {
281- format ! (
282- "Failed to create patch for file {}." ,
283- path. to_string_lossy( )
284- )
285- } ) ?;
286- Ok ( patch)
287298}
288299
289300/// A trait for generating suggestions from a [`FileObj`]'s advice's generated `patched` buffer.
@@ -301,7 +312,7 @@ pub trait MakeSuggestions {
301312 file_obj : & FileObj ,
302313 patch : & mut Patch ,
303314 summary_only : bool ,
304- ) -> Result < ( ) > {
315+ ) -> Result < ( ) , SuggestionError > {
305316 let is_tidy_tool = ( & self . get_tool_name ( ) == "clang-tidy" ) as usize ;
306317 let hunks_total = patch. num_hunks ( ) ;
307318 let mut hunks_in_patch = 0u32 ;
@@ -313,21 +324,32 @@ pub trait MakeSuggestions {
313324 . to_owned ( ) ;
314325 let patch_buf = & patch
315326 . to_buf ( )
316- . with_context ( || "Failed to convert patch to byte array" ) ?
327+ . map_err ( |e| SuggestionError :: PatchIntoBytesFailed {
328+ file_name : file_name. clone ( ) ,
329+ source : e,
330+ } ) ?
317331 . to_vec ( ) ;
318332 review_comments. full_patch [ is_tidy_tool] . push_str (
319333 String :: from_utf8 ( patch_buf. to_owned ( ) )
320- . with_context ( || format ! ( "Failed to convert patch to string: {file_name}" ) ) ?
334+ . map_err ( |e| SuggestionError :: PatchIntoStringFailed {
335+ file_name : file_name. clone ( ) ,
336+ source : e,
337+ } ) ?
321338 . as_str ( ) ,
322339 ) ;
323340 if summary_only {
324341 review_comments. tool_total [ is_tidy_tool] . get_or_insert ( 0 ) ;
325342 return Ok ( ( ) ) ;
326343 }
327344 for hunk_id in 0 ..hunks_total {
328- let ( hunk, line_count) = patch. hunk ( hunk_id) . with_context ( || {
329- format ! ( "Failed to get hunk {hunk_id} from patch for {file_name}" )
330- } ) ?;
345+ let ( hunk, line_count) =
346+ patch
347+ . hunk ( hunk_id)
348+ . map_err ( |e| SuggestionError :: GetHunkFailed {
349+ hunk_id,
350+ file_name : file_name. clone ( ) ,
351+ source : e,
352+ } ) ?;
331353 hunks_in_patch += 1 ;
332354 let hunk_range = file_obj. is_hunk_in_diff ( & hunk) ;
333355 match hunk_range {
@@ -337,11 +359,23 @@ pub trait MakeSuggestions {
337359 let suggestion_help = self . get_suggestion_help ( start_line, end_line) ;
338360 let mut removed = vec ! [ ] ;
339361 for line_index in 0 ..line_count {
340- let diff_line = patch
341- . line_in_hunk ( hunk_id, line_index)
342- . with_context ( || format ! ( "Failed to get line {line_index} in a hunk {hunk_id} of patch for {file_name}" ) ) ?;
343- let line = String :: from_utf8 ( diff_line. content ( ) . to_owned ( ) )
344- . with_context ( || format ! ( "Failed to convert line {line_index} buffer to string in hunk {hunk_id} of patch for {file_name}" ) ) ?;
362+ let diff_line = patch. line_in_hunk ( hunk_id, line_index) . map_err ( |e| {
363+ SuggestionError :: GetHunkLineFailed {
364+ line_index,
365+ hunk_id,
366+ file_name : file_name. clone ( ) ,
367+ source : e,
368+ }
369+ } ) ?;
370+ let line =
371+ String :: from_utf8 ( diff_line. content ( ) . to_owned ( ) ) . map_err ( |e| {
372+ SuggestionError :: HunkLineIntoStringFailed {
373+ line_index,
374+ hunk_id,
375+ file_name : file_name. clone ( ) ,
376+ source : e,
377+ }
378+ } ) ?;
345379 if [ '+' , ' ' ] . contains ( & diff_line. origin ( ) ) {
346380 suggestion. push_str ( line. as_str ( ) ) ;
347381 } else {
0 commit comments