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