diff --git a/src/core/models.py b/src/core/models.py new file mode 100644 index 0000000..7895ab3 --- /dev/null +++ b/src/core/models.py @@ -0,0 +1,133 @@ +"""Data models for scan results and issues.""" + +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum +from pathlib import Path +from typing import Optional + + +class SeverityLevel(Enum): + """Severity levels for issues.""" + + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + CRITICAL = "critical" + + +class IssueCategory(Enum): + """Categories of issues that can be detected.""" + + SECURITY = "security" + CODE_QUALITY = "code_quality" + ERROR_HANDLING = "error_handling" + ANTI_PATTERN = "anti_pattern" + COMPLEXITY = "complexity" + STYLE = "style" + + +@dataclass +class Issue: + """Represents a detected issue in the code.""" + + severity: SeverityLevel + category: IssueCategory + file_path: str + line_number: int + message: str + suggestion: Optional[str] = None + code_snippet: Optional[str] = None + scanner_name: str = "unknown" + + def to_dict(self) -> dict: + """Convert issue to dictionary.""" + return { + "severity": self.severity.value, + "category": self.category.value, + "file_path": self.file_path, + "line_number": self.line_number, + "message": self.message, + "suggestion": self.suggestion, + "code_snippet": self.code_snippet, + "scanner_name": self.scanner_name, + } + + +@dataclass +class ScanResult: + """Result of a code scan operation.""" + + files_scanned: int + issues: list[Issue] = field(default_factory=list) + warnings: list[str] = field(default_factory=list) + scan_time: datetime = field(default_factory=datetime.now) + target_path: str = "" + + def add_issue(self, issue: Issue) -> None: + """Add an issue to the results.""" + self.issues.append(issue) + + def add_warning(self, warning: str) -> None: + """Add a warning to the results.""" + self.warnings.append(warning) + + def filter_by_severity(self, min_severity: SeverityLevel) -> "ScanResult": + """Filter issues by minimum severity level.""" + severity_order = [ + SeverityLevel.LOW, + SeverityLevel.MEDIUM, + SeverityLevel.HIGH, + SeverityLevel.CRITICAL, + ] + min_index = severity_order.index(min_severity) + + filtered = ScanResult( + files_scanned=self.files_scanned, + target_path=self.target_path, + ) + for issue in self.issues: + if severity_order.index(issue.severity) >= min_index: + filtered.add_issue(issue) + filtered.warnings = self.warnings.copy() + return filtered + + def filter_by_category(self, categories: list[IssueCategory]) -> "ScanResult": + """Filter issues by categories.""" + filtered = ScanResult( + files_scanned=self.files_scanned, + target_path=self.target_path, + ) + category_set = set(categories) + for issue in self.issues: + if issue.category in category_set: + filtered.add_issue(issue) + filtered.warnings = self.warnings.copy() + return filtered + + def get_summary(self) -> dict: + """Get a summary of the scan results.""" + summary = { + "files_scanned": self.files_scanned, + "total_issues": len(self.issues), + "issues_by_severity": {}, + "issues_by_category": {}, + } + + for issue in self.issues: + severity = issue.severity.value + category = issue.category.value + summary["issues_by_severity"][severity] = summary["issues_by_severity"].get(severity, 0) + 1 + summary["issues_by_category"][category] = summary["issues_by_category"].get(category, 0) + 1 + + return summary + + def to_dict(self) -> dict: + """Convert scan result to dictionary.""" + return { + "files_scanned": self.files_scanned, + "issues": [issue.to_dict() for issue in self.issues], + "warnings": self.warnings, + "scan_time": self.scan_time.isoformat(), + "target_path": self.target_path, + }