| from typing import List, Dict, Any |
| import ast |
| import re |
|
|
| class CodeReviewAnalyzer: |
| def __init__(self): |
| self.metrics = { |
| 'complexity': self._analyze_complexity, |
| 'naming': self._analyze_naming, |
| 'documentation': self._analyze_documentation, |
| 'security': self._analyze_security |
| } |
|
|
| def review_code(self, code: str, language: str) -> Dict[str, Any]: |
| results = {} |
| for metric_name, analyzer in self.metrics.items(): |
| results[metric_name] = analyzer(code, language) |
| return results |
|
|
| def _analyze_complexity(self, code: str, language: str) -> Dict[str, Any]: |
| if language == 'python': |
| tree = ast.parse(code) |
| return { |
| 'cyclomatic_complexity': self._count_branches(tree), |
| 'cognitive_complexity': self._analyze_cognitive_complexity(tree) |
| } |
| return {} |
|
|
| def _analyze_naming(self, code: str, language: str) -> Dict[str, List[str]]: |
| issues = [] |
| if language == 'python': |
| tree = ast.parse(code) |
| for node in ast.walk(tree): |
| if isinstance(node, ast.Name): |
| if not self._is_valid_name(node.id): |
| issues.append(f"Invalid name: {node.id}") |
| return {'issues': issues} |
|
|
| def _analyze_documentation(self, code: str, language: str) -> Dict[str, Any]: |
| doc_ratio = len(re.findall(r'"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'', code)) / max(1, len(code.splitlines())) |
| return { |
| 'documentation_ratio': doc_ratio, |
| 'has_module_docstring': code.lstrip().startswith('"""') or code.lstrip().startswith("'''") |
| } |
|
|
| def _analyze_security(self, code: str, language: str) -> Dict[str, List[str]]: |
| vulnerabilities = [] |
| dangerous_patterns = { |
| 'python': [ |
| (r'eval\(', 'Dangerous eval() usage'), |
| (r'exec\(', 'Dangerous exec() usage'), |
| (r'os\.system\(', 'Unsafe system command execution') |
| ] |
| } |
| |
| for pattern, message in dangerous_patterns.get(language, []): |
| if re.search(pattern, code): |
| vulnerabilities.append(message) |
| return {'vulnerabilities': vulnerabilities} |
|
|
| def _count_branches(self, tree: ast.AST) -> int: |
| count = 0 |
| for node in ast.walk(tree): |
| if isinstance(node, (ast.If, ast.For, ast.While, ast.Try)): |
| count += 1 |
| return count |
|
|
| def _analyze_cognitive_complexity(self, tree: ast.AST) -> int: |
| complexity = 0 |
| for node in ast.walk(tree): |
| if isinstance(node, (ast.If, ast.While, ast.For)): |
| complexity += 1 |
| |
| complexity += self._get_nesting_level(node) * 0.5 |
| return int(complexity) |
|
|
| def _get_nesting_level(self, node: ast.AST, level: int = 0) -> int: |
| parent = getattr(node, 'parent', None) |
| if parent is None: |
| return level |
| return self._get_nesting_level(parent, level + 1) |
|
|
| def _is_valid_name(self, name: str) -> bool: |
| if len(name) < 2: |
| return False |
| if not name.isidentifier(): |
| return False |
| return True |
|
|