fix: A38 phase 2 — 0 doublon de fonction globale + garde-fou (v7.34.0)
- Inventaire exhaustif des 13 noms `function NAME` définis 2+ fois (templates + static/js) avec scan de profondeur de brace (strings, comments, backticks gérés) : 12 sont déjà scopés dans des IIFEs depuis A27 (escHtml/flush/emit/setMeta/initials/up/esc/show/close…) — aucun conflit de page possible. - Seul doublon GLOBALE = openCardDetail (corps byte-identiques ×2 dans board_fragment + detailed_board, fragments de vues mutuellement exclusifs) → dédupliquée vers static/js/app.js, 2 copies supprimées ; les onclick/@click des deux fragments appellent la même définition (owner/repo globaux fournis par board.js au moment du clic). - test_no_duplicate_global_functions : garde-fou 0-doublon entre templates et static/js (scanner naïf, plafond ponytail commenté). Reste A38 : méthodes jumelles library/local_workspace (~9-21 noms communs, corps divergents) → fusion workspace-tree.js reportée (réconciliation sans E2E, même logique que A39/A20). suite **1093/1093** · ruff OK · node --check vert · docs à jour
This commit is contained in:
@@ -366,6 +366,73 @@ def test_csrf_server_rendered_no_placeholder(client):
|
||||
assert token == cookie, (token[:8], cookie[:8])
|
||||
|
||||
|
||||
def test_no_duplicate_global_functions():
|
||||
"""A38 : aucune fonction `function NAME` GLOBALE (profondeur 0) définie
|
||||
2+ fois entre les templates et static/js — les paires à risque d'ombre
|
||||
silencieuse (onDoc, escHtml, openCardDetail…) vivent dans des IIFEs ou
|
||||
sont dédupliquées (openCardDetail → app.js)."""
|
||||
import pathlib as _pathlib
|
||||
import re as _re
|
||||
|
||||
files = list(_pathlib.Path("app/templates").glob("*.html")) + list(
|
||||
_pathlib.Path("static/js").glob("*.js")
|
||||
)
|
||||
found: dict[str, list[str]] = {}
|
||||
for p in files:
|
||||
s = p.read_text(encoding="utf-8", errors="ignore")
|
||||
depth = 0
|
||||
line = 1
|
||||
i = 0
|
||||
state = None
|
||||
n = len(s)
|
||||
# ponytail: scanner naïve (strings/comments/backticks) — un faux
|
||||
# positif se voit immédiatement à la lecture du nom signalé
|
||||
while i < n:
|
||||
c = s[i]
|
||||
if c == "\n":
|
||||
line += 1
|
||||
if state is None:
|
||||
if c in ('"', "'"):
|
||||
state = c
|
||||
i += 1
|
||||
continue
|
||||
if c == "`":
|
||||
state = c
|
||||
i += 1
|
||||
continue
|
||||
if c == "/" and i + 1 < n and s[i + 1] == "/":
|
||||
state = "//"
|
||||
i += 2
|
||||
continue
|
||||
if c == "/" and i + 1 < n and s[i + 1] == "*":
|
||||
state = "/*"
|
||||
i += 2
|
||||
continue
|
||||
if c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
else:
|
||||
if c == "\\":
|
||||
i += 2
|
||||
continue
|
||||
if (state in ('"', "'") and c == state) or (state == "`" and c == state):
|
||||
state = None
|
||||
elif state == "//" and c == "\n":
|
||||
state = None
|
||||
elif state == "/*" and c == "*" and i + 1 < n and s[i + 1] == "/":
|
||||
state = None
|
||||
i += 2
|
||||
continue
|
||||
i += 1
|
||||
if state is None and depth == 0 and s.startswith("function ", i):
|
||||
m = _re.match(r"function\s+([A-Za-z_]\w*)", s[i : i + 60])
|
||||
if m:
|
||||
found.setdefault(m.group(1), []).append(f"{p.name}:{line}")
|
||||
dups = {k: v for k, v in found.items() if len(v) >= 2}
|
||||
assert dups == {}, dups
|
||||
|
||||
|
||||
def test_no_duplicate_routes():
|
||||
"""A24 : deux routes même méthode+chemin → l'une écrase silencieusement l'autre."""
|
||||
from app.main import app
|
||||
|
||||
Reference in New Issue
Block a user