7
7
import base64
8
8
import contextlib
9
9
import fnmatch
10
- import glob
11
10
import io
12
11
import itertools
13
12
import json
19
18
import textwrap
20
19
import zipfile
21
20
from operator import itemgetter
22
- from typing import Dict , List , Optional , TypedDict
21
+ from pathlib import Path
22
+ from typing import Any , Dict , List , Optional , TypedDict
23
23
24
24
import unidiff
25
25
import urllib3
26
26
import yaml
27
27
from github import Auth , Github
28
28
from github .PaginatedList import PaginatedList
29
+ from github .PullRequest import ReviewComment
29
30
from github .Requester import Requester
30
31
from github .WorkflowRun import WorkflowRun
31
32
@@ -46,20 +47,10 @@ class Metadata(TypedDict):
46
47
pr_number : int
47
48
48
49
49
- class PRReviewComment (TypedDict ):
50
- path : str
51
- position : Optional [int ]
52
- body : str
53
- line : Optional [int ]
54
- side : Optional [str ]
55
- start_line : Optional [int ]
56
- start_side : Optional [str ]
57
-
58
-
59
50
class PRReview (TypedDict ):
60
51
body : str
61
52
event : str
62
- comments : List [ PRReviewComment ]
53
+ comments : list [ ReviewComment ]
63
54
64
55
65
56
class HashableComment :
@@ -125,7 +116,7 @@ def add_auth_arguments(parser: argparse.ArgumentParser):
125
116
group_app .add_argument ("--installation-id" , type = int , help = "app installation ID" )
126
117
127
118
128
- def get_auth_from_arguments (args : argparse .Namespace ) -> Auth :
119
+ def get_auth_from_arguments (args : argparse .Namespace ) -> Auth . Auth :
129
120
if args .token :
130
121
return Auth .Token (args .token )
131
122
@@ -251,7 +242,7 @@ def config_file_or_checks(
251
242
def load_clang_tidy_warnings ():
252
243
"""Read clang-tidy warnings from FIXES_FILE. Can be produced by build_clang_tidy_warnings"""
253
244
try :
254
- with open (FIXES_FILE ) as fixes_file :
245
+ with Path (FIXES_FILE ). open ( ) as fixes_file :
255
246
return yaml .safe_load (fixes_file )
256
247
except FileNotFoundError :
257
248
return {}
@@ -260,7 +251,7 @@ def load_clang_tidy_warnings():
260
251
class PullRequest :
261
252
"""Add some convenience functions not in PyGithub"""
262
253
263
- def __init__ (self , repo : str , pr_number : Optional [int ], auth : Auth ) -> None :
254
+ def __init__ (self , repo : str , pr_number : Optional [int ], auth : Auth . Auth ) -> None :
264
255
self .repo_name = repo
265
256
self .pr_number = pr_number
266
257
self .auth = auth
@@ -402,12 +393,12 @@ def make_file_offset_lookup(filenames):
402
393
lookup = {}
403
394
404
395
for filename in filenames :
405
- with open (filename ) as file :
396
+ with Path (filename ). open ( ) as file :
406
397
lines = file .readlines ()
407
398
# Length of each line
408
399
line_lengths = map (len , lines )
409
400
# Cumulative sum of line lengths => offset at end of each line
410
- lookup [os . path . abspath ( filename )] = [
401
+ lookup [Path ( filename ). resolve (). as_posix ( )] = [
411
402
0 ,
412
403
* list (itertools .accumulate (line_lengths )),
413
404
]
@@ -427,22 +418,21 @@ def get_diagnostic_file_path(clang_tidy_diagnostic, build_dir):
427
418
file_path = clang_tidy_diagnostic ["DiagnosticMessage" ]["FilePath" ]
428
419
if file_path == "" :
429
420
return ""
430
- if os .path .isabs (file_path ):
431
- return os .path .normpath (os .path .abspath (file_path ))
421
+ file_path = Path (file_path )
422
+ if file_path .is_absolute ():
423
+ return os .path .normpath (file_path .resolve ())
432
424
if "BuildDirectory" in clang_tidy_diagnostic :
433
425
return os .path .normpath (
434
- os .path .abspath (
435
- os .path .join (clang_tidy_diagnostic ["BuildDirectory" ], file_path )
436
- )
426
+ (Path (clang_tidy_diagnostic ["BuildDirectory" ]) / file_path ).resolve ()
437
427
)
438
- return os .path .normpath (os . path . abspath ( file_path ))
428
+ return os .path .normpath (file_path . resolve ( ))
439
429
440
430
# Pre-clang-tidy-9 format
441
431
if "FilePath" in clang_tidy_diagnostic :
442
432
file_path = clang_tidy_diagnostic ["FilePath" ]
443
433
if file_path == "" :
444
434
return ""
445
- return os .path .normpath (os . path . abspath ( os . path . join (build_dir , file_path )))
435
+ return os .path .normpath (( Path (build_dir ) / file_path ). resolve ( ))
446
436
447
437
return ""
448
438
@@ -469,7 +459,7 @@ def find_line_number_from_offset(offset_lookup, filename, offset):
469
459
def read_one_line (filename , line_offset ):
470
460
"""Read a single line from a source file"""
471
461
# Could cache the files instead of opening them each time?
472
- with open (filename ) as file :
462
+ with Path (filename ). open ( ) as file :
473
463
file .seek (line_offset )
474
464
return file .readline ().rstrip ("\n " )
475
465
@@ -493,7 +483,7 @@ def collate_replacement_sets(diagnostic, offset_lookup):
493
483
# from the FilePath and we'll end up looking for a path that's not in
494
484
# the lookup dict
495
485
# To fix this, we'll convert all the FilePaths to absolute paths
496
- replacement ["FilePath" ] = os . path . abspath (replacement ["FilePath" ])
486
+ replacement ["FilePath" ] = Path (replacement ["FilePath" ]). resolve (). as_posix ( )
497
487
498
488
# It's possible the replacement is needed in another file?
499
489
# Not really sure how that could come about, but let's
@@ -540,7 +530,7 @@ def replace_one_line(replacement_set, line_num, offset_lookup):
540
530
line_offset = offset_lookup [filename ][line_num ]
541
531
542
532
# List of (start, end) offsets from line_offset
543
- insert_offsets = [(0 , 0 )]
533
+ insert_offsets : list [ tuple [ Optional [ int ], Optional [ int ]]] = [(0 , 0 )]
544
534
# Read all the source lines into a dict so we only get one copy of
545
535
# each line, though we might read the same line in multiple times
546
536
source_lines = {}
@@ -661,7 +651,7 @@ def fix_absolute_paths(build_compile_commands, base_dir):
661
651
print (f"Found '{ build_compile_commands } ', updating absolute paths" )
662
652
# We might need to change some absolute paths if we're inside
663
653
# a docker container
664
- with open (build_compile_commands ) as f :
654
+ with Path (build_compile_commands ). open ( ) as f :
665
655
compile_commands = json .load (f )
666
656
667
657
print (f"Replacing '{ basedir } ' with '{ newbasedir } '" , flush = True )
@@ -670,7 +660,7 @@ def fix_absolute_paths(build_compile_commands, base_dir):
670
660
str (basedir ), str (newbasedir )
671
661
)
672
662
673
- with open (build_compile_commands , "w" ) as f :
663
+ with Path (build_compile_commands ). open ( "w" ) as f :
674
664
f .write (modified_compile_commands )
675
665
676
666
@@ -756,7 +746,7 @@ def create_review_file(
756
746
if "Diagnostics" not in clang_tidy_warnings :
757
747
return None
758
748
759
- comments : List [PRReviewComment ] = []
749
+ comments : List [ReviewComment ] = []
760
750
761
751
for diagnostic in clang_tidy_warnings ["Diagnostics" ]:
762
752
try :
@@ -930,32 +920,34 @@ def create_review(
930
920
files = filter_files (diff , include , exclude )
931
921
932
922
if files == []:
933
- with message_group ("No files to check!" ):
934
- with open (REVIEW_FILE , "w" ) as review_file :
935
- json .dump (
936
- {
937
- "body" : "clang-tidy found no files to check" ,
938
- "event" : "COMMENT" ,
939
- "comments" : [],
940
- },
941
- review_file ,
942
- )
923
+ with message_group ("No files to check!" ), Path (REVIEW_FILE ).open (
924
+ "w"
925
+ ) as review_file :
926
+ json .dump (
927
+ {
928
+ "body" : "clang-tidy found no files to check" ,
929
+ "event" : "COMMENT" ,
930
+ "comments" : [],
931
+ },
932
+ review_file ,
933
+ )
943
934
return None
944
935
945
936
print (f"Checking these files: { files } " , flush = True )
946
937
947
938
line_ranges = get_line_ranges (diff , files )
948
939
if line_ranges == "[]" :
949
- with message_group ("No lines added in this PR!" ):
950
- with open (REVIEW_FILE , "w" ) as review_file :
951
- json .dump (
952
- {
953
- "body" : "clang-tidy found no lines added" ,
954
- "event" : "COMMENT" ,
955
- "comments" : [],
956
- },
957
- review_file ,
958
- )
940
+ with message_group ("No lines added in this PR!" ), Path (REVIEW_FILE ).open (
941
+ "w"
942
+ ) as review_file :
943
+ json .dump (
944
+ {
945
+ "body" : "clang-tidy found no lines added" ,
946
+ "event" : "COMMENT" ,
947
+ "comments" : [],
948
+ },
949
+ review_file ,
950
+ )
959
951
return None
960
952
961
953
print (f"Line filter for clang-tidy:\n { line_ranges } \n " )
@@ -997,7 +989,7 @@ def create_review(
997
989
review = create_review_file (
998
990
clang_tidy_warnings , diff_lookup , offset_lookup , build_dir
999
991
)
1000
- with open (REVIEW_FILE , "w" ) as review_file :
992
+ with Path (REVIEW_FILE ). open ( "w" ) as review_file :
1001
993
json .dump (review , review_file )
1002
994
1003
995
return review
@@ -1049,7 +1041,7 @@ def load_metadata() -> Optional[Metadata]:
1049
1041
print (f"WARNING: Could not find metadata file ('{ METADATA_FILE } ')" , flush = True )
1050
1042
return None
1051
1043
1052
- with open (METADATA_FILE ) as metadata_file :
1044
+ with Path (METADATA_FILE ). open ( ) as metadata_file :
1053
1045
return json .load (metadata_file )
1054
1046
1055
1047
@@ -1058,7 +1050,7 @@ def save_metadata(pr_number: int) -> None:
1058
1050
1059
1051
metadata : Metadata = {"pr_number" : pr_number }
1060
1052
1061
- with open (METADATA_FILE , "w" ) as metadata_file :
1053
+ with Path (METADATA_FILE ). open ( "w" ) as metadata_file :
1062
1054
json .dump (metadata , metadata_file )
1063
1055
1064
1056
@@ -1069,15 +1061,15 @@ def load_review(review_file: pathlib.Path) -> Optional[PRReview]:
1069
1061
print (f"WARNING: Could not find review file ('{ review_file } ')" , flush = True )
1070
1062
return None
1071
1063
1072
- with open (review_file ) as review_file_handle :
1064
+ with review_file . open () as review_file_handle :
1073
1065
payload = json .load (review_file_handle )
1074
1066
return payload or None
1075
1067
1076
1068
1077
1069
def load_and_merge_profiling () -> Dict :
1078
1070
result = {}
1079
- for profile_file in glob . iglob ( os . path . join ( PROFILE_DIR , "*.json" ) ):
1080
- profile_dict = json .load (open (profile_file ))
1071
+ for profile_file in Path ( PROFILE_DIR ). glob ( "*.json" ):
1072
+ profile_dict = json .load (profile_file . open ())
1081
1073
filename = profile_dict ["file" ]
1082
1074
current_profile = result .get (filename , {})
1083
1075
for check , timing in profile_dict ["profile" ].items ():
@@ -1150,7 +1142,7 @@ def get_line_ranges(diff, files):
1150
1142
# Adding a copy of the line filters with backslashes allows for both cl.exe and clang.exe to work.
1151
1143
if os .path .sep == "\\ " :
1152
1144
# Converts name to backslashes for the cl.exe line filter.
1153
- name = os . path . join (* name .split ("/" ))
1145
+ name = Path . joinpath (* name .split ("/" ))
1154
1146
line_filter_json .append ({"name" : name , "lines" : lines })
1155
1147
return json .dumps (line_filter_json , separators = ("," , ":" ))
1156
1148
@@ -1196,7 +1188,7 @@ def set_output(key: str, val: str) -> bool:
1196
1188
return False
1197
1189
1198
1190
# append key-val pair to file
1199
- with open (os .environ ["GITHUB_OUTPUT" ], "a" ) as f :
1191
+ with Path (os .environ ["GITHUB_OUTPUT" ]). open ( "a" ) as f :
1200
1192
f .write (f"{ key } ={ val } \n " )
1201
1193
1202
1194
return True
@@ -1207,7 +1199,7 @@ def set_summary(val: str) -> bool:
1207
1199
return False
1208
1200
1209
1201
# append key-val pair to file
1210
- with open (os .environ ["GITHUB_STEP_SUMMARY" ], "a" ) as f :
1202
+ with Path (os .environ ["GITHUB_STEP_SUMMARY" ]). open ( "a" ) as f :
1211
1203
f .write (val )
1212
1204
1213
1205
return True
@@ -1223,10 +1215,10 @@ def decorate_check_names(comment: str) -> str:
1223
1215
url = f"https://clang.llvm.org/{ version } /clang-tidy/checks"
1224
1216
regex = r"(\[((?:clang-analyzer)|(?:(?!clang)[\w]+))-([\.\w-]+)\]$)"
1225
1217
subst = f"[\\ g<1>({ url } /\\ g<2>/\\ g<3>.html)]"
1226
- return re .sub (regex , subst , comment , 1 , re .MULTILINE )
1218
+ return re .sub (regex , subst , comment , count = 1 , flags = re .MULTILINE )
1227
1219
1228
1220
1229
- def decorate_comment (comment : PRReviewComment ) -> PRReviewComment :
1221
+ def decorate_comment (comment : ReviewComment ) -> ReviewComment :
1230
1222
comment ["body" ] = decorate_check_names (comment ["body" ])
1231
1223
return comment
1232
1224
@@ -1287,10 +1279,12 @@ def convert_comment_to_annotations(comment):
1287
1279
}
1288
1280
1289
1281
1290
- def post_annotations (pull_request : PullRequest , review : Optional [PRReview ]) -> int :
1282
+ def post_annotations (
1283
+ pull_request : PullRequest , review : Optional [PRReview ]
1284
+ ) -> Optional [int ]:
1291
1285
"""Post the first 10 comments in the review as annotations"""
1292
1286
1293
- body = {
1287
+ body : dict [ str , Any ] = {
1294
1288
"name" : "clang-tidy-review" ,
1295
1289
"head_sha" : pull_request .pull_request .head .sha ,
1296
1290
"status" : "completed" ,
@@ -1308,7 +1302,7 @@ def post_annotations(pull_request: PullRequest, review: Optional[PRReview]) -> i
1308
1302
for comment in review ["comments" ]:
1309
1303
first_line = comment ["body" ].splitlines ()[0 ]
1310
1304
comments .append (
1311
- f"{ comment ['path' ]} :{ comment .get ('start_line' , comment [ 'line' ] )} : { first_line } "
1305
+ f"{ comment ['path' ]} :{ comment .get ('start_line' , comment . get ( 'line' , 0 ) )} : { first_line } "
1312
1306
)
1313
1307
1314
1308
total_comments = len (review ["comments" ])
0 commit comments