1
+ #!/usr/bin/env python3
2
+ """
3
+ AI Code Reviewer - A Python script for automated code analysis and improvement suggestions.
4
+
5
+ This script analyzes Python code for common issues, style violations, and potential
6
+ improvements, providing detailed feedback to help developers write better code.
7
+ """
8
+
1
9
import ast
10
+ import sys
2
11
import pycodestyle
12
+ from typing import List , Set , Dict , Optional , Any
13
+ from dataclasses import dataclass
14
+ import re
15
+ from pathlib import Path
16
+
17
+ @dataclass
18
+ class CodeIssue :
19
+ """Data class to store information about code issues."""
20
+ line_number : int
21
+ issue_type : str
22
+ message : str
23
+ severity : str # 'HIGH', 'MEDIUM', 'LOW'
24
+
25
+ class AICodeReviewer :
26
+ """
27
+ A comprehensive code review tool that analyzes Python code for various issues
28
+ and provides improvement suggestions.
29
+ """
3
30
4
- class CodeReviewer :
5
31
def __init__ (self ):
6
- self .feedback = []
32
+ """Initialize the AICodeReviewer with empty issue lists and configuration."""
33
+ self .issues : List [CodeIssue ] = []
34
+ self .source_code : str = ""
35
+ self .ast_tree : Optional [ast .AST ] = None
36
+
37
+ # Configure severity levels for different types of issues
38
+ self .severity_levels : Dict [str , str ] = {
39
+ 'syntax_error' : 'HIGH' ,
40
+ 'undefined_variable' : 'HIGH' ,
41
+ 'style_violation' : 'MEDIUM' ,
42
+ 'missing_docstring' : 'MEDIUM' ,
43
+ 'comment_issue' : 'LOW' ,
44
+ 'complexity_issue' : 'MEDIUM'
45
+ }
46
+
47
+ def load_file (self , file_path : str ) -> bool :
48
+ """
49
+ Load Python code from a file.
7
50
8
- def analyze_python_code (self , code ):
51
+ Args:
52
+ file_path (str): Path to the Python file to analyze
53
+
54
+ Returns:
55
+ bool: True if file was successfully loaded, False otherwise
56
+ """
9
57
try :
10
- # Parse the Python code into an Abstract Syntax Tree (AST)
11
- tree = ast .parse (code )
58
+ with open (file_path , 'r' , encoding = 'utf-8' ) as file :
59
+ self .source_code = file .read ()
60
+ return True
61
+ except Exception as e :
62
+ self .issues .append (CodeIssue (
63
+ 0 ,
64
+ 'file_error' ,
65
+ f"Error loading file: { str (e )} " ,
66
+ 'HIGH'
67
+ ))
68
+ return False
69
+
70
+ def load_code (self , code : str ) -> None :
71
+ """
72
+ Load Python code from a string.
73
+
74
+ Args:
75
+ code (str): Python code to analyze
76
+ """
77
+ self .source_code = code
78
+
79
+ def analyze (self ) -> None :
80
+ """
81
+ Perform comprehensive code analysis by running all available checks.
82
+ """
83
+ self .issues = [] # Reset issues list before new analysis
84
+
85
+ # Parse AST
86
+ try :
87
+ self .ast_tree = ast .parse (self .source_code )
12
88
except SyntaxError as e :
13
- self .feedback .append (f"Syntax Error: { e } " )
89
+ self .issues .append (CodeIssue (
90
+ e .lineno or 0 ,
91
+ 'syntax_error' ,
92
+ f"Syntax Error: { str (e )} " ,
93
+ 'HIGH'
94
+ ))
14
95
return
15
96
16
- # Check for indentation errors and undefined variables
17
- self ._check_indentation (tree )
18
- self ._check_undefined_vars (tree )
97
+ # Run all analysis checks
98
+ self ._check_syntax ()
99
+ self ._check_style ()
100
+ self ._check_docstrings ()
101
+ self ._check_complexity ()
102
+ self ._check_variables ()
103
+ self ._check_comments ()
104
+ self ._check_best_practices ()
105
+
106
+ def _check_syntax (self ) -> None :
107
+ """Check for syntax errors and basic structural issues."""
108
+ for node in ast .walk (self .ast_tree ):
109
+ # Check for empty code blocks
110
+ if isinstance (node , (ast .For , ast .While , ast .If , ast .With )):
111
+ if not node .body :
112
+ self .issues .append (CodeIssue (
113
+ getattr (node , 'lineno' , 0 ),
114
+ 'syntax_error' ,
115
+ f"Empty { node .__class__ .__name__ } block found" ,
116
+ 'HIGH'
117
+ ))
19
118
20
- # Check code style using pycodestyle
21
- self ._check_code_style (code )
119
+ def _check_style (self ) -> None :
120
+ """Check code style using pycodestyle."""
121
+ style_guide = pycodestyle .StyleGuide (quiet = True )
122
+
123
+ # Create a temporary file for pycodestyle to analyze
124
+ temp_file = Path ('temp_code_review.py' )
125
+ try :
126
+ temp_file .write_text (self .source_code )
127
+ result = style_guide .check_files ([temp_file ])
128
+
129
+ for line_number , offset , code , text , doc in result ._deferred_print :
130
+ self .issues .append (CodeIssue (
131
+ line_number ,
132
+ 'style_violation' ,
133
+ f"{ code } : { text } " ,
134
+ 'MEDIUM'
135
+ ))
136
+ finally :
137
+ if temp_file .exists ():
138
+ temp_file .unlink ()
22
139
23
- # Check code comments
24
- self ._check_comments (code )
140
+ def _check_docstrings (self ) -> None :
141
+ """Check for missing or inadequate docstrings."""
142
+ for node in ast .walk (self .ast_tree ):
143
+ if isinstance (node , (ast .FunctionDef , ast .ClassDef , ast .Module )):
144
+ has_docstring = False
145
+ if node .body and isinstance (node .body [0 ], ast .Expr ):
146
+ if isinstance (node .body [0 ].value , ast .Str ):
147
+ has_docstring = True
148
+ # Check docstring quality
149
+ docstring = node .body [0 ].value .s
150
+ if len (docstring .strip ()) < 10 :
151
+ self .issues .append (CodeIssue (
152
+ node .lineno ,
153
+ 'docstring_quality' ,
154
+ f"Short or uninformative docstring in { node .__class__ .__name__ } " ,
155
+ 'LOW'
156
+ ))
157
+
158
+ if not has_docstring :
159
+ self .issues .append (CodeIssue (
160
+ node .lineno ,
161
+ 'missing_docstring' ,
162
+ f"Missing docstring in { node .__class__ .__name__ } " ,
163
+ 'MEDIUM'
164
+ ))
25
165
26
- def _check_indentation (self , tree ):
27
- for node in ast .walk (tree ):
166
+ def _check_complexity (self ) -> None :
167
+ """Check for code complexity issues."""
168
+ for node in ast .walk (self .ast_tree ):
169
+ # Check function complexity
28
170
if isinstance (node , ast .FunctionDef ):
29
- if node .body and not isinstance (node .body [0 ], ast .Expr ):
30
- self .feedback .append (f"Function '{ node .name } ' should have a docstring or 'pass' statement." )
31
- elif isinstance (node , (ast .For , ast .While , ast .If , ast .With )):
32
- if not isinstance (node .body [0 ], ast .Expr ):
33
- self .feedback .append (f"Indentation Error: Missing 'pass' statement for '{ ast .dump (node )} '." )
34
-
35
- def _check_undefined_vars (self , tree ):
36
- undefined_vars = set ()
37
- for node in ast .walk (tree ):
38
- if isinstance (node , ast .Name ) and isinstance (node .ctx , ast .Store ):
39
- undefined_vars .discard (node .id )
40
- elif isinstance (node , ast .Name ) and isinstance (node .ctx , ast .Load ):
41
- undefined_vars .add (node .id )
42
-
43
- for var in undefined_vars :
44
- self .feedback .append (f"Variable '{ var } ' is used but not defined." )
45
-
46
- def _check_code_style (self , code ):
47
- style_guide = pycodestyle .StyleGuide ()
48
- result = style_guide .check_code (code )
49
- if result .total_errors :
50
- self .feedback .append ("Code style issues found. Please check and fix them." )
51
-
52
- def _check_comments (self , code ):
53
- lines = code .split ('\n ' )
54
- for i , line in enumerate (lines ):
55
- if line .strip ().startswith ('#' ):
56
- # Check for empty comments or comments without space after '#'
57
- if len (line .strip ()) == 1 or line .strip ()[1 ] != ' ' :
58
- self .feedback .append (f"Improve comment style in line { i + 1 } : '{ line .strip ()} '" )
59
-
60
- def get_feedback (self ):
61
- return self .feedback
171
+ num_statements = len (list (ast .walk (node )))
172
+ if num_statements > 50 :
173
+ self .issues .append (CodeIssue (
174
+ node .lineno ,
175
+ 'complexity_issue' ,
176
+ f"Function '{ node .name } ' is too complex ({ num_statements } statements)" ,
177
+ 'MEDIUM'
178
+ ))
62
179
63
- if __name__ == "__main__" :
180
+ def _check_variables (self ) -> None :
181
+ """Check for undefined and unused variables."""
182
+ defined_vars : Set [str ] = set ()
183
+ used_vars : Set [str ] = set ()
184
+ builtins = set (dir (__builtins__ ))
185
+
186
+ for node in ast .walk (self .ast_tree ):
187
+ if isinstance (node , ast .Name ):
188
+ if isinstance (node .ctx , ast .Store ):
189
+ defined_vars .add (node .id )
190
+ elif isinstance (node .ctx , ast .Load ):
191
+ if node .id not in builtins :
192
+ used_vars .add (node .id )
193
+
194
+ # Check for undefined variables
195
+ undefined = used_vars - defined_vars
196
+ for var in undefined :
197
+ self .issues .append (CodeIssue (
198
+ 0 , # We don't have line numbers for this check
199
+ 'undefined_variable' ,
200
+ f"Variable '{ var } ' is used but not defined" ,
201
+ 'HIGH'
202
+ ))
203
+
204
+ def _check_comments (self ) -> None :
205
+ """Analyze code comments for quality and formatting."""
206
+ lines = self .source_code .split ('\n ' )
207
+ for i , line in enumerate (lines , 1 ):
208
+ stripped = line .strip ()
209
+ if stripped .startswith ('#' ):
210
+ # Check for empty comments
211
+ if len (stripped ) == 1 :
212
+ self .issues .append (CodeIssue (
213
+ i ,
214
+ 'comment_issue' ,
215
+ "Empty comment found" ,
216
+ 'LOW'
217
+ ))
218
+ # Check for space after #
219
+ elif stripped [1 ] != ' ' :
220
+ self .issues .append (CodeIssue (
221
+ i ,
222
+ 'comment_issue' ,
223
+ "Comments should have a space after '#'" ,
224
+ 'LOW'
225
+ ))
226
+ # Check for TODO comments
227
+ elif 'TODO' in stripped .upper ():
228
+ self .issues .append (CodeIssue (
229
+ i ,
230
+ 'comment_issue' ,
231
+ "TODO comment found - Consider addressing it" ,
232
+ 'LOW'
233
+ ))
234
+
235
+ def _check_best_practices (self ) -> None :
236
+ """Check for violations of Python best practices."""
237
+ for node in ast .walk (self .ast_tree ):
238
+ # Check for excessive line length in strings
239
+ if isinstance (node , ast .Str ):
240
+ if len (node .s ) > 79 :
241
+ self .issues .append (CodeIssue (
242
+ getattr (node , 'lineno' , 0 ),
243
+ 'best_practice' ,
244
+ "String literal is too long (> 79 characters)" ,
245
+ 'LOW'
246
+ ))
247
+
248
+ def get_report (self ) -> str :
249
+ """
250
+ Generate a detailed report of all issues found during analysis.
251
+
252
+ Returns:
253
+ str: Formatted report of all issues
254
+ """
255
+ if not self .issues :
256
+ return "No issues found. Code looks good! 🎉"
257
+
258
+ # Sort issues by severity and line number
259
+ sorted_issues = sorted (
260
+ self .issues ,
261
+ key = lambda x : (
262
+ {'HIGH' : 0 , 'MEDIUM' : 1 , 'LOW' : 2 }[x .severity ],
263
+ x .line_number
264
+ )
265
+ )
266
+
267
+ report = ["Code Review Report" , "=================\n " ]
268
+
269
+ # Group issues by severity
270
+ for severity in ['HIGH' , 'MEDIUM' , 'LOW' ]:
271
+ severity_issues = [i for i in sorted_issues if i .severity == severity ]
272
+ if severity_issues :
273
+ report .append (f"{ severity } Priority Issues:" )
274
+ report .append ("-" * 20 )
275
+ for issue in severity_issues :
276
+ location = f"Line { issue .line_number } : " if issue .line_number else ""
277
+ report .append (f"{ location } { issue .message } " )
278
+ report .append ("" )
279
+
280
+ return "\n " .join (report )
281
+
282
+ def main ():
283
+ """Main function to demonstrate the AI Code Reviewer usage."""
64
284
# Example Python code to analyze
65
- python_code = """
66
- def add(a, b):
67
- result = a + b
68
- print(result)
69
- """
285
+ example_code = """
286
+ def calculate_sum(numbers):
287
+ #bad comment
288
+ total = sum(numbers)
289
+ print(undefined_variable) # This will raise an issue
290
+ return total
291
+
292
+ class ExampleClass:
293
+ def method_without_docstring(self):
294
+ pass
70
295
71
- code_reviewer = CodeReviewer ()
72
- code_reviewer .analyze_python_code (python_code )
296
+ def complicated_method(self):
297
+ # TODO: Simplify this method
298
+ result = 0
299
+ for i in range(100):
300
+ for j in range(100):
301
+ for k in range(100):
302
+ result += i * j * k
303
+ return result
304
+ """
73
305
74
- feedback = code_reviewer .get_feedback ()
306
+ # Initialize and run the code reviewer
307
+ reviewer = AICodeReviewer ()
308
+ reviewer .load_code (example_code )
309
+ reviewer .analyze ()
310
+ print (reviewer .get_report ())
75
311
76
- if feedback :
77
- print ("Code Review Feedback:" )
78
- for msg in feedback :
79
- print (f"- { msg } " )
80
- else :
81
- print ("No coding errors found. Code looks good!" )
312
+ if __name__ == "__main__" :
313
+ main ()
0 commit comments