full_scan.py

Code Hygiene Score: 77

Keine Issues gefunden.

Dependencies 11

Funktionen 7

Code

#!/usr/bin/python3
"""
Full Codebase Quality Scan.

Scannt alle PHP-Dateien gegen Pre- und Post-Hook Regeln.
Erstellt optional Tasks für Violations.

Usage:
    full_scan.py                    # Scan mit Output
    full_scan.py --json             # JSON-Output
    full_scan.py --create-tasks     # Tasks für Violations erstellen
    full_scan.py --path /src        # Nur bestimmten Pfad scannen
"""

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Any

# Eigene Module laden
sys.path.insert(0, str(Path(__file__).parent))
import pre_rules
import post_rules

# =============================================================================
# CONFIGURATION
# =============================================================================

DEFAULT_SCAN_PATH = "/var/www/dev.campus.systemische-tools.de/src"

SKIP_DIRS = [
    "/vendor/",
    "/node_modules/",
    "/.git/",
    "/cache/",
    "/var/cache/",
    "/var/log/",
]

# =============================================================================
# SCANNER
# =============================================================================


def find_php_files(base_path: str) -> List[Path]:
    """Findet alle PHP-Dateien im Pfad."""
    base = Path(base_path)
    if not base.exists():
        return []

    files = []
    for php_file in base.rglob("*.php"):
        file_str = str(php_file)
        if not any(skip in file_str for skip in SKIP_DIRS):
            files.append(php_file)

    return sorted(files)


def scan_file(file_path: Path) -> Dict[str, Any]:
    """Scannt eine einzelne Datei."""
    try:
        content = file_path.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError) as e:
        return {
            "file": str(file_path),
            "error": str(e),
            "blocks": [],
            "warnings": [],
        }

    file_str = str(file_path)

    # Pre-Rules (Blocks)
    pre_result = pre_rules.check(file_str, content)
    blocks = []
    if not pre_result.get("allowed", True):
        blocks.append(pre_result.get("message", "Unknown block"))

    # Post-Rules (Warnings)
    post_result = post_rules.check(file_str, content)
    warnings = post_result.get("warnings", [])

    return {
        "file": file_str,
        "blocks": blocks,
        "warnings": warnings,
    }


def scan_all(base_path: str) -> Dict[str, Any]:
    """Scannt alle PHP-Dateien."""
    files = find_php_files(base_path)

    results = {
        "scan_path": base_path,
        "total_files": len(files),
        "files_with_blocks": 0,
        "files_with_warnings": 0,
        "total_blocks": 0,
        "total_warnings": 0,
        "violations": [],
    }

    for file_path in files:
        file_result = scan_file(file_path)

        if file_result.get("error"):
            results["violations"].append(file_result)
            continue

        has_issues = False

        if file_result["blocks"]:
            results["files_with_blocks"] += 1
            results["total_blocks"] += len(file_result["blocks"])
            has_issues = True

        if file_result["warnings"]:
            results["files_with_warnings"] += 1
            results["total_warnings"] += len(file_result["warnings"])
            has_issues = True

        if has_issues:
            results["violations"].append(file_result)

    return results


# =============================================================================
# OUTPUT FORMATTERS
# =============================================================================


