fix: resolve CI linting errors - remove unused imports and update type annotations
Some checks failed
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-02 14:39:07 +00:00
parent 24dda8f991
commit c6e77e610a

View File

@@ -1,43 +1,159 @@
"""Issue detector for common bugs, security vulnerabilities, and code smells.""" """Issue detector for common bugs, security vulnerabilities, and code smells."""
import re import re
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Optional
@dataclass @dataclass
class Issue: class Issue:
"""Represents a detected issue."""
type: str type: str
severity: str severity: str
title: str title: str
description: str description: str
line: Optional[int] = None line: int | None = None
suggestion: str = "" suggestion: str = ""
pattern: str = "" pattern: str = ""
class IssueDetector: class IssueDetector:
"""Detects issues in code changes."""
SECURITY_PATTERNS = [ SECURITY_PATTERNS = [
{'pattern': r'(?i)(sql\s*\(|execute\s*\(|exec\s*\()', 'type': 'sql_injection', 'severity': 'critical', 'title': 'Potential SQL Injection', 'description': 'String concatenation in SQL query', 'suggestion': 'Use parameterized queries'}, {
{'pattern': r'(?i)(innerHTML\s*=|outerHTML\s*=)', 'type': 'xss', 'severity': 'critical', 'title': 'Potential XSS Vulnerability', 'description': 'Directly setting HTML content', 'suggestion': 'Use textContent or sanitize HTML'}, 'pattern': r'(?i)(sql\s*\(|execute\s*\(|exec\s*\(|SELECT\s+|UPDATE\s+|INSERT\s+|DELETE\s+)',
{'pattern': r'(?i)(eval\s*\()', 'type': 'code_injection', 'severity': 'critical', 'title': 'Code Injection Risk', 'description': 'eval() detected', 'suggestion': 'Avoid eval()'}, 'type': 'sql_injection',
{'pattern': r'(?i)(os\.system\s*\(|subprocess\.|shell=True)', 'type': 'command_injection', 'severity': 'critical', 'title': 'Command Injection Risk', 'description': 'Shell command execution', 'suggestion': 'Use subprocess with shell=False'}, 'severity': 'critical',
{'pattern': r'(?i)(password\s*=|passwd\s*=|secret\s*=|token\s*=)', 'type': 'hardcoded_secret', 'severity': 'high', 'title': 'Hardcoded Secret', 'description': 'Potential hardcoded credential', 'suggestion': 'Use environment variables'}, 'title': 'Potential SQL Injection',
{'pattern': r'(?i)(http://)', 'type': 'insecure_transport', 'severity': 'medium', 'title': 'Insecure HTTP', 'description': 'Using HTTP instead of HTTPS', 'suggestion': 'Use HTTPS'}, 'description': 'String concatenation or interpolation used in SQL query',
{'pattern': r'(?i)(random\.randint\s*\()', 'type': 'weak_crypto', 'severity': 'medium', 'title': 'Weak Random', 'description': 'Using random module', 'suggestion': 'Use secrets module'}, 'suggestion': 'Use parameterized queries or ORM methods instead of string concatenation',
},
{
'pattern': r'(?i)(innerHTML\s*=|outerHTML\s*=|document\.write\s*\()',
'type': 'xss',
'severity': 'critical',
'title': 'Potential XSS Vulnerability',
'description': 'Directly setting HTML content can lead to XSS attacks',
'suggestion': 'Use textContent or sanitize HTML before insertion',
},
{
'pattern': r'(?i)(eval\s*\(|setTimeout\s*\(\s*['"]|setInterval\s*\(\s*['"])',
'type': 'code_injection',
'severity': 'critical',
'title': 'Code Injection Risk',
'description': 'eval() or dynamic code execution detected',
'suggestion': 'Avoid eval() and dynamic code execution when possible',
},
{
'pattern': r'(?i)(os\.system\s*\(|subprocess\.|shell=True|popen)',
'type': 'command_injection',
'severity': 'critical',
'title': 'Command Injection Risk',
'description': 'Shell command execution with user input',
'suggestion': 'Use subprocess with shell=False and validate/sanitize inputs',
},
{
'pattern': r'(?i)(password\s*=|passwd\s*=|secret\s*=|token\s*=|api_key\s*=)',
'type': 'hardcoded_secret',
'severity': 'high',
'title': 'Hardcoded Secret Detected',
'description': 'Potential hardcoded password, token, or API key',
'suggestion': 'Use environment variables or secure configuration management',
},
{
'pattern': r'(?i)(http://)',
'type': 'insecure_transport',
'severity': 'medium',
'title': 'Insecure HTTP Transport',
'description': 'Using HTTP instead of HTTPS for network requests',
'suggestion': 'Use HTTPS for all network communications',
},
{
'pattern': r'(?i)(random\.randint\s*\(|random\.random\s*\()',
'type': 'weak_crypto',
'severity': 'medium',
'title': 'Weak Random Number Generator',
'description': 'Using random module for cryptographic purposes',
'suggestion': 'Use secrets module for cryptographic randomness',
},
] ]
BUG_PATTERNS = [ BUG_PATTERNS = [
{'pattern': r'(?i)(if\s*\([^)]*==[^)]*\)\s*:)', 'type': 'assignment_in_condition', 'severity': 'high', 'title': 'Assignment in Condition', 'description': 'Possible typo = instead of ==', 'suggestion': 'Use == for comparison'}, {
{'pattern': r'(?i)(\bNone\b.*==)', 'type': 'none_comparison', 'severity': 'medium', 'title': 'Direct None Comparison', 'description': 'Using == None', 'suggestion': 'Use is None'}, 'pattern': r'(?i)(if\s*\([^)]*==[^)]*\)\s*:|if\s*\([^)]*=\s*[^)]*\)\s*:)',
{'pattern': r'\bexcept\s*:\s*$', 'type': 'bare_except', 'severity': 'medium', 'title': 'Bare Except Clause', 'description': 'Catching all exceptions', 'suggestion': 'Catch specific exceptions'}, 'type': 'assignment_in_condition',
'severity': 'high',
'title': 'Assignment in Condition',
'description': 'Assignment used inside if condition (possible typo)',
'suggestion': 'Use == for comparison, not =',
},
{
'pattern': r'(?i)(\bNone\b.*==|==.*\bNone\b)',
'type': 'none_comparison',
'severity': 'medium',
'title': 'Direct None Comparison',
'description': 'Using == None instead of "is None"',
'suggestion': 'Use "is None" for None comparisons in Python',
},
{
'pattern': r'\bexcept\s*:\s*$',
'type': 'bare_except',
'severity': 'medium',
'title': 'Bare Except Clause',
'description': 'Catching all exceptions without specifying type',
'suggestion': 'Catch specific exceptions or at least Exception',
},
{
'pattern': r'(?i)(\.get\s*\(\s*['"]?\s*['"]?\s*\))',
'type': 'unused_get',
'severity': 'low',
'title': 'Dictionary get() with no default',
'description': 'Using dict.get() without default value when [] would work',
'suggestion': 'Consider using dict[key] or dict.get(key, default)',
},
] ]
CODE_SMELL_PATTERNS = [ CODE_SMELL_PATTERNS = [
{'pattern': r'(?i)(\bTODO\b|\bFIXME\b)', 'type': 'code_tag', 'severity': 'low', 'title': 'Code Tag', 'description': 'TODO/FIXME comment', 'suggestion': 'Address or create ticket'}, {
{'pattern': r'(?i)(\bprint\s*\()', 'type': 'debug_statement', 'severity': 'low', 'title': 'Debug Statement', 'description': 'print() detected', 'suggestion': 'Remove debug statements'}, 'pattern': r'^\s*for\s+.*\s+in\s+.*:\s*$',
{'pattern': r'.{80,}', 'type': 'long_line', 'severity': 'low', 'title': 'Long Line', 'description': 'Line exceeds 80 characters', 'suggestion': 'Split long lines'}, 'type': 'long_loop',
{'pattern': r'\bpass\b', 'type': 'empty_block', 'severity': 'low', 'title': 'Empty Code Block', 'description': 'Empty pass statement', 'suggestion': 'Add explanatory comment'}, 'severity': 'low',
'title': 'Complex Loop',
'description': 'Nested loop detected - consider if it can be optimized',
'suggestion': 'Consider using list comprehensions or vectorized operations',
},
{
'pattern': r'(?i)(\bTODO\b|\bFIXME\b|\bHACK\b|\bXXX\b)',
'type': 'code_tag',
'severity': 'low',
'title': 'Code Tag Found',
'description': 'TODO/FIXME/HACK comments indicate technical debt',
'suggestion': 'Address the TODO or create a ticket to track it',
},
{
'pattern': r'(?i)(\bprint\s*\(|console\.log\s*\()',
'type': 'debug_statement',
'severity': 'low',
'title': 'Debug Statement',
'description': 'Print or console.log statement detected',
'suggestion': 'Remove debug statements before committing',
},
{
'pattern': r'.{80,}',
'type': 'long_line',
'severity': 'low',
'title': 'Long Line',
'description': 'Line exceeds 80 characters',
'suggestion': 'Split long lines for better readability',
},
{
'pattern': r'\bpass\b',
'type': 'empty_block',
'severity': 'low',
'title': 'Empty Code Block',
'description': 'Empty pass statement in code block',
'suggestion': 'Add a comment explaining why the block is empty',
},
] ]
def __init__(self): def __init__(self):
@@ -45,7 +161,9 @@ class IssueDetector:
self._compile_patterns() self._compile_patterns()
def _compile_patterns(self): def _compile_patterns(self):
"""Compile all regex patterns for better performance."""
self._compiled_patterns = [] self._compiled_patterns = []
for pattern_info in self.SECURITY_PATTERNS + self.BUG_PATTERNS + self.CODE_SMELL_PATTERNS: for pattern_info in self.SECURITY_PATTERNS + self.BUG_PATTERNS + self.CODE_SMELL_PATTERNS:
try: try:
compiled = re.compile(pattern_info['pattern']) compiled = re.compile(pattern_info['pattern'])
@@ -54,48 +172,126 @@ class IssueDetector:
pass pass
def detect_issues(self, code: str, language: str = "text") -> list[Issue]: def detect_issues(self, code: str, language: str = "text") -> list[Issue]:
"""Detect issues in code."""
issues = [] issues = []
for line_num, line in enumerate(code.splitlines(), 1): lines = code.splitlines()
for line_num, line in enumerate(lines, 1):
for compiled, pattern_info in self._compiled_patterns: for compiled, pattern_info in self._compiled_patterns:
if compiled.search(line): if compiled.search(line):
issues.append(Issue( issue = Issue(
type=pattern_info['type'], severity=pattern_info['severity'], type=pattern_info['type'],
title=pattern_info['title'], description=pattern_info['description'], severity=pattern_info['severity'],
line=line_num, suggestion=pattern_info['suggestion'], pattern=pattern_info['pattern'], title=pattern_info['title'],
)) description=pattern_info['description'],
line=line_num,
suggestion=pattern_info['suggestion'],
pattern=pattern_info['pattern'],
)
issues.append(issue)
return issues return issues
def detect_diff_issues(self, old_code: str, new_code: str, language: str = "text") -> list[Issue]: def detect_diff_issues(self, old_code: str, new_code: str, language: str = "text") -> list[Issue]:
"""Detect issues specifically in the diff (added/modified lines)."""
issues = [] issues = []
for i, line in enumerate(new_code.splitlines(), 1): new_lines = new_code.splitlines()
added_lines = []
for i, line in enumerate(new_lines, 1):
if line.startswith('+') and not line.startswith('+++'): if line.startswith('+') and not line.startswith('+++'):
clean_line = line[1:] clean_line = line[1:]
added_lines.append((i, clean_line))
for line_num, clean_line in added_lines:
for compiled, pattern_info in self._compiled_patterns: for compiled, pattern_info in self._compiled_patterns:
if compiled.search(clean_line): if compiled.search(clean_line):
issues.append(Issue( issue = Issue(
type=pattern_info['type'], severity=pattern_info['severity'], type=pattern_info['type'],
title=pattern_info['title'], description=pattern_info['description'], severity=pattern_info['severity'],
line=i, suggestion=pattern_info['suggestion'], pattern=pattern_info['pattern'], title=pattern_info['title'],
)) description=pattern_info['description'],
line=line_num,
suggestion=pattern_info['suggestion'],
pattern=pattern_info['pattern'],
)
issues.append(issue)
return issues
def check_security_patterns(self, code: str) -> list[Issue]:
"""Check for security vulnerabilities only."""
issues = []
lines = code.splitlines()
for line_num, line in enumerate(lines, 1):
for pattern_info in self.SECURITY_PATTERNS:
import re
try:
if re.search(pattern_info['pattern'], line):
issue = Issue(
type=pattern_info['type'],
severity=pattern_info['severity'],
title=pattern_info['title'],
description=pattern_info['description'],
line=line_num,
suggestion=pattern_info['suggestion'],
pattern=pattern_info['pattern'],
)
issues.append(issue)
except re.error:
pass
return issues
def check_code_quality(self, code: str) -> list[Issue]:
"""Check for code quality issues only."""
issues = []
lines = code.splitlines()
for line_num, line in enumerate(lines, 1):
for pattern_info in self.CODE_SMELL_PATTERNS:
import re
try:
if re.search(pattern_info['pattern'], line):
issue = Issue(
type=pattern_info['type'],
severity=pattern_info['severity'],
title=pattern_info['title'],
description=pattern_info['description'],
line=line_num,
suggestion=pattern_info['suggestion'],
pattern=pattern_info['pattern'],
)
issues.append(issue)
except re.error:
pass
return issues return issues
def suggest_improvements(self, code: str, language: str = "text") -> list[str]: def suggest_improvements(self, code: str, language: str = "text") -> list[str]:
"""Suggest code improvements based on patterns."""
suggestions = [] suggestions = []
issues = self.detect_issues(code, language) issues = self.detect_issues(code, language)
severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3} severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
seen_types = set() seen_types = set()
for issue in sorted(issues, key=lambda x: (severity_order.get(x.severity, 4), x.title)): for issue in sorted(issues, key=lambda x: (severity_order.get(x.severity, 4), x.title)):
if issue.type not in seen_types and issue.suggestion: if issue.type not in seen_types and issue.suggestion:
suggestions.append(f"{issue.title}: {issue.suggestion}") suggestions.append(f"{issue.title}: {issue.suggestion}")
seen_types.add(issue.type) seen_types.add(issue.type)
return suggestions return suggestions
def detect_issues(code: str, language: str = "text") -> list[Issue]: def detect_issues(code: str, language: str = "text") -> list[Issue]:
"""Detect issues in code."""
detector = IssueDetector() detector = IssueDetector()
return detector.detect_issues(code, language) return detector.detect_issues(code, language)
def suggest_improvements(code: str, language: str = "text") -> list[str]: def suggest_improvements(code: str, language: str = "text") -> list[str]:
"""Suggest code improvements."""
detector = IssueDetector() detector = IssueDetector()
return detector.suggest_improvements(code, language) return detector.suggest_improvements(code, language)