33Code format checker script
44Used to check if modified C/C++ files in PR comply with clang-format standards,
55detect Chinese characters in code, and validate file headers
6+
7+ Support modes:
8+ - PR mode (default): Check files modified in PR relative to base branch
9+ - Debug mode: Check specified local files or directories for development
610"""
711
812import os
1317import re
1418from pathlib import Path
1519from datetime import datetime
20+ import glob
1621
1722
1823class FormatChecker :
19- def __init__ (self , base_ref = "master" ):
24+ def __init__ (self , base_ref = "master" , debug_mode = False ):
2025 self .base_ref = base_ref
26+ self .debug_mode = debug_mode
2127 self .project_root = self ._find_project_root ()
2228 self .clang_format_ignore = self ._load_ignore_patterns ()
2329 self .current_year = datetime .now ().year
@@ -81,6 +87,52 @@ def _get_changed_files(self):
8187 print (f"Error: Failed to execute git command: { e } " )
8288 return []
8389
90+ def _get_local_files (self , file_patterns = None , directories = None ):
91+ """Get local C/C++ files for debug mode"""
92+ files = []
93+ c_cpp_extensions = ('.c' , '.cpp' , '.h' , '.hpp' , '.cc' , '.cxx' )
94+
95+ # Process specified files
96+ if file_patterns :
97+ for pattern in file_patterns :
98+ # Support glob patterns
99+ matched_files = glob .glob (pattern , recursive = True )
100+ for file_path in matched_files :
101+ path_obj = Path (file_path )
102+ if path_obj .is_file () and path_obj .suffix in c_cpp_extensions :
103+ rel_path = path_obj .relative_to (self .project_root ) if path_obj .is_absolute () else path_obj
104+ if not self ._should_ignore_file (str (rel_path )):
105+ files .append (str (rel_path ))
106+
107+ # Process specified directories
108+ if directories :
109+ for directory in directories :
110+ dir_path = Path (directory )
111+ if not dir_path .exists ():
112+ print (f"Warning: Directory does not exist: { directory } " )
113+ continue
114+
115+ # Find all C/C++ files in directory recursively
116+ for ext in c_cpp_extensions :
117+ pattern = f"**/*{ ext } "
118+ for file_path in dir_path .glob (pattern ):
119+ rel_path = file_path .relative_to (self .project_root ) if file_path .is_absolute () else file_path
120+ if not self ._should_ignore_file (str (rel_path )):
121+ files .append (str (rel_path ))
122+
123+ # If no files or directories specified, scan current directory
124+ if not file_patterns and not directories :
125+ current_dir = Path .cwd ()
126+ for ext in c_cpp_extensions :
127+ pattern = f"**/*{ ext } "
128+ for file_path in current_dir .glob (pattern ):
129+ rel_path = file_path .relative_to (self .project_root ) if file_path .is_absolute () else file_path
130+ if not self ._should_ignore_file (str (rel_path )):
131+ files .append (str (rel_path ))
132+
133+ # Remove duplicates and return sorted list
134+ return sorted (list (set (files )))
135+
84136 def _check_clang_format_available (self ):
85137 """Check if clang-format is available"""
86138 try :
@@ -125,10 +177,7 @@ def _show_diff(self, file_path, original, formatted):
125177
126178 diff_content = '' .join (diff )
127179 if diff_content :
128- print (f"\n File { file_path } does not conform to format:" )
129- print ("=" * 60 )
130- print (diff_content )
131- print ("=" * 60 )
180+ print (f"❌ File { file_path } does not conform to format standards" )
132181 return True
133182 return False
134183
@@ -163,12 +212,7 @@ def _check_chinese_characters(self, file_path):
163212 def _show_chinese_errors (self , file_path , chinese_errors ):
164213 """Show Chinese character errors for file"""
165214 if chinese_errors :
166- print (f"\n File { file_path } contains Chinese characters:" )
167- print ("=" * 60 )
168- for error in chinese_errors :
169- print (f"Line { error ['line' ]} , Column { error ['column' ]} : '{ error ['character' ]} '" )
170- print (f" Context: { error ['context' ]} " )
171- print ("=" * 60 )
215+ print (f"❌ File { file_path } contains Chinese characters" )
172216 return True
173217 return False
174218
@@ -238,49 +282,38 @@ def _check_file_header(self, file_path):
238282 def _show_header_errors (self , file_path , header_errors ):
239283 """Show file header errors"""
240284 if header_errors :
241- print (f"\n File { file_path } has header issues:" )
242- print ("=" * 60 )
243- for error in header_errors :
244- print (f" - { error } " )
245- print ("\n Expected header format:" )
246- print ("/**" )
247- print (" * @file filename.c" )
248- print (" * @brief Brief description of the file" )
249- print (" *" )
250- print (" * Detailed description..." )
251- print (" *" )
252- print (f" * @copyright Copyright (c) 2021-{ self .current_year } Tuya Inc. All Rights Reserved." )
253- print (" */" )
254- print ("=" * 60 )
285+ print (f"❌ File { file_path } has invalid header format" )
255286 return True
256287 return False
257288
258289 def _show_header_warnings (self , file_path , header_warnings ):
259290 """Show file header warnings"""
260291 if header_warnings :
261- print (f"\n File { file_path } has header suggestions:" )
262- print ("~" * 60 )
263- for warning in header_warnings :
264- print (f" ⚠️ { warning } " )
265- print ("~" * 60 )
292+ print (f"⚠️ File { file_path } has header suggestions" )
266293 return True
267294 return False
268295
269- def check_format (self ):
296+ def check_format (self , file_patterns = None , directories = None ):
270297 """Check code format, Chinese characters, and file headers"""
271298 if not self ._check_clang_format_available ():
272299 print ("Error: clang-format is not installed or not in PATH" )
273300 print ("Please install clang-format (recommended version 14)" )
274301 return False
275302
276- print (f"Checking files modified relative to { self .base_ref } branch..." )
277-
278- changed_files = self ._get_changed_files ()
279- if not changed_files :
280- print ("No C/C++ files need to be checked" )
281- return True
303+ if self .debug_mode :
304+ print ("🔧 Debug mode: Checking local files..." )
305+ changed_files = self ._get_local_files (file_patterns , directories )
306+ if not changed_files :
307+ print ("❌ No C/C++ files found matching the criteria" )
308+ return True
309+ else :
310+ print (f"📋 PR mode: Checking files modified relative to { self .base_ref } branch..." )
311+ changed_files = self ._get_changed_files ()
312+ if not changed_files :
313+ print ("✅ No C/C++ files need to be checked" )
314+ return True
282315
283- print (f"Found { len (changed_files )} modified C/C++ files :" )
316+ print (f"📁 Found { len (changed_files )} C/C++ file(s) to check :" )
284317 for file_path in changed_files :
285318 print (f" - { file_path } " )
286319
@@ -289,7 +322,6 @@ def check_format(self):
289322 header_errors = []
290323 header_warnings = []
291324
292- print ("\n --- Checking code format ---" )
293325 for file_path in changed_files :
294326 full_path = self .project_root / file_path
295327 original , formatted = self ._format_file_content (full_path )
@@ -298,7 +330,6 @@ def check_format(self):
298330 if self ._show_diff (file_path , original , formatted ):
299331 format_errors .append (file_path )
300332
301- print ("\n --- Checking Chinese characters ---" )
302333 for file_path in changed_files :
303334 full_path = self .project_root / file_path
304335 chinese_issues = self ._check_chinese_characters (full_path )
@@ -307,7 +338,6 @@ def check_format(self):
307338 if self ._show_chinese_errors (file_path , chinese_issues ):
308339 chinese_errors .append (file_path )
309340
310- print ("\n --- Checking file headers ---" )
311341 for file_path in changed_files :
312342 full_path = self .project_root / file_path
313343 errors , warnings = self ._check_file_header (full_path )
@@ -323,56 +353,87 @@ def check_format(self):
323353 has_errors = format_errors or chinese_errors or header_errors
324354
325355 if format_errors :
326- print (f"\n ❌ Found { len (format_errors )} files that do not conform to format:" )
356+ print (f"\n ❌ Found { len (format_errors )} file(s) that do not conform to format:" )
327357 for file_path in format_errors :
328358 print (f" - { file_path } " )
329- print ("\n Please run the following command to fix format issues:" )
330- print ("clang-format -style=file -i " + " " .join (format_errors ))
331359
332360 if chinese_errors :
333- print (f"\n ❌ Found { len (chinese_errors )} files containing Chinese characters:" )
361+ print (f"\n ❌ Found { len (chinese_errors )} file(s) containing Chinese characters:" )
334362 for file_path in chinese_errors :
335363 print (f" - { file_path } " )
336- print ("\n Please remove all Chinese characters from the code." )
337- print ("Chinese comments and text are not allowed in the codebase." )
338364
339365 if header_errors :
340- print (f"\n ❌ Found { len (header_errors )} files with header issues:" )
366+ print (f"\n ❌ Found { len (header_errors )} file(s) with header issues:" )
341367 for file_path in header_errors :
342368 print (f" - { file_path } " )
343- print (f"\n Please update file headers to include:" )
344- print ("- Proper @file, @brief, and @copyright tags" )
345- print (f"- Copyright year ending with { self .current_year } " )
346369
347370 if header_warnings :
348- print (f"\n ⚠️ Found { len (header_warnings )} files with header suggestions:" )
371+ print (f"\n ⚠️ Found { len (header_warnings )} file(s) with header suggestions:" )
349372 for file_path in header_warnings :
350373 print (f" - { file_path } " )
351374
352375 if not has_errors and not header_warnings :
353- print ("\n ✅ All modified files conform to format standards, contain no Chinese characters, and have proper headers!" )
376+ print ("\n ✅ All files conform to format standards, contain no Chinese characters, and have proper headers!" )
354377 elif not has_errors :
355- print ("\n ✅ All modified files pass required checks! (Some suggestions above)" )
378+ print ("\n ✅ All files pass required checks! (Some suggestions above)" )
356379
357380 return not has_errors
358381
359382
360383def main ():
361- parser = argparse .ArgumentParser (description = "Check if code format complies with clang-format standards, detect Chinese characters, and validate file headers" )
384+ parser = argparse .ArgumentParser (
385+ description = "Check if code format complies with clang-format standards, detect Chinese characters, and validate file headers" ,
386+ formatter_class = argparse .RawTextHelpFormatter
387+ )
388+
389+ # Mode selection
390+ parser .add_argument ("--debug" , "--local" , action = "store_true" ,
391+ help = "Enable debug mode to check local specified files instead of PR files" )
392+
393+ # PR mode options
362394 parser .add_argument ("--base" , default = "master" ,
363395 help = "Base branch name (default: master)" )
396+
397+ # Debug mode options
398+ parser .add_argument ("--files" , nargs = "+" , metavar = "FILE" ,
399+ help = "Debug mode: Specify files to check (supports wildcards)" )
400+ parser .add_argument ("--dir" , "--directories" , nargs = "+" , metavar = "DIR" ,
401+ help = "Debug mode: Specify directories to check (recursive)" )
402+
403+ # General options
364404 parser .add_argument ("--verbose" , "-v" , action = "store_true" ,
365405 help = "Show verbose information" )
366406
367407 args = parser .parse_args ()
368408
409+ # Validation
410+ if args .debug and (args .files or args .dir ):
411+ pass # Valid: debug mode with specified files/dirs
412+ elif args .debug :
413+ pass # Valid: debug mode scanning current directory
414+ elif not args .debug and (args .files or args .dir ):
415+ print ("Error: --files and --dir arguments can only be used in debug mode (--debug)" )
416+ sys .exit (1 )
417+
369418 if args .verbose :
370419 print (f"Project root: { Path .cwd ()} " )
371- print (f"Base branch: { args .base } " )
420+ if args .debug :
421+ print ("Run mode: Debug mode" )
422+ if args .files :
423+ print (f"Specified files: { args .files } " )
424+ if args .dir :
425+ print (f"Specified directories: { args .dir } " )
426+ else :
427+ print ("Run mode: PR check mode" )
428+ print (f"Base branch: { args .base } " )
372429 print (f"Current year: { datetime .now ().year } " )
373430
374- checker = FormatChecker (base_ref = args .base )
375- success = checker .check_format ()
431+ checker = FormatChecker (base_ref = args .base , debug_mode = args .debug )
432+
433+ if args .debug :
434+ success = checker .check_format (file_patterns = args .files , directories = args .dir )
435+ else :
436+ success = checker .check_format ()
376437
377438 sys .exit (0 if success else 1 )
378439
0 commit comments