"""Ancrage des citations : une URL citée en commentaire doit venir des notes de recherche web de la session. Un candidat signalé passe derrière les autres à résultat de tests égal ; il n'est jamais rejeté si ses tests passent. Les URL du code lui-même (chaînes, constantes) ne sont pas concernées : seules les citations le sont. """ from __future__ import annotations import io import re import tokenize _URL = re.compile(r"https?://[^\s)\]>'\"`]+") def _clean(url: str) -> str: return url.rstrip(".,;:") def comment_urls(code: str) -> list[str]: urls: list[str] = [] try: for tok in tokenize.generate_tokens(io.StringIO(code).readline): if tok.type == tokenize.COMMENT: urls += [_clean(u) for u in _URL.findall(tok.string)] except (tokenize.TokenError, IndentationError, SyntaxError): return [] return urls class CitationCheck: def check(self, files, notes=None) -> list[str]: allowed = {_clean(u) for n in (notes or []) for u in _URL.findall(n)} issues = [] for name, code in sorted(files.items()): for url in comment_urls(code): if url not in allowed: issues.append(f"citation non sourcée dans {name} : {url[:120]}") return issues