rules_failfast.py
- Pfad:
/var/www/tools/ki-protokoll/claude-hook/quality/rules_failfast.py - Namespace: claude-hook.quality
- Zeilen: 160 | Größe: 5,196 Bytes
- Geändert: 2025-12-28 12:40:54 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 100
- Dependencies: 100 (25%)
- LOC: 100 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 5
- extends Rule
- use re
- use typing.List
- use rule_base.Rule
- use rule_base.GLOBAL_ALLOWLIST
Klassen 5
-
W8_1_EmptyCatchclass Zeile 19 -
W8_2_ErrorSuppressionclass Zeile 46 -
W8_3_CatchWithOnlyReturnclass Zeile 69 -
W8_4_GenericExceptionclass Zeile 88 -
W8_5_ThrowInDestructorclass Zeile 113
Code
#!/usr/bin/env python3
"""
Post-Hook Fail Fast Regeln (WARN) - Fail Fast Principle.
W8.x Regeln: Warnt bei stillen Fehlern, Error Suppression und leeren Catch-Blöcken.
Prinzip: "Ungültige Zustände werden sofort erkannt. Fehler werden früh sichtbar gemacht."
"""
import re
from typing import List
from .rule_base import Rule, GLOBAL_ALLOWLIST
# =============================================================================
# W8: FAIL FAST
# =============================================================================
class W8_1_EmptyCatch(Rule):
"""W8.1: Leerer catch-Block - Fehler werden verschluckt."""
def check(self, file_path: str, content: str) -> List[str]:
warnings = []
# Leerer catch-Block: catch(...) { } oder catch(...) { // comment }
# Pattern: catch mit leerem Body oder nur Whitespace/Kommentaren
empty_catch_patterns = [
# Komplett leer
r"catch\s*\([^)]+\)\s*\{\s*\}",
# Nur Whitespace
r"catch\s*\([^)]+\)\s*\{\s+\}",
]
for pattern in empty_catch_patterns:
matches = re.findall(pattern, content)
if matches:
warnings.append(
f"W8.1: Empty catch block detected. Errors are silently swallowed. "
f"Log the exception or re-throw."
)
break
return warnings
class W8_2_ErrorSuppression(Rule):
"""W8.2: Error Suppression mit @ Operator."""
def check(self, file_path: str, content: str) -> List[str]:
warnings = []
# @ vor Variablen: @$var
var_suppression = re.findall(r"@\s*\$\w+", content)
# @ vor Funktionen: @function()
func_suppression = re.findall(r"@\s*[a-zA-Z_]\w*\s*\(", content)
total = len(var_suppression) + len(func_suppression)
if total > 0:
warnings.append(
f"W8.2: Error suppression with @ operator ({total}x). "
f"Handle errors explicitly instead of suppressing."
)
return warnings
class W8_3_CatchWithOnlyReturn(Rule):
"""W8.3: Catch-Block der nur return enthält - versteckt Fehlerursache."""
def check(self, file_path: str, content: str) -> List[str]:
warnings = []
# catch(...) { return null; } oder { return false; } oder { return; }
pattern = r"catch\s*\([^)]+\)\s*\{\s*return\s*(?:null|false|true|\d+)?;\s*\}"
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
warnings.append(
f"W8.3: Catch block only returns without logging ({len(matches)}x). "
f"Consider logging the exception before returning."
)
return warnings
class W8_4_GenericException(Rule):
"""W8.4: Generische Exception ohne spezifischen Catch."""
def check(self, file_path: str, content: str) -> List[str]:
warnings = []
# catch (Exception $e) oder catch (\Exception $e) ohne spezifischere catches davor
generic_catch = re.findall(r"catch\s*\(\s*\\?Exception\s+\$\w+\s*\)", content)
# Wenn generischer Catch, prüfe ob spezifische Catches vorhanden sind
if generic_catch:
specific_catch = re.findall(
r"catch\s*\(\s*\\?(?!Exception\b)[A-Z]\w*Exception\s+\$\w+\s*\)",
content
)
if not specific_catch:
warnings.append(
f"W8.4: Only generic Exception catch without specific handlers. "
f"Consider catching specific exceptions first."
)
return warnings
class W8_5_ThrowInDestructor(Rule):
"""W8.5: Exception in Destructor - kann zu unerwartetem Verhalten führen."""
def check(self, file_path: str, content: str) -> List[str]:
warnings = []
# Finde __destruct Methoden
destruct_match = re.search(
r"function\s+__destruct\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{",
content
)
if destruct_match:
# Suche throw innerhalb des Destructors (vereinfacht)
destruct_start = destruct_match.end()
# Finde das Ende des Destructors
brace_count = 1
pos = destruct_start
while pos < len(content) and brace_count > 0:
if content[pos] == '{':
brace_count += 1
elif content[pos] == '}':
brace_count -= 1
pos += 1
destruct_body = content[destruct_start:pos]
if re.search(r"\bthrow\s+", destruct_body):
warnings.append(
"W8.5: Exception thrown in __destruct(). "
"This can cause unexpected behavior. Catch and log instead."
)
return warnings
# =============================================================================
# RULE COLLECTION
# =============================================================================
RULES = [
W8_1_EmptyCatch(),
W8_2_ErrorSuppression(),
W8_3_CatchWithOnlyReturn(),
W8_4_GenericException(),
W8_5_ThrowInDestructor(),
]