def format_console(results: Dict[str, Any]) -> str:
    """Formatiert Ergebnis für Console-Output."""
    lines = []
    lines.append("=" * 70)
    lines.append("QUALITY SCAN REPORT")
    lines.append("=" * 70)
    lines.append(f"Scan Path: {results['scan_path']}")
    lines.append(f"Total Files: {results['total_files']}")
    lines.append("")
    lines.append(f"Files with BLOCKS: {results['files_with_blocks']}")
    lines.append(f"Files with WARNINGS: {results['files_with_warnings']}")
    lines.append(f"Total BLOCKS: {results['total_blocks']}")
    lines.append(f"Total WARNINGS: {results['total_warnings']}")
    lines.append("")

    if results["violations"]:
        lines.append("-" * 70)
        lines.append("VIOLATIONS:")
        lines.append("-" * 70)

        for v in results["violations"]:
            # Kurzer Pfad
            short_path = v["file"].replace("/var/www/dev.campus.systemische-tools.de/", "")
            lines.append(f"\n{short_path}")

            if v.get("error"):
                lines.append(f"  ERROR: {v['error']}")

            for block in v.get("blocks", []):
                lines.append(f"  [BLOCK] {block}")

            for warning in v.get("warnings", []):
                lines.append(f"  [WARN]  {warning}")

    lines.append("")
    lines.append("=" * 70)

    if results["total_blocks"] > 0:
        lines.append("STATUS: FAILED (has blocking violations)")
    elif results["total_warnings"] > 0:
        lines.append("STATUS: PASSED with warnings")
    else:
        lines.append("STATUS: PASSED")

    lines.append("=" * 70)

    return "\n".join(lines)


def format_json(results: Dict[str, Any]) -> str:
    """Formatiert Ergebnis als JSON."""
    return json.dumps(results, indent=2, ensure_ascii=False)


# =============================================================================
# TASK CREATION
# =============================================================================


def create_tasks_for_violations(results: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Erstellt Tasks für alle Violations."""
    try:
        import task_creator
    except ImportError:
        return [{"error": "task_creator module not found"}]

    created = []

    for v in results["violations"]:
        file_path = v["file"]

        for block in v.get("blocks", []):
            # Parse Rule-ID aus Message
            rule_id = "UNKNOWN"
            if "[" in block and "]" in block:
                rule_id = block.split("[")[1].split("]")[0]

            try:
                task_id = task_creator.create_violation_task(
                    file_path, rule_id, block, "block"
                )
                created.append({
                    "file": file_path,
                    "rule": rule_id,
                    "type": "block",
                    "task_id": task_id,
                })
            except Exception as e:
                created.append({
                    "file": file_path,
                    "rule": rule_id,
                    "type": "block",
                    "error": str(e),
                })

        for warning in v.get("warnings", []):
            # Parse Rule-ID aus Warning
            rule_id = "UNKNOWN"
            if warning.startswith("W") and ":" in warning:
                rule_id = warning.split(":")[0]

            try:
                task_id = task_creator.create_warning_task(file_path, rule_id, warning)
                created.append({
                    "file": file_path,
                    "rule": rule_id,
                    "type": "warning",
                    "task_id": task_id,
                })
            except Exception as e:
                created.append({
                    "file": file_path,
                    "rule": rule_id,
                    "type": "warning",
                    "error": str(e),
                })

    return created


# =============================================================================
# MAIN
# =============================================================================


def main():
    parser = argparse.ArgumentParser(description="Full Codebase Quality Scan")
    parser.add_argument(
        "--path",
        default=DEFAULT_SCAN_PATH,
        help=f"Path to scan (default: {DEFAULT_SCAN_PATH})",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Output as JSON",
    )
    parser.add_argument(
        "--create-tasks",
        action="store_true",
        help="Create tasks for violations",
    )
    parser.add_argument(
        "--blocks-only",
        action="store_true",
        help="Only report blocking violations",
    )

    args = parser.parse_args()

    # Scan durchführen
    results = scan_all(args.path)

    # Wenn blocks-only, Warnings entfernen
    if args.blocks_only:
        for v in results["violations"]:
            v["warnings"] = []
        results["violations"] = [
            v for v in results["violations"] if v["blocks"]
        ]
        results["total_warnings"] = 0
        results["files_with_warnings"] = 0

    # Tasks erstellen wenn gewünscht
    if args.create_tasks:
        created_tasks = create_tasks_for_violations(results)
        results["created_tasks"] = created_tasks

    # Output
    if args.json:
        print(format_json(results))
    else:
        print(format_console(results))
        if args.create_tasks and results.get("created_tasks"):
            print(f"\nCreated {len(results['created_tasks'])} tasks for violations.")

    # Exit Code
    if results["total_blocks"] > 0:
        sys.exit(1)
    sys.exit(0)


if __name__ == "__main__":
    main()
← Übersicht