44Comprehensive code quality analysis for the A.YLM project
55"""
66
7- import os
87import ast
9- import sys
108import re
9+ import sys
1110from pathlib import Path
1211
1312
1413class CodeQualityReviewer :
15- def __init__ (self , root_dir = '.' ):
14+ def __init__ (self , root_dir = "." ):
1615 self .root_dir = Path (root_dir )
1716 self .issues = []
1817
1918 def add_issue (self , file_path , line_num , category , severity , description ):
20- self .issues .append ({
21- 'file' : str (file_path ),
22- 'line' : line_num ,
23- 'category' : category ,
24- 'severity' : severity ,
25- 'description' : description
26- })
19+ self .issues .append (
20+ {
21+ "file" : str (file_path ),
22+ "line" : line_num ,
23+ "category" : category ,
24+ "severity" : severity ,
25+ "description" : description ,
26+ }
27+ )
2728
2829 def check_python_files (self ):
29- print (' Checking Python files...' )
30- for py_file in self .root_dir .rglob (' *.py' ):
31- if ' ml-sharp' in str (py_file ):
30+ print (" Checking Python files..." )
31+ for py_file in self .root_dir .rglob (" *.py" ):
32+ if " ml-sharp" in str (py_file ):
3233 continue # Skip ml-sharp submodule
3334 self ._check_single_python_file (py_file )
3435
3536 def _check_single_python_file (self , file_path ):
3637 try :
37- with open (file_path , 'r' , encoding = ' utf-8' ) as f :
38+ with open (file_path , "r" , encoding = " utf-8" ) as f :
3839 content = f .read ()
39- lines = content .split (' \n ' )
40+ lines = content .split (" \n " )
4041
4142 # Syntax check
4243 try :
4344 ast .parse (content )
4445 except SyntaxError as e :
45- self .add_issue (file_path , e .lineno or 0 , 'syntax' , 'critical' , f'Syntax error: { e .msg } ' )
46+ self .add_issue (
47+ file_path ,
48+ e .lineno or 0 ,
49+ "syntax" ,
50+ "critical" ,
51+ f"Syntax error: { e .msg } " ,
52+ )
4653 return
4754
4855 # Check each line
4956 for i , line in enumerate (lines , 1 ):
5057 stripped = line .strip ()
5158
5259 # Check trailing whitespace
53- if line .rstrip () != line .rstrip ('\n ' ):
54- self .add_issue (file_path , i , 'formatting' , 'minor' , 'Trailing whitespace' )
60+ if line .rstrip () != line .rstrip ("\n " ):
61+ self .add_issue (
62+ file_path , i , "formatting" , "minor" , "Trailing whitespace"
63+ )
5564
5665 # Check line length
5766 if len (line .rstrip ()) > 100 :
58- self .add_issue (file_path , i , 'formatting' , 'minor' , f'Line too long ({ len (line .rstrip ())} characters)' )
67+ self .add_issue (
68+ file_path ,
69+ i ,
70+ "formatting" ,
71+ "minor" ,
72+ f"Line too long ({ len (line .rstrip ())} characters)" ,
73+ )
5974
6075 # Check tabs
61- if '\t ' in line :
62- self .add_issue (file_path , i , 'formatting' , 'minor' , 'Tab character found' )
76+ if "\t " in line :
77+ self .add_issue (
78+ file_path , i , "formatting" , "minor" , "Tab character found"
79+ )
6380
6481 # Check print statements without parentheses (Python 2 style)
65- if re .search (r'\bprint\s+[^(]' , stripped ):
66- self .add_issue (file_path , i , 'compatibility' , 'major' , 'Print statement without parentheses' )
82+ if re .search (r"\bprint\s+[^(]" , stripped ):
83+ self .add_issue (
84+ file_path ,
85+ i ,
86+ "compatibility" ,
87+ "major" ,
88+ "Print statement without parentheses" ,
89+ )
6790
6891 # Check bare except clauses
69- if re .search (r'\bexcept\s*:' , stripped ) and not re .search (r'\bexcept\s+\w+' , stripped ):
70- self .add_issue (file_path , i , 'error_handling' , 'major' , 'Bare except clause' )
92+ if re .search (r"\bexcept\s*:" , stripped ) and not re .search (
93+ r"\bexcept\s+\w+" , stripped
94+ ):
95+ self .add_issue (
96+ file_path , i , "error_handling" , "major" , "Bare except clause"
97+ )
7198
7299 # Check TODO/FIXME comments
73- if 'TODO' in line .upper () or 'FIXME' in line .upper ():
74- self .add_issue (file_path , i , 'maintenance' , 'info' , 'TODO/FIXME comment found' )
100+ if "TODO" in line .upper () or "FIXME" in line .upper ():
101+ self .add_issue (
102+ file_path , i , "maintenance" , "info" , "TODO/FIXME comment found"
103+ )
75104
76105 # Check unreachable code patterns
77- if stripped .startswith (' return ' ) or stripped .startswith (' raise ' ):
106+ if stripped .startswith (" return " ) or stripped .startswith (" raise " ):
78107 # Check if there's code after return/raise in the same logical block
79108 indent_level = len (line ) - len (line .lstrip ())
80109 for j in range (i , min (i + 5 , len (lines ))):
81110 next_line = lines [j ].strip ()
82- if next_line and not next_line .startswith ('#' ):
111+ if next_line and not next_line .startswith ("#" ):
83112 next_indent = len (lines [j ]) - len (lines [j ].lstrip ())
84- if next_indent > indent_level and not next_line .startswith (('except' , 'finally' , 'else:' )):
85- self .add_issue (file_path , j + 1 , 'logic' , 'major' , 'Potentially unreachable code after return/raise' )
113+ if next_indent > indent_level and not next_line .startswith (
114+ ("except" , "finally" , "else:" )
115+ ):
116+ self .add_issue (
117+ file_path ,
118+ j + 1 ,
119+ "logic" ,
120+ "major" ,
121+ "Potentially unreachable code after return/raise" ,
122+ )
86123 break
87124
88125 except Exception as e :
89- self .add_issue (file_path , 0 , 'file_error' , 'major' , f'Could not read file: { e } ' )
126+ self .add_issue (
127+ file_path , 0 , "file_error" , "major" , f"Could not read file: { e } "
128+ )
90129
91130 def check_shell_scripts (self ):
92- print (' Checking shell scripts...' )
93- for sh_file in self .root_dir .rglob (' *.sh' ):
131+ print (" Checking shell scripts..." )
132+ for sh_file in self .root_dir .rglob (" *.sh" ):
94133 self ._check_single_shell_file (sh_file )
95134
96135 def _check_single_shell_file (self , file_path ):
97136 try :
98- with open (file_path , 'r' , encoding = ' utf-8' ) as f :
137+ with open (file_path , "r" , encoding = " utf-8" ) as f :
99138 content = f .read ()
100- lines = content .split (' \n ' )
139+ lines = content .split (" \n " )
101140
102141 for i , line in enumerate (lines , 1 ):
103142 stripped = line .strip ()
104143
105144 # Check for deprecated command substitutions
106- if re .search (r'`[^`]*`' , stripped ):
107- self .add_issue (file_path , i , 'compatibility' , 'minor' , 'Deprecated backtick command substitution' )
145+ if re .search (r"`[^`]*`" , stripped ):
146+ self .add_issue (
147+ file_path ,
148+ i ,
149+ "compatibility" ,
150+ "minor" ,
151+ "Deprecated backtick command substitution" ,
152+ )
108153
109154 # Check for unquoted variables
110155 if re .search (r'\\$\\w+[^"]' , stripped ):
111- self .add_issue (file_path , i , 'robustness' , 'minor' , 'Unquoted variable' )
156+ self .add_issue (
157+ file_path , i , "robustness" , "minor" , "Unquoted variable"
158+ )
112159
113160 # Check for set -e usage
114- if 'set -e' in content and i == 1 and 'set -e' not in stripped :
115- self .add_issue (file_path , 1 , 'robustness' , 'info' , 'Consider adding set -e for error handling' )
161+ if "set -e" in content and i == 1 and "set -e" not in stripped :
162+ self .add_issue (
163+ file_path ,
164+ 1 ,
165+ "robustness" ,
166+ "info" ,
167+ "Consider adding set -e for error handling" ,
168+ )
116169
117170 except Exception as e :
118- self .add_issue (file_path , 0 , 'file_error' , 'major' , f'Could not read file: { e } ' )
171+ self .add_issue (
172+ file_path , 0 , "file_error" , "major" , f"Could not read file: { e } "
173+ )
119174
120175 def check_config_files (self ):
121- print ('Checking configuration files...' )
122- config_files = ['requirements.txt' , 'pyproject.toml' , 'mypy.ini' , '.pre-commit-config.yaml' ]
176+ print ("Checking configuration files..." )
177+ config_files = [
178+ "requirements.txt" ,
179+ "pyproject.toml" ,
180+ "mypy.ini" ,
181+ ".pre-commit-config.yaml" ,
182+ ]
123183
124184 for config_file in config_files :
125185 config_path = self .root_dir / config_file
126186 if config_path .exists ():
127187 try :
128- with open (config_path , 'r' , encoding = ' utf-8' ) as f :
188+ with open (config_path , "r" , encoding = " utf-8" ) as f :
129189 content = f .read ()
130190
131191 # Basic checks for common config issues
132- if config_file == ' requirements.txt' :
133- lines = content .split (' \n ' )
192+ if config_file == " requirements.txt" :
193+ lines = content .split (" \n " )
134194 for i , line in enumerate (lines , 1 ):
135195 line = line .strip ()
136- if line and not line .startswith ('#' ):
196+ if line and not line .startswith ("#" ):
137197 # Check for unpinned versions
138- if '>=' in line or '==' not in line :
139- self .add_issue (config_path , i , 'security' , 'minor' , 'Unpinned dependency version' )
140-
141- elif config_file == 'pyproject.toml' :
142- if '[tool.black]' not in content :
143- self .add_issue (config_path , 0 , 'configuration' , 'info' , 'Consider adding Black configuration' )
198+ if ">=" in line or "==" not in line :
199+ self .add_issue (
200+ config_path ,
201+ i ,
202+ "security" ,
203+ "minor" ,
204+ "Unpinned dependency version" ,
205+ )
206+
207+ elif config_file == "pyproject.toml" :
208+ if "[tool.black]" not in content :
209+ self .add_issue (
210+ config_path ,
211+ 0 ,
212+ "configuration" ,
213+ "info" ,
214+ "Consider adding Black configuration" ,
215+ )
144216
145217 except Exception as e :
146- self .add_issue (config_path , 0 , 'file_error' , 'major' , f'Could not read config file: { e } ' )
218+ self .add_issue (
219+ config_path ,
220+ 0 ,
221+ "file_error" ,
222+ "major" ,
223+ f"Could not read config file: { e } " ,
224+ )
147225
148226 def generate_report (self ):
149- print (f' \n === Code Quality Review Report ===' )
150- print (f' Total issues found: { len (self .issues )} ' )
227+ print (" \n === Code Quality Review Report ===" )
228+ print (f" Total issues found: { len (self .issues )} " )
151229
152230 # Group issues by severity
153231 severity_counts = {}
154232 category_counts = {}
155233
156234 for issue in self .issues :
157- severity_counts [issue ['severity' ]] = severity_counts .get (issue ['severity' ], 0 ) + 1
158- category_counts [issue ['category' ]] = category_counts .get (issue ['category' ], 0 ) + 1
159-
160- print (f'\n Issues by severity:' )
235+ severity_counts [issue ["severity" ]] = (
236+ severity_counts .get (issue ["severity" ], 0 ) + 1
237+ )
238+ category_counts [issue ["category" ]] = (
239+ category_counts .get (issue ["category" ], 0 ) + 1
240+ )
241+
242+ print ("\n Issues by severity:" )
161243 for severity , count in severity_counts .items ():
162- print (f' { severity } : { count } ' )
244+ print (f" { severity } : { count } " )
163245
164- print (f' \n Issues by category:' )
246+ print (" \n Issues by category:" )
165247 for category , count in category_counts .items ():
166- print (f' { category } : { count } ' )
248+ print (f" { category } : { count } " )
167249
168250 # Show critical and major issues first
169- critical_major = [i for i in self .issues if i ['severity' ] in ['critical' , 'major' ]]
251+ critical_major = [
252+ i for i in self .issues if i ["severity" ] in ["critical" , "major" ]
253+ ]
170254 if critical_major :
171- print (f' \n === Critical/Major Issues ===' )
255+ print (" \n === Critical/Major Issues ===" )
172256 for issue in critical_major [:10 ]: # Show first 10
173- print (f'{ issue ["file" ]} :{ issue ["line" ]} [{ issue ["severity" ]} ] { issue ["description" ]} ' )
257+ print (
258+ f'{ issue ["file" ]} :{ issue ["line" ]} [{ issue ["severity" ]} ] { issue ["description" ]} '
259+ )
174260
175261 return self .issues
176262
@@ -184,14 +270,14 @@ def main():
184270 issues = reviewer .generate_report ()
185271
186272 # Exit with appropriate code
187- critical_issues = [i for i in issues if i [' severity' ] == ' critical' ]
273+ critical_issues = [i for i in issues if i [" severity" ] == " critical" ]
188274 if critical_issues :
189- print (f' \n Found { len (critical_issues )} critical issues' )
275+ print (f" \n Found { len (critical_issues )} critical issues" )
190276 return 1
191277 else :
192- print (f' \n No critical issues found' )
278+ print (" \n No critical issues found" )
193279 return 0
194280
195281
196282if __name__ == "__main__" :
197- sys .exit (main ())
283+ sys .exit (main ())
0 commit comments