fix: resolve CI issues - push complete implementation with tests
Some checks failed
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
CI / test (3.10) (push) Has been cancelled

This commit is contained in:
2026-02-02 15:30:34 +00:00
parent cc6022cdc7
commit 9878d95b39

View File

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