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+
19import ast
10+ import sys
211import 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+ """
330
4- class CodeReviewer :
531 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.
750
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+ """
957 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 )
1288 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+ ))
1495 return
1596
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+ ))
19118
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 ()
22139
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+ ))
25165
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
28170 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+ ))
62179
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."""
64284 # 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
70295
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+ """
73305
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 ())
75311
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