1818"""
1919
2020import argparse
21+ import github
22+ import json
2123from operator import attrgetter
2224import os
2325import subprocess
2426import sys
25- from typing import List , Optional
27+ from typing import Any , Dict , Final , List , Optional
2628
2729
2830class LintArgs :
29- start_rev : str = None
30- end_rev : str = None
31- repo : str = None
32- changed_files : List [str ] = []
33- token : str = None
31+ start_rev : Optional [ str ] = None
32+ end_rev : Optional [ str ] = None
33+ repo : Optional [ str ] = None
34+ changed_files : Optional [str ] = None
35+ token : Optional [ str ] = None
3436 verbose : bool = True
3537 issue_number : int = 0
3638 build_path : str = "build"
3739 clang_tidy_binary : str = "clang-tidy"
38- doc8_binary : str = "doc8"
3940
4041 def __init__ (self , args : argparse .Namespace ) -> None :
4142 if not args is None :
4243 self .start_rev = args .start_rev
4344 self .end_rev = args .end_rev
4445 self .repo = args .repo
4546 self .token = args .token
46- if args .changed_files :
47- self .changed_files = args .changed_files .split ("," )
48- else :
49- self .changed_files = []
47+ self .changed_files = args .changed_files
5048 self .issue_number = args .issue_number
5149 self .verbose = args .verbose
5250 self .build_path = args .build_path
5351 self .clang_tidy_binary = args .clang_tidy_binary
54- self .doc8_binary = args .doc8_binary
5552
5653
5754class LintHelper :
5855 COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: {linter}-->"
5956 name : str
6057 friendly_name : str
61- comment : dict = None
58+ comment : Optional [ Dict [ str , Any ]] = None
6259
6360 @property
6461 def comment_tag (self ) -> str :
6562 return self .COMMENT_TAG .format (linter = self .name )
6663
67- @property
6864 def instructions (self ) -> str :
6965 raise NotImplementedError ()
7066
71- def filter_changed_files (self , changed_files : List [ str ] ) -> List [str ]:
67+ def filter_changed_files (self ) -> List [str ]:
7268 raise NotImplementedError ()
7369
74- def run_linter_tool (
75- self , files_to_lint : List [str ], args : LintArgs
76- ) -> Optional [str ]:
70+ def run_linter_tool (self ) -> Optional [str ]:
7771 raise NotImplementedError ()
7872
79- def pr_comment_text_for_diff (
80- self , linter_output : str , files_to_lint : List [str ], args : LintArgs
81- ) -> str :
82- instructions = self .instructions (files_to_lint , args )
73+ def create_comment_text (self , linter_output : str ) -> str :
74+ instructions = self .instructions ()
8375 return f"""
8476:warning: { self .friendly_name } , { self .name } found issues in your code. :warning:
8577
@@ -107,7 +99,7 @@ def pr_comment_text_for_diff(
10799"""
108100
109101 # TODO: Refactor this
110- def find_comment (self , pr : any ) -> any :
102+ def find_comment (self , pr : Any ) -> Any :
111103 all_linter_names = list (map (attrgetter ("name" ), ALL_LINTERS ))
112104 other_linter_names = [name for name in all_linter_names if name != self .name ]
113105
@@ -124,27 +116,25 @@ def find_comment(self, pr: any) -> any:
124116 return None
125117
126118 def update_pr (self , comment_text : str , args : LintArgs , create_new : bool ) -> None :
127- import github
128- from github import IssueComment , PullRequest
129-
119+ assert args .repo is not None
130120 repo = github .Github (args .token ).get_repo (args .repo )
131121 pr = repo .get_issue (args .issue_number ).as_pull_request ()
132122
133- comment_text = self .comment_tag + " \n \n " + comment_text
123+ comment_text = f" { self .comment_tag } \n \n { comment_text } "
134124
135125 existing_comment = self .find_comment (pr )
136126
137- if create_new or existing_comment :
138- self .comment = {"body" : comment_text }
139127 if existing_comment :
140- self .comment ["id" ] = existing_comment .id
128+ self .comment = {"body" : comment_text , "id" : existing_comment .id }
129+ elif create_new :
130+ self .comment = {"body" : comment_text }
141131
142132
143- def run (self , args : LintArgs ) -> bool :
133+ def run (self , changed_files : List [ str ], args : LintArgs ) -> bool :
144134 if args .verbose :
145- print (f"got changed files: { args . changed_files } " )
135+ print (f"got changed files: { changed_files } " )
146136
147- files_to_lint = self .filter_changed_files (args . changed_files )
137+ files_to_lint = self .filter_changed_files ()
148138
149139 if not files_to_lint and args .verbose :
150140 print ("no modified files found" )
@@ -153,7 +143,7 @@ def run(self, args: LintArgs) -> bool:
153143 linter_output = None
154144
155145 if files_to_lint :
156- linter_output = self .run_linter_tool (files_to_lint , args )
146+ linter_output = self .run_linter_tool ()
157147 if linter_output :
158148 is_success = False
159149
@@ -170,11 +160,11 @@ def run(self, args: LintArgs) -> bool:
170160 else :
171161 if should_update_gh :
172162 if linter_output :
173- comment_text = self .pr_comment_text_for_diff (
174- linter_output , files_to_lint , args
175- )
163+ comment_text = self .create_comment_text (linter_output )
176164 self .update_pr (comment_text , args , create_new = True )
177165 else :
166+ # The linter failed but didn't output a result (e.g. some sort of
167+ # infrastructure failure).
178168 comment_text = (
179169 f":warning: The { self .friendly_name } failed without printing "
180170 "an output. Check the logs for output. :warning:"
@@ -193,14 +183,14 @@ def run(self, args: LintArgs) -> bool:
193183
194184
195185class ClangTidyLintHelper (LintHelper ):
196- name = "clang-tidy"
197- friendly_name = "C/C++ code linter"
186+ name : Final [ str ] = "clang-tidy"
187+ friendly_name : Final [ str ] = "C/C++ code linter"
198188
199189 def instructions (self , cpp_files : List [str ], args : LintArgs ) -> str :
200190 files_str = " " .join (cpp_files )
201191 return f"""
202192git diff -U0 origin/main...HEAD -- { files_str } |
203- python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
193+ python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \
204194 -path { args .build_path } -p1 -quiet"""
205195
206196 def filter_changed_files (self , changed_files : List [str ]) -> List [str ]:
@@ -211,7 +201,7 @@ def filter_changed_files(self, changed_files: List[str]) -> List[str]:
211201 filtered_files = []
212202 for filepath in clang_tidy_changed_files :
213203 _ , ext = os .path .splitext (filepath )
214- if ext not in (".cpp " , ".c " , ".h " , ".hpp " , ".hxx " , ".cxx " ):
204+ if ext not in (".c " , ".cpp " , ".cxx " , ".h " , ".hpp " , ".hxx " ):
215205 continue
216206 if not self ._should_lint_file (filepath ):
217207 continue
@@ -277,6 +267,11 @@ def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str]
277267 return clean_output
278268
279269 def _clean_clang_tidy_output (self , output : str ) -> Optional [str ]:
270+ """
271+ - Remove 'Running clang-tidy in X threads...' line
272+ - Remove 'N warnings generated.' line
273+ - Strip leading workspace path from file paths
274+ """
280275 if not output or output == "No relevant changes found." :
281276 return None
282277
@@ -287,6 +282,7 @@ def _clean_clang_tidy_output(self, output: str) -> Optional[str]:
287282 if line .startswith ("Running clang-tidy in" ) or line .endswith ("generated." ):
288283 continue
289284
285+ # Remove everything up to rightmost "llvm-project/" for correct files names
290286 idx = line .rfind ("llvm-project/" )
291287 if idx != - 1 :
292288 line = line [idx + len ("llvm-project/" ) :]
@@ -298,58 +294,7 @@ def _clean_clang_tidy_output(self, output: str) -> Optional[str]:
298294 return None
299295
300296
301- class Doc8LintHelper (LintHelper ):
302- name = "doc8"
303- friendly_name = "documentation linter"
304-
305- def instructions (self , doc_files : List [str ], args : LintArgs ) -> str :
306- files_str = " " .join (doc_files )
307- return f"doc8 -q { files_str } "
308-
309- def filter_changed_files (self , changed_files : List [str ]) -> List [str ]:
310- filtered_files = []
311- for filepath in changed_files :
312- _ , ext = os .path .splitext (filepath )
313- if ext not in (".rst" ):
314- continue
315- if not filepath .startswith ("clang-tools-extra/docs/clang-tidy/checks/" ):
316- continue
317- if os .path .exists (filepath ):
318- filtered_files .append (filepath )
319- return filtered_files
320-
321- def run_linter_tool (self , doc_files : List [str ], args : LintArgs ) -> Optional [str ]:
322- if not doc_files :
323- return None
324-
325- doc8_cmd = [args .doc8_binary , "-q" ] + doc_files
326-
327- if args .verbose :
328- print (f"Running doc8: { ' ' .join (doc8_cmd )} " )
329-
330- proc = subprocess .run (
331- doc8_cmd ,
332- stdout = subprocess .PIPE ,
333- stderr = subprocess .PIPE ,
334- text = True ,
335- check = False ,
336- )
337-
338- if proc .returncode == 0 :
339- return None
340-
341- output = proc .stdout .strip ()
342- if output :
343- return output
344-
345- error_output = proc .stderr .strip ()
346- if error_output :
347- return error_output
348-
349- return f"doc8 exited with return code { proc .returncode } but no output."
350-
351-
352- ALL_LINTERS = (ClangTidyLintHelper (), Doc8LintHelper ())
297+ ALL_LINTERS = (ClangTidyLintHelper (),)
353298
354299
355300if __name__ == "__main__" :
@@ -390,42 +335,39 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str]
390335 default = "clang-tidy" ,
391336 help = "Path to clang-tidy binary" ,
392337 )
393- parser .add_argument (
394- "--doc8-binary" ,
395- type = str ,
396- default = "doc8" ,
397- help = "Path to doc8 binary" ,
398- )
399338 parser .add_argument (
400339 "--verbose" , action = "store_true" , default = True , help = "Verbose output"
401340 )
402341
403342 parsed_args = parser .parse_args ()
404343 args = LintArgs (parsed_args )
405344
345+ changed_files = []
346+ if args .changed_files :
347+ changed_files = args .changed_files .split ("," )
348+
406349 overall_success = True
407- failed_linters = []
408- all_comments = []
350+ failed_linters : List [ str ] = []
351+ all_comments : List [ Dict [ str , Any ]] = []
409352
410353 for linter in ALL_LINTERS :
411354 if args .verbose :
412355 print (f"running linter { linter .name } " )
413356
414- linter_passed = linter .run (args )
357+ linter_passed = linter .run (changed_files , args )
415358 if not linter_passed :
416359 overall_success = False
417360 failed_linters .append (linter .name )
418361 if args .verbose :
419362 print (f"linter { linter .name } failed" )
420363
364+ # Write comments file if we have a comment
421365 if linter .comment :
422366 if args .verbose :
423367 print (f"linter { linter .name } has comment: { linter .comment } " )
424368 all_comments .append (linter .comment )
425369
426370 if len (all_comments ):
427- import json
428-
429371 existing_comments = []
430372 if os .path .exists ("comments" ):
431373 try :
0 commit comments