File size: 2,351 Bytes
f8a5c40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""Sprint S7.4 — invariant CLAUDE.md : pas de ``except Exception: pass``.

CLAUDE.md déclare comme règle stricte :

    **Ne jamais mettre `except Exception: pass`** : remplacer par
    `logger.warning("[module] fonctionnalité dégradée : %s", e)`.

Ce test scanne la base et échoue si un nouveau silent-except est
introduit.  Trois patterns silencieux interdits :

1. ``except Exception: pass`` (le plus visible).
2. ``except Exception: ...`` (Ellipsis comme no-op).
3. ``except Exception as e: pass`` (typo qui ressemble à du log).

Best-effort accepté avec ``logger.debug(...)`` minimum (signal
opérationnel pour ops).
"""

from __future__ import annotations

import re
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
PRODUCTION = REPO_ROOT / "picarones"

# Patterns interdits
_SILENT_EXCEPT_RE = re.compile(
    r"except\s+(?:\([^)]*Exception[^)]*\)|Exception)\s*"
    r"(?:as\s+\w+)?\s*:\s*(?:#[^\n]*)?\s*\n"
    r"(\s+)(pass|\.\.\.)\s*$",
    re.MULTILINE,
)


def _scan_silent_excepts() -> list[tuple[str, int, str]]:
    issues: list[tuple[str, int, str]] = []
    for f in PRODUCTION.rglob("*.py"):
        if "__pycache__" in str(f):
            continue
        text = f.read_text(encoding="utf-8")
        for m in _SILENT_EXCEPT_RE.finditer(text):
            line_no = text[: m.start()].count("\n") + 1
            relpath = str(f.relative_to(REPO_ROOT))
            issues.append((relpath, line_no, m.group(2)))
    return issues


def test_no_silent_except_exception_in_production() -> None:
    """Aucun ``except Exception: pass`` dans le code production.

    Le test échoue si un mainteneur introduit un silent except.
    Pour fixer : remplacer ``pass`` par ``logger.warning(...)``
    ou ``logger.debug(...)`` selon la criticité.
    """
    issues = _scan_silent_excepts()
    if issues:
        formatted = "\n".join(
            f"  - {f}:{line} ({kind})" for f, line, kind in issues
        )
        raise AssertionError(
            f"Violations CLAUDE.md règle « pas de "
            f"``except Exception: pass`` » détectées dans "
            f"{len(issues)} emplacement(s) :\n{formatted}\n\n"
            f"Remplacer ``pass`` par "
            f"``logger.warning('[module] feature dégradée : %s', e)`` "
            f"ou ``logger.debug(...)`` pour les best-effort."
        )