70 lines
2.0 KiB
Python
70 lines
2.0 KiB
Python
"""Code scanning logic."""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from .models import ScanResult, Issue, IssueCategory, SeverityLevel
|
|
from .options import ScanOptions
|
|
|
|
|
|
class CodeScanner:
|
|
"""Scan code files for issues."""
|
|
|
|
SUPPORTED_EXTENSIONS = {".py", ".js", ".ts", ".jsx", ".tsx"}
|
|
|
|
def __init__(self):
|
|
self.issues: list[Issue] = []
|
|
|
|
def scan(
|
|
self, path: Path, options: Optional[ScanOptions] = None
|
|
) -> ScanResult:
|
|
"""Scan a file or directory for issues."""
|
|
options = options or ScanOptions()
|
|
result = ScanResult()
|
|
|
|
files = self._collect_files(path)
|
|
result.files_scanned = len(files)
|
|
|
|
for file_path in files:
|
|
issues = self._scan_file(file_path, options)
|
|
result.issues.extend(issues)
|
|
|
|
return result
|
|
|
|
def _collect_files(self, path: Path) -> list[Path]:
|
|
"""Collect files to scan."""
|
|
if path.is_file() and self._is_supported(path):
|
|
return [path]
|
|
|
|
files = []
|
|
for match in path.rglob("*"):
|
|
if match.is_file() and self._is_supported(match):
|
|
files.append(match)
|
|
|
|
return files
|
|
|
|
def _is_supported(self, path: Path) -> bool:
|
|
"""Check if file extension is supported."""
|
|
return path.suffix in self.SUPPORTED_EXTENSIONS
|
|
|
|
def _scan_file(self, path: Path, options: ScanOptions) -> list[Issue]:
|
|
"""Scan a single file for issues."""
|
|
issues = []
|
|
|
|
content = path.read_text(errors="ignore")
|
|
lines = content.split("\n")
|
|
|
|
for i, line in enumerate(lines, 1):
|
|
if "TODO" in line or "FIXME" in line:
|
|
issues.append(
|
|
Issue(
|
|
category=IssueCategory.MAINTAINABILITY,
|
|
severity=SeverityLevel.LOW,
|
|
file_path=str(path),
|
|
line_number=i,
|
|
message="TODO/FIXME comment found",
|
|
)
|
|
)
|
|
|
|
return issues
|