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
from dataclasses import dataclass
@dataclass
class Issue:
"""Represents a detected issue."""
type: str
severity: str
title: str
description: str
line: int | None = None
line: int = None
suggestion: str = ""
pattern: str = ""
class IssueDetector:
"""Detects issues in code changes."""
SECURITY_PATTERNS = [
{
'pattern': (
r'(?i)(sql\\s*\\(|execute\\s*\\(|exec\\s*\\(|SELECT\\s+|UPDATE\\s+|'
r'INSERT\\s+|DELETE\\s+)'
"pattern": (
r"(?i)(sql\\s*\\(|execute\\s*\\(|exec\\s*\\(|SELECT\\s+|UPDATE\\s+|"
r"INSERT\\s+|DELETE\\s+)"
),
'type': 'sql_injection',
'severity': 'critical',
'title': 'Potential SQL Injection',
'description': (
'String concatenation or interpolation used in SQL query'
"type": "sql_injection",
"severity": "critical",
"title": "Potential SQL Injection",
"description": (
"String concatenation or interpolation used in SQL query"
),
'suggestion': (
'Use parameterized queries or ORM methods instead of string concatenation'
"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)(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)(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)(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)(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)(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',
"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 = [
{
'pattern': r'(?i)(if\\s*\\([^)]*==[^)]*\\)\\s*:|if\\s*\\([^)]*=\\s*[^)]*\\)\\s*:)',
'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)(if\\s*\\([^)]*==[^)]*\\)\\s*:|if\\s*\\([^)]*=\\s*[^)]*\\)\\s*:)",
"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"(?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"\\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)',
"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 = [
{
'pattern': r'^\\s*for\\s+.*\\s+in\\s+.*:\\s*$',
'type': 'long_loop',
'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"^\\s*for\\s+.*\\s+in\\s+.*:\\s*$",
"type": "long_loop",
"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)(\\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"(?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".{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',
"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",
},
]
@@ -168,18 +163,16 @@ class IssueDetector:
self._compile_patterns()
def _compile_patterns(self):
"""Compile all regex patterns for better performance."""
self._compiled_patterns = []
for pattern_info in self.SECURITY_PATTERNS + self.BUG_PATTERNS + self.CODE_SMELL_PATTERNS:
try:
compiled = re.compile(pattern_info['pattern'])
compiled = re.compile(pattern_info["pattern"])
self._compiled_patterns.append((compiled, pattern_info))
except re.error:
pass
def detect_issues(self, code: str, language: str = "text") -> list[Issue]:
"""Detect issues in code."""
def detect_issues(self, code, language="text"):
issues = []
lines = code.splitlines()
@@ -187,28 +180,25 @@ class IssueDetector:
for compiled, pattern_info in self._compiled_patterns:
if compiled.search(line):
issue = Issue(
type=pattern_info['type'],
severity=pattern_info['severity'],
title=pattern_info['title'],
description=pattern_info['description'],
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'],
suggestion=pattern_info["suggestion"],
pattern=pattern_info["pattern"],
)
issues.append(issue)
return issues
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)."""
def detect_diff_issues(self, old_code, new_code, language="text"):
issues = []
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:]
added_lines.append((i, clean_line))
@@ -216,36 +206,34 @@ class IssueDetector:
for compiled, pattern_info in self._compiled_patterns:
if compiled.search(clean_line):
issue = Issue(
type=pattern_info['type'],
severity=pattern_info['severity'],
title=pattern_info['title'],
description=pattern_info['description'],
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'],
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."""
def check_security_patterns(self, code):
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):
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'],
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'],
suggestion=pattern_info["suggestion"],
pattern=pattern_info["pattern"],
)
issues.append(issue)
except re.error:
@@ -253,24 +241,22 @@ class IssueDetector:
return issues
def check_code_quality(self, code: str) -> list[Issue]:
"""Check for code quality issues only."""
def check_code_quality(self, code):
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):
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'],
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'],
suggestion=pattern_info["suggestion"],
pattern=pattern_info["pattern"],
)
issues.append(issue)
except re.error:
@@ -278,12 +264,11 @@ class IssueDetector:
return issues
def suggest_improvements(self, code: str, language: str = "text") -> list[str]:
"""Suggest code improvements based on patterns."""
def suggest_improvements(self, code, language="text"):
suggestions = []
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()
for issue in sorted(issues, key=lambda x: (severity_order.get(x.severity, 4), x.title)):
@@ -294,13 +279,11 @@ class IssueDetector:
return suggestions
def detect_issues(code: str, language: str = "text") -> list[Issue]:
"""Detect issues in code."""
def detect_issues(code, language="text"):
detector = IssueDetector()
return detector.detect_issues(code, language)
def suggest_improvements(code: str, language: str = "text") -> list[str]:
"""Suggest code improvements."""
def suggest_improvements(code, language="text"):
detector = IssueDetector()
return detector.suggest_improvements(code, language)