diff --git a/script/analyse/check_comment_hygiene.py b/script/analyse/check_comment_hygiene.py index a2e8f8d..d1c1795 100755 --- a/script/analyse/check_comment_hygiene.py +++ b/script/analyse/check_comment_hygiene.py @@ -68,7 +68,7 @@ except Exception: # pragma: no cover - repli si i18n indisponible return key -SUFFIXES = (".py", ".sh", ".bash") +SUFFIXES = (".py", ".sh", ".bash", ".go") # Ce qui vient d'ailleurs ou n'est pas du source : le dépôt ne le réécrit pas. EXCLUS = ( @@ -308,10 +308,70 @@ def blocs_shell(source): return _regroupe(commentaires) +def blocs_go(source): + """Les commentaires d'un fichier Go, les consécutifs regroupés. + + Trois pièges, et le premier est celui qui compte : « // » ouvre un + commentaire SAUF dans une chaîne — et « https:// » en porte deux. Go a + trois formes de chaîne, dont la brute entre accents graves, où la barre + oblique inverse n'échappe rien. Les commentaires de bloc « /* … */ » + couvrent plusieurs lignes, chacune comptant pour ce qu'elle dit. + """ + commentaires = [] + en_bloc = False + for numero, ligne in enumerate(source.split("\n"), start=1): + if en_bloc: + fin = ligne.find("*/") + texte = (ligne if fin < 0 else ligne[:fin]).strip() + if texte: + commentaires.append((numero, texte.lstrip("*").strip())) + if fin >= 0: + en_bloc = False + continue + quote = None + precedent = "" + index = 0 + while index < len(ligne): + caractere = ligne[index] + if precedent == "\\" and quote in ('"', "'"): + # La chaîne brute ignore l'échappement : seules les deux + # autres formes le connaissent. + precedent = "" + index += 1 + continue + if quote: + if caractere == quote: + quote = None + elif caractere in "\"'`": + quote = caractere + elif caractere == "/" and ligne[index : index + 2] == "//": + texte = ligne[index:].lstrip("/").strip() + if texte: + commentaires.append((numero, texte)) + break + elif caractere == "/" and ligne[index : index + 2] == "/*": + reste = ligne[index + 2 :] + fin = reste.find("*/") + texte = (reste if fin < 0 else reste[:fin]).strip() + if texte: + commentaires.append((numero, texte)) + if fin < 0: + en_bloc = True + break + index += 2 + fin + 2 + precedent = "" + continue + precedent = caractere + index += 1 + return _regroupe(commentaires) + + def blocs(chemin, source): """Les commentaires d'un fichier, selon son suffixe.""" if chemin.endswith(".py"): return blocs_python(source) + if chemin.endswith(".go"): + return blocs_go(source) return blocs_shell(source) diff --git a/test/test_check_comment_hygiene.py b/test/test_check_comment_hygiene.py index ae248a3..84d5173 100644 --- a/test/test_check_comment_hygiene.py +++ b/test/test_check_comment_hygiene.py @@ -134,6 +134,67 @@ class TestLeTemoignage(unittest.TestCase): self.assertEqual(2, len({t[1].lower() for t in trouves})) +class TestLeGo(unittest.TestCase): + """Go est entré dans le dépôt, et l'outil ne le lisait pas. + + Sondé avec des motifs interdits, il restait muet sur un « .go » et sortait + 0 : les commentaires du cache de téléchargement n'ont donc jamais été + relus par personne d'autre que leur auteur. + """ + + def texte(self, source): + """Le texte recollé de tous les blocs. Un bloc est un dict : + « line », « text », « lines », « offsets ».""" + return " ".join(b["text"] for b in hygiene.blocs_go(source)) + + def test_un_commentaire_de_ligne(self): + trouve = hygiene.inspect("x.go", "// Vécu : la panne\npackage main\n") + self.assertTrue(trouve, "un commentaire Go n'est pas inspecté") + + def test_une_url_en_chaine_nouvre_rien(self): + """« https:// » porte deux barres obliques : c'est LE piège du Go.""" + source = 'package main\n\nconst a = "https://vecu.example/hier"\n' + self.assertEqual(hygiene.blocs_go(source), []) + + def test_une_chaine_brute_nouvre_rien(self): + """L'accent grave délimite une chaîne où rien ne s'échappe.""" + source = ( + "package main\n\nconst a = `https://mesure.example/nous avons`\n" + ) + self.assertEqual(hygiene.blocs_go(source), []) + + def test_un_commentaire_apres_une_chaine(self): + source = ( + 'package main\n\nconst a = "https://x.example" // Vécu : ici\n' + ) + trouve = hygiene.recits(self.texte(source)) + self.assertTrue(trouve, "le commentaire qui suit une URL est perdu") + + def test_un_bloc_sur_plusieurs_lignes(self): + source = ( + "package main\n\n/*\nVécu : la semaine où\nnous avons vu.\n*/\n" + ) + self.assertTrue( + hygiene.blocs_go(source), "un commentaire /* */ n'est pas lu" + ) + texte = self.texte(source) + self.assertIn("Vécu", texte) + self.assertIn("nous avons", texte) + + def test_un_bloc_sur_une_seule_ligne(self): + source = "package main\n\nvar x = 1 /* Vécu : ici */\nvar y = 2\n" + self.assertTrue(hygiene.blocs_go(source)) + self.assertIn("Vécu", self.texte(source)) + + def test_le_code_apres_un_bloc_ferme_est_relu(self): + """Un « // » qui suit un bloc fermé sur la même ligne compte encore.""" + source = "package main\n\nvar x = 1 /* rien */ // Vécu : là\n" + self.assertIn("Vécu", self.texte(source)) + + def test_le_suffixe_est_balaye(self): + self.assertIn(".go", hygiene.SUFFIXES) + + class TestLesIdentifiants(unittest.TestCase): def test_un_courriel(self): self.assertIn(