Skip to content

Commit f092386

Browse files
authored
Merge pull request #1629 from mdhaarishussain/main
Updated code_reviwer.py
2 parents 347f4ad + 21030dc commit f092386

File tree

1 file changed

+295
-63
lines changed

1 file changed

+295
-63
lines changed
Lines changed: 295 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,313 @@
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+
19
import ast
10+
import sys
211
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+
"""
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

Comments
 (0)