pre_rules_validation.py
- Pfad:
/var/www/tools/ki-protokoll/claude-hook/quality/pre_rules_validation.py - Namespace: claude-hook.quality
- Zeilen: 112 | Größe: 3,600 Bytes
- Geändert: 2025-12-25 16:58:58 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 98
- Dependencies: 90 (25%)
- LOC: 100 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 6
- use re
- use pathlib.Path
- use typing.Optional
- use rule_base.GLOBAL_ALLOWLIST
- use rule_base.is_in_allowlist
- use rule_base.block
Funktionen 4
-
p3_1_strict_types()Zeile 18 -
p3_2_namespace_matches_path()Zeile 30 -
p3_3_classname_matches_filename()Zeile 61 -
p3_4_public_method_return_type()Zeile 79
Code
#!/usr/bin/env python3
"""
Pre-Hook Validation Regeln (BLOCK) - PSR + Types.
P3.x Regeln: strict_types, namespace, classname, return types
"""
import re
from pathlib import Path
from typing import Optional
from .rule_base import GLOBAL_ALLOWLIST, is_in_allowlist, block
# =============================================================================
# PRÜFUNG 3: PSR + Types
# =============================================================================
def p3_1_strict_types(file_path: str, content: str) -> Optional[dict]:
"""P3.1: strict_types erforderlich."""
if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):
return None
strict_pattern = r"declare\s*\(\s*strict_types\s*=\s*1\s*\)\s*;"
if not re.search(strict_pattern, content):
return block("P3.1", "Missing declare(strict_types=1)")
return None
def p3_2_namespace_matches_path(file_path: str, content: str) -> Optional[dict]:
"""P3.2: Namespace muss Pfad entsprechen."""
if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):
return None
# Namespace-Mapping
path_to_namespace = {
"/src/Controller/": "Controller\\",
"/src/Domain/": "Domain\\",
"/src/UseCases/": "UseCases\\",
"/src/Application/": "Application\\",
"/src/Infrastructure/": "Infrastructure\\",
"/src/Framework/": "Framework\\",
"/app/": "App\\",
}
namespace_match = re.search(r"namespace\s+([^;]+);", content)
if not namespace_match:
return None # Kein Namespace = kein Check
declared_namespace = namespace_match.group(1).strip()
for path_prefix, ns_prefix in path_to_namespace.items():
if path_prefix in file_path:
if not declared_namespace.startswith(ns_prefix.rstrip("\\")):
return block("P3.2", f"Namespace '{declared_namespace}' does not match path. Expected: '{ns_prefix}...'")
break
return None
def p3_3_classname_matches_filename(file_path: str, content: str) -> Optional[dict]:
"""P3.3: Klassenname muss Dateiname entsprechen."""
if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):
return None
class_match = re.search(r"(?:class|interface|trait|enum)\s+(\w+)", content)
if not class_match:
return None # Keine Klasse = kein Check
class_name = class_match.group(1)
expected_name = Path(file_path).stem
if class_name != expected_name:
return block("P3.3", f"Class '{class_name}' does not match filename '{expected_name}'")
return None
def p3_4_public_method_return_type(file_path: str, content: str) -> Optional[dict]:
"""P3.4: Public Methods müssen Return-Type haben."""
if is_in_allowlist(file_path, GLOBAL_ALLOWLIST):
return None
# Finde public functions
public_methods = re.findall(
r"public\s+function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\S+)?",
content
)
for method in public_methods:
if method.startswith("__"):
continue # Magic methods überspringen
# Prüfe ob Return-Type vorhanden
pattern = rf"public\s+function\s+{method}\s*\([^)]*\)\s*:\s*\S+"
if not re.search(pattern, content):
return block("P3.4", f"Public method '{method}' missing return type")
return None
# =============================================================================
# RULE COLLECTION
# =============================================================================
RULES = [
p3_1_strict_types,
p3_2_namespace_matches_path,
p3_3_classname_matches_filename,
p3_4_public_method_return_type,
]