rules_security.py

Code Hygiene Score: 100

Keine Issues gefunden.

Dependencies 4

Klassen 5

Code

#!/usr/bin/env python3
"""
Security & Architecture Rules (W2, W3, W7).

Regeln für MVC-Architektur, Type-Safety und Dependency Injection.
"""

import re
from typing import List
from .rule_base import Rule


# =============================================================================
# W2: MVC + CRUD - Architectural Boundaries
# =============================================================================

class W2_1_BusinessKeywordsInController(Rule):
    """W2.1: Business-Keywords in Controller."""

    def should_skip(self, file_path: str) -> bool:
        if "/Controller/" not in file_path:
            return True
        return super().should_skip(file_path)

    def check(self, file_path: str, content: str) -> List[str]:
        keywords = [
            (r"\bcalculate\w*\s*\(", "calculate"),
            (r"\bcompute\w*\s*\(", "compute"),
            (r"\bvalidate\w*\s*\(", "validate"),
            (r"\bprocess\w*\s*\(", "process"),
            (r"\btransform\w*\s*\(", "transform"),
            (r"\bconvert\w*\s*\(", "convert"),
        ]

        found = []
        for pattern, name in keywords:
            if re.search(pattern, content, re.IGNORECASE):
                found.append(name)

        if found:
            return [f"W2.1: Business keywords in Controller: {', '.join(found)}. Consider moving to Domain/UseCase."]

        return []


class W2_2_PrivateMethodsInController(Rule):
    """W2.2: Viele private Methoden in Controller (max 5)."""

    def should_skip(self, file_path: str) -> bool:
        if "/Controller/" not in file_path:
            return True
        return super().should_skip(file_path)

    def check(self, file_path: str, content: str) -> List[str]:
        private_methods = re.findall(r"private\s+function\s+\w+", content)
        count = len(private_methods)

        if count > 5:
            return [f"W2.2: Controller has {count} private methods (max 5). Extract to Service."]

        return []


# =============================================================================
# W3: PSR + Types - Type Safety
# =============================================================================

class W3_1_PotentialUntypedParams(Rule):
    """W3.1: Potentiell untypisierte Parameter."""

    def check(self, file_path: str, content: str) -> List[str]:
        # Heuristik: Suche nach $param direkt nach ( oder ,
        potential = re.findall(r"function\s+\w+\s*\([^)]*[(,]\s*\$", content)

        if potential:
            return ["W3.1: Potential untyped parameters detected. Run PHPStan for verification."]

        return []


class W3_3_MixedType(Rule):
    """W3.3: mixed Type verwendet."""

    def check(self, file_path: str, content: str) -> List[str]:
        if re.search(r":\s*mixed\b", content):
            return ["W3.3: 'mixed' type used. Consider more specific type."]

        return []


# =============================================================================
# W7: DI + AUTOWIRING - Dependency Injection
# =============================================================================

class W7_1_UseCaseInterfaceDependency(Rule):
    """W7.1: UseCase mit Interface-Dependency - Hinweis zur Registrierung."""

    def should_skip(self, file_path: str) -> bool:
        if "/UseCases/" not in file_path:
            return True
        return super().should_skip(file_path)

    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)
        interface_deps = re.findall(r"(\w+Interface)\s+\$", params)

        if interface_deps:
            return [f"W7.1: Hint - UseCase uses interfaces: {', '.join(interface_deps)}. Ensure registered in services.php."]

        return []


# =============================================================================
# RULE COLLECTION
# =============================================================================

RULES = [
    W2_1_BusinessKeywordsInController(),
    W2_2_PrivateMethodsInController(),
    W3_1_PotentialUntypedParams(),
    W3_3_MixedType(),
    W7_1_UseCaseInterfaceDependency(),
]
← Übersicht Graph