rules_quality.py
- Pfad:
/var/www/tools/ki-protokoll/claude-hook/quality/rules_quality.py
- Namespace: claude-hook.quality
- Zeilen: 240 | Größe: 8,302 Bytes
- Geändert: 2025-12-25 16:56:09 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 93
- Dependencies: 90 (25%)
- LOC: 86 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 80 (10%)
Issues 2
| Zeile |
Typ |
Beschreibung |
| 112 |
magic_number |
Magic Number gefunden: 100 |
| 149 |
magic_number |
Magic Number gefunden: 100 |
Dependencies 7
- extends Rule
- use re
- use pathlib.Path
- use typing.List
- use rule_base.Rule
- use rule_base.DTO_ALLOWLIST
- use rule_base.count_non_empty_lines
Klassen 11
-
W1_1_ClassSize
class
Zeile 19
-
W1_2_PublicMethodCount
class
Zeile 35
-
W1_3_ConstructorParams
class
Zeile 48
-
W1_4_DependencyCount
class
Zeile 65
-
W1_5_SuspiciousNames
class
Zeile 78
-
W4_1_AnemicModel
class
Zeile 98
-
W4_2_ClassWithoutBehavior
class
Zeile 117
-
W4_3_LowEncapsulation
class
Zeile 134
-
W4_4_HighStaticRatio
class
Zeile 154
-
W6_1_InterfaceTooLarge
class
Zeile 173
-
W6_2_TooManyInterfaces
class
Zeile 206
Code
#!/usr/bin/env python3
"""
Quality Rules (W1, W4, W6) - SRP, KISS, OOP, SOLID.
Regeln für Code-Qualität: Klassengröße, Single Responsibility,
OOP-Prinzipien, Encapsulation, Interface Segregation.
"""
import re
from pathlib import Path
from typing import List
from .rule_base import Rule, DTO_ALLOWLIST
# =============================================================================
# W1: SRP + KISS - Single Responsibility & Keep It Simple
# =============================================================================
class W1_1_ClassSize(Rule):
"""W1.1: Klassengröße (max 300 LOC)."""
def check(self, file_path: str, content: str) -> List[str]:
from .rule_base import count_non_empty_lines
loc = count_non_empty_lines(content)
warnings = []
if loc > 300:
warnings.append(f"W1.1: Class has {loc} lines (max 300). Consider splitting.")
elif loc > 200:
warnings.append(f"W1.1: Class has {loc} lines (approaching limit of 300).")
return warnings
class W1_2_PublicMethodCount(Rule):
"""W1.2: Anzahl public methods (max 10)."""
def check(self, file_path: str, content: str) -> List[str]:
public_methods = re.findall(r"public\s+function\s+\w+", content)
count = len(public_methods)
if count > 10:
return [f"W1.2: Class has {count} public methods (max 10). Consider splitting."]
return []
class W1_3_ConstructorParams(Rule):
"""W1.3: Constructor-Parameter (max 5)."""
def check(self, file_path: str, content: str) -> List[str]:
constructor_match = re.search(r"function\s+__construct\s*\(([^)]*)\)", content, re.DOTALL)
if not constructor_match:
return []
params = constructor_match.group(1)
param_count = len([p for p in params.split(',') if p.strip()])
if param_count > 5:
return [f"W1.3: Constructor has {param_count} parameters (max 5). Consider refactoring."]
return []
class W1_4_DependencyCount(Rule):
"""W1.4: Anzahl use-Statements (max 10)."""
def check(self, file_path: str, content: str) -> List[str]:
use_statements = re.findall(r"^use\s+", content, re.MULTILINE)
count = len(use_statements)
if count > 10:
return [f"W1.4: Class has {count} dependencies (max 10). Consider reducing coupling."]
return []
class W1_5_SuspiciousNames(Rule):
"""W1.5: Verdächtige Namen (Manager, And)."""
def check(self, file_path: str, content: str) -> List[str]:
filename = Path(file_path).stem
hints = []
if "Manager" in filename:
hints.append(f"W1.5: Hint - '{filename}' contains 'Manager'. Verify single responsibility.")
if re.search(r"[a-z]And[A-Z]", filename):
hints.append(f"W1.5: Hint - '{filename}' contains 'And'. May indicate multiple responsibilities.")
return hints
# =============================================================================
# W4: OOP - Object-Oriented Programming Principles
# =============================================================================
class W4_1_AnemicModel(Rule):
"""W4.1: Anämisches Model (hohe Accessor-Ratio > 70%)."""
def __init__(self):
super().__init__(allowlist=DTO_ALLOWLIST)
def check(self, file_path: str, content: str) -> List[str]:
getters = len(re.findall(r"public\s+function\s+get[A-Z]\w*\s*\(", content))
setters = len(re.findall(r"public\s+function\s+set[A-Z]\w*\s*\(", content))
all_public = len(re.findall(r"public\s+function\s+\w+", content))
if all_public > 4:
accessor_ratio = (getters + setters) / all_public
if accessor_ratio > 0.7:
return [f"W4.1: Potential anemic model: {int(accessor_ratio*100)}% accessors. Consider adding behavior."]
return []
class W4_2_ClassWithoutBehavior(Rule):
"""W4.2: Klasse ohne Verhalten."""
def __init__(self):
super().__init__(allowlist=DTO_ALLOWLIST)
def check(self, file_path: str, content: str) -> List[str]:
has_properties = bool(re.search(r"(?:private|protected|public)\s+.*\$\w+", content))
methods = re.findall(r"(?:public|private|protected)\s+function\s+(\w+)", content)
real_methods = [m for m in methods if not m.startswith(("get", "set", "__"))]
if has_properties and len(real_methods) == 0:
return ["W4.2: Class has properties but no behavior methods."]
return []
class W4_3_LowEncapsulation(Rule):
"""W4.3: Niedrige Kapselung (> 50% exposed properties)."""
def __init__(self):
super().__init__(allowlist=DTO_ALLOWLIST)
def check(self, file_path: str, content: str) -> List[str]:
public_props = len(re.findall(r"public\s+(?!function|const).*\$", content))
protected_props = len(re.findall(r"protected\s+(?!function|const).*\$", content))
private_props = len(re.findall(r"private\s+(?!function|const).*\$", content))
total = public_props + protected_props + private_props
if total > 3:
exposed_ratio = (public_props + protected_props) / total
if exposed_ratio > 0.5:
return [f"W4.3: Low encapsulation: {int(exposed_ratio*100)}% exposed properties."]
return []
class W4_4_HighStaticRatio(Rule):
"""W4.4: Hohe Static-Ratio (> 50% static methods)."""
def check(self, file_path: str, content: str) -> List[str]:
static_methods = len(re.findall(r"public\s+static\s+function", content))
all_methods = len(re.findall(r"public\s+function", content))
if all_methods > 0 and static_methods > 2:
static_ratio = static_methods / all_methods
if static_ratio > 0.5:
return [f"W4.4: High static method ratio: {static_methods}/{all_methods}. Consider instance methods."]
return []
# =============================================================================
# W6: SOLID + DIP - Interface Segregation Principle
# =============================================================================
class W6_1_InterfaceTooLarge(Rule):
"""W6.1: Interface mit zu vielen Methoden (max 7)."""
def check(self, file_path: str, content: str) -> List[str]:
interface_match = re.search(r"interface\s+(\w+)[^{]*\{", content)
if not interface_match:
return []
interface_name = interface_match.group(1)
interface_start = interface_match.end()
interface_content = content[interface_start:]
# Finde Interface-Ende
brace_count = 1
end_pos = 0
for i, char in enumerate(interface_content):
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
end_pos = i
break
interface_body = interface_content[:end_pos]
method_count = len(re.findall(r"function\s+\w+", interface_body))
if method_count > 7:
return [f"W6.1: Interface '{interface_name}' has {method_count} methods (max 7). Consider splitting (ISP)."]
return []
class W6_2_TooManyInterfaces(Rule):
"""W6.2: Klasse implementiert zu viele Interfaces (max 5)."""
def check(self, file_path: str, content: str) -> List[str]:
implements_match = re.search(r"implements\s+([^{]+)", content)
if not implements_match:
return []
interfaces = [i.strip() for i in implements_match.group(1).split(',') if i.strip()]
count = len(interfaces)
if count > 5:
return [f"W6.2: Class implements {count} interfaces (max 5). Potential role confusion."]
return []
# =============================================================================
# RULE COLLECTION
# =============================================================================
RULES = [
W1_1_ClassSize(),
W1_2_PublicMethodCount(),
W1_3_ConstructorParams(),
W1_4_DependencyCount(),
W1_5_SuspiciousNames(),
W4_1_AnemicModel(),
W4_2_ClassWithoutBehavior(),
W4_3_LowEncapsulation(),
W4_4_HighStaticRatio(),
W6_1_InterfaceTooLarge(),
W6_2_TooManyInterfaces(),
]