Add CLI, utils, and fixers modules
Some checks failed
CI / test (push) Failing after 10s
CI / build (push) Has been skipped

This commit is contained in:
2026-01-29 23:11:58 +00:00
parent 5772b7362e
commit b378002a36

212
src/fixers/__init__.py Normal file
View File

@@ -0,0 +1,212 @@
"""Fixer base class and implementations."""
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional
import tree_sitter
from src.analyzers.base import Finding
class Fixer(ABC):
"""Abstract base class for code fixers."""
@abstractmethod
def can_fix(self, finding: Finding) -> bool:
"""Check if this fixer can handle the finding."""
pass
@abstractmethod
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
"""Apply fix to source code."""
pass
@abstractmethod
def rule_id(self) -> str:
"""Return the rule ID this fixer handles."""
pass
class SQLInjectionFixer(Fixer):
"""Fixer for SQL injection vulnerabilities."""
def rule_id(self) -> str:
return "security.sql_injection"
def can_fix(self, finding: Finding) -> bool:
return finding.rule_id == self.rule_id()
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
lines = source_code.split("\n")
line_idx = finding.line_number - 1
if 0 <= line_idx < len(lines):
original_line = lines[line_idx]
fixed_line = self._fix_line(original_line)
lines[line_idx] = fixed_line
return "\n".join(lines)
def _fix_line(self, line: str) -> str:
if "execute(" in line:
return line.replace("%s", "?").replace("f\"", "\"").replace("'", "\"")
return line
class EvalUsageFixer(Fixer):
"""Fixer for eval/exec usage."""
def rule_id(self) -> str:
return "security.eval_usage"
def can_fix(self, finding: Finding) -> bool:
return finding.rule_id == self.rule_id()
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
lines = source_code.split("\n")
line_idx = finding.line_number - 1
if 0 <= line_idx < len(lines):
original_line = lines[line_idx]
fixed_line = self._fix_line(original_line)
lines[line_idx] = fixed_line
return "\n".join(lines)
def _fix_line(self, line: str) -> str:
if "eval(" in line:
return line.replace("eval(", "# eval( # Warning: eval is dangerous - ")
if "exec(" in line:
return line.replace("exec(", "# exec( # Warning: exec is dangerous - ")
return line
class ExceptionSwallowFixer(Fixer):
"""Fixer for exception swallowing."""
def rule_id(self) -> str:
return "antipattern.exception_swallow"
def can_fix(self, finding: Finding) -> bool:
return finding.rule_id == self.rule_id()
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
lines = source_code.split("\n")
line_idx = finding.line_number - 1
if 0 <= line_idx < len(lines):
for i, line in enumerate(lines):
if "except:" in line or "except ()" in line:
lines[i] = line.replace("except:", "except Exception as e:").replace("except ()", "except Exception as e:")
elif line.strip() == "pass":
lines[i] = line.replace("pass", "logger.error(f'Exception occurred: {e}')")
return "\n".join(lines)
def _fix_line(self, line: str) -> str:
if "except:" in line or "except ():" in line:
return line.replace("except:", "except Exception as e:").replace("except ():", "except Exception as e:")
if "pass" in line:
return line.replace("pass", "logger.error(f'Exception occurred: {e}')")
return line
class MagicNumberFixer(Fixer):
"""Fixer for magic numbers."""
def rule_id(self) -> str:
return "antipattern.magic_number"
def can_fix(self, finding: Finding) -> bool:
return finding.rule_id == self.rule_id()
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
lines = source_code.split("\n")
line_idx = finding.line_number - 1
if 0 <= line_idx < len(lines):
original_line = lines[line_idx]
fixed_line = self._fix_line(original_line)
lines[line_idx] = fixed_line
return "\n".join(lines)
def _fix_line(self, line: str) -> str:
import re
magic_pattern = r"(\d{2,})"
matches = list(re.finditer(magic_pattern, line))
if matches:
last_match = matches[-1]
num = last_match.group(1)
line = line[:last_match.start()] + f"MAGIC_NUMBER_{num}" + line[last_match.end():]
return line
class RedundantOperationFixer(Fixer):
"""Fixer for redundant operations."""
def rule_id(self) -> str:
return "performance.redundant_operation"
def can_fix(self, finding: Finding) -> bool:
return finding.rule_id == self.rule_id()
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
lines = source_code.split("\n")
line_idx = finding.line_number - 1
if 0 <= line_idx < len(lines):
original_line = lines[line_idx]
fixed_line = self._fix_line(original_line)
lines[line_idx] = fixed_line
return "\n".join(lines)
def _fix_line(self, line: str) -> str:
if "list(list(" in line:
return line.replace("list(list(", "list(")
if "str(str(" in line:
return line.replace("str(str(", "str(")
if "dict(dict(" in line:
return line.replace("dict(dict(", "dict(")
return line
class FixerRegistry:
"""Registry for code fixers."""
def __init__(self):
self._fixers: dict[str, Fixer] = {
"security.sql_injection": SQLInjectionFixer(),
"security.eval_usage": EvalUsageFixer(),
"antipattern.exception_swallow": ExceptionSwallowFixer(),
"antipattern.magic_number": MagicNumberFixer(),
"performance.redundant_operation": RedundantOperationFixer(),
}
def get_fixer(self, rule_id: str) -> Optional[Fixer]:
return self._fixers.get(rule_id)
def can_fix(self, finding: Finding) -> bool:
fixer = self._fixers.get(finding.rule_id)
return fixer is not None and fixer.can_fix(finding)
def fix(
self, source_code: str, finding: Finding, tree: tree_sitter.Tree
) -> str:
fixer = self._fixers.get(finding.rule_id)
if fixer and fixer.can_fix(finding):
return fixer.fix(source_code, finding, tree)
return source_code