rule_evaluator.py
- Pfad:
/var/www/mcp-servers/mcp-contracts/validators/rule_evaluator.py - Namespace: -
- Zeilen: 212 | Größe: 7,756 Bytes
- Geändert: 2025-12-28 13:27:57 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 99
- Dependencies: 100 (25%)
- LOC: 96 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 3
- use re
- use typing.Any
- use domain.contracts.ContractValidationResult
Klassen 1
-
RuleEvaluatorclass Zeile 14
Code
"""
Rule Evaluator - Domain Service fuer Regel-Auswertung.
WICHTIG: Dies ist KEIN Repository!
Keine CRUD-Operationen, nur Business-Logik.
"""
import re
from typing import Any
from domain.contracts import ContractValidationResult
class RuleEvaluator:
"""Evaluiert Contract-Regeln gegen Code."""
def evaluate_rule(
self,
file_path: str,
content: str,
rule: dict[str, Any],
result: ContractValidationResult,
) -> None:
"""Prueft eine einzelne Rule gegen eine Datei."""
check_type = rule.get("check_type", "")
if check_type == "line_count":
self._check_line_count(file_path, content, rule, result)
elif check_type == "forbidden_pattern":
self._check_forbidden_pattern(file_path, content, rule, result)
elif check_type == "required_pattern":
self._check_required_pattern(file_path, content, rule, result)
elif check_type == "dependency_check":
self._check_dependency(file_path, content, rule, result)
def evaluate_legacy_forbidden(
self,
file_path: str,
content: str,
forbidden: list,
result: ContractValidationResult,
) -> None:
"""Prueft auf verbotene Elemente (Legacy-Format)."""
for item in forbidden:
if isinstance(item, dict):
element = item.get("element", "")
severity = item.get("severity", "minor")
message = item.get("message", f"Forbidden element found: {element}")
else:
element = str(item)
severity = "minor"
message = f"Forbidden element found: {element}"
if element and element in content:
self._add_violation(result, severity, {
"factor": "forbidden_element",
"file": file_path,
"message": message,
})
def evaluate_legacy_required(
self,
file_path: str,
content: str,
required: list,
result: ContractValidationResult,
) -> None:
"""Prueft auf erforderliche Elemente (Legacy-Format)."""
for item in required:
if isinstance(item, dict):
element = item.get("element", "")
severity = item.get("severity", "major")
message = item.get("message", f"Required element missing: {element}")
applies_to = item.get("applies_to", [])
else:
element = str(item)
severity = "major"
message = f"Required element missing: {element}"
applies_to = []
if applies_to:
matches = any(
file_path.endswith(p.lstrip("*")) for p in applies_to
)
if not matches:
continue
if element and element not in content:
self._add_violation(result, severity, {
"factor": "required_element",
"file": file_path,
"message": message,
})
def determine_outcome(self, result: ContractValidationResult) -> str:
"""Bestimmt das Validierungsergebnis basierend auf Violations."""
if result.critical > 0:
return "rejected"
elif result.major > 2:
return "revision_required"
return "passed"
def _check_line_count(
self, file_path: str, content: str, rule: dict, result: ContractValidationResult
) -> None:
"""Prueft Zeilenanzahl einer Datei."""
max_lines = rule.get("max_lines", 500)
line_count = len(content.splitlines())
if line_count > max_lines:
self._add_violation(result, rule.get("severity", "major"), {
"rule_id": rule.get("id", "line_count"),
"factor": "line_count",
"file": file_path,
"message": f"File has {line_count} lines (max: {max_lines})",
"actual": line_count,
"limit": max_lines,
})
def _check_forbidden_pattern(
self, file_path: str, content: str, rule: dict, result: ContractValidationResult
) -> None:
"""Prueft auf verbotene Patterns."""
patterns = rule.get("patterns", [])
for pattern in patterns:
try:
matches = re.findall(pattern, content)
except re.error:
matches = [pattern] if pattern in content else []
if matches:
line_no = self._find_line_number(content, pattern)
self._add_violation(result, rule.get("severity", "major"), {
"rule_id": rule.get("id", "forbidden_pattern"),
"factor": "forbidden_pattern",
"file": file_path,
"message": f"Forbidden pattern found: '{pattern}'",
"description": rule.get("description", ""),
"line": line_no,
"occurrences": len(matches),
})
def _check_required_pattern(
self, file_path: str, content: str, rule: dict, result: ContractValidationResult
) -> None:
"""Prueft auf erforderliche Patterns."""
patterns = rule.get("patterns", [])
for pattern in patterns:
try:
found = bool(re.search(pattern, content))
except re.error:
found = pattern in content
if not found:
self._add_violation(result, rule.get("severity", "major"), {
"rule_id": rule.get("id", "required_pattern"),
"factor": "required_pattern",
"file": file_path,
"message": f"Required pattern not found: '{pattern}'",
"description": rule.get("description", ""),
})
def _check_dependency(
self, file_path: str, content: str, rule: dict, result: ContractValidationResult
) -> None:
"""Prueft auf verbotene Dependencies/Imports."""
forbidden_imports = rule.get("forbidden_imports", [])
for forbidden in forbidden_imports:
php_pattern = rf"use\s+.*{re.escape(forbidden)}"
py_pattern = rf"(from|import)\s+.*{re.escape(forbidden)}"
try:
if re.search(php_pattern, content) or re.search(py_pattern, content):
self._add_violation(result, rule.get("severity", "critical"), {
"rule_id": rule.get("id", "dependency_check"),
"factor": "forbidden_dependency",
"file": file_path,
"message": f"Forbidden import/dependency: '{forbidden}'",
})
except re.error:
pass
def _find_line_number(self, content: str, pattern: str) -> int | None:
"""Findet die Zeilennummer eines Patterns."""
for i, line in enumerate(content.splitlines(), 1):
if pattern in line:
return i
try:
if re.search(pattern, line):
return i
except re.error:
pass
return None
def _add_violation(
self, result: ContractValidationResult, severity: str, finding: dict
) -> None:
"""Fuegt eine Violation zum Result hinzu."""
finding["type"] = severity
if severity == "critical":
result.critical += 1
elif severity == "major":
result.major += 1
else:
result.minor += 1
result.findings.append(finding)