Add core analyzer modules: base classes and language parsers
Some checks failed
CI / test (push) Failing after 6s
CI / build (push) Has been skipped

This commit is contained in:
2026-01-29 23:08:26 +00:00
parent 377a3c3c86
commit 4ec376c6ff

129
src/analyzers/base.py Normal file
View 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