Add core analyzer modules: base classes and language parsers
This commit is contained in:
129
src/analyzers/base.py
Normal file
129
src/analyzers/base.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Base analyzer class and common functionality."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import tree_sitter
|
||||
|
||||
|
||||
class SeverityLevel(str, Enum):
|
||||
"""Severity levels for findings."""
|
||||
|
||||
CRITICAL = "critical"
|
||||
HIGH = "high"
|
||||
MEDIUM = "medium"
|
||||
LOW = "low"
|
||||
|
||||
|
||||
class FindingCategory(str, Enum):
|
||||
"""Categories of findings."""
|
||||
|
||||
SECURITY = "security"
|
||||
ANTIPATTERN = "antipattern"
|
||||
SECRET = "secret"
|
||||
PERFORMANCE = "performance"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
"""Represents a code issue finding."""
|
||||
|
||||
rule_id: str
|
||||
rule_name: str
|
||||
severity: SeverityLevel
|
||||
category: FindingCategory
|
||||
message: str
|
||||
suggestion: str
|
||||
file_path: Path
|
||||
line_number: int
|
||||
column: int
|
||||
end_line: Optional[int] = None
|
||||
end_column: Optional[int] = None
|
||||
node: Optional[tree_sitter.Node] = None
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"rule_id": self.rule_id,
|
||||
"rule_name": self.rule_name,
|
||||
"severity": self.severity.value,
|
||||
"category": self.category.value,
|
||||
"message": self.message,
|
||||
"suggestion": self.suggestion,
|
||||
"file_path": str(self.file_path),
|
||||
"line_number": self.line_number,
|
||||
"column": self.column,
|
||||
"end_line": self.end_line,
|
||||
"end_column": self.end_column,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisResult:
|
||||
"""Result of analyzing a file."""
|
||||
|
||||
file_path: Path
|
||||
findings: list[Finding] = field(default_factory=list)
|
||||
error: Optional[str] = None
|
||||
lines_of_code: int = 0
|
||||
|
||||
def has_issues(self) -> bool:
|
||||
return len(self.findings) > 0
|
||||
|
||||
def critical_count(self) -> int:
|
||||
return sum(1 for f in self.findings if f.severity == SeverityLevel.CRITICAL)
|
||||
|
||||
def high_count(self) -> int:
|
||||
return sum(1 for f in self.findings if f.severity == SeverityLevel.HIGH)
|
||||
|
||||
def medium_count(self) -> int:
|
||||
return sum(1 for f in self.findings if f.severity == SeverityLevel.MEDIUM)
|
||||
|
||||
def low_count(self) -> int:
|
||||
return sum(1 for f in self.findings if f.severity == SeverityLevel.LOW)
|
||||
|
||||
def summary(self) -> dict:
|
||||
return {
|
||||
"critical": self.critical_count(),
|
||||
"high": self.high_count(),
|
||||
"medium": self.medium_count(),
|
||||
"low": self.low_count(),
|
||||
"total": len(self.findings),
|
||||
"lines_of_code": self.lines_of_code,
|
||||
}
|
||||
|
||||
|
||||
class Analyzer(ABC):
|
||||
"""Abstract base class for analyzers."""
|
||||
|
||||
@abstractmethod
|
||||
def analyze(
|
||||
self, source_code: str, file_path: Path, tree: tree_sitter.Tree
|
||||
) -> list[Finding]:
|
||||
"""Analyze source code and return findings."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rule_id(self) -> str:
|
||||
"""Return unique identifier for the rule."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rule_name(self) -> str:
|
||||
"""Return human-readable rule name."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def severity(self) -> SeverityLevel:
|
||||
"""Return severity level of the rule."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def category(self) -> FindingCategory:
|
||||
"""Return category of the rule."""
|
||||
pass
|
||||
|
||||
def supports_language(self, language: str) -> bool:
|
||||
"""Check if analyzer supports a language."""
|
||||
return True
|
||||
Reference in New Issue
Block a user