From 0b8cc125b3a8b6dd075b42c25723c1399bc18d48 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Mon, 27 Jul 2026 09:55:11 +0300 Subject: [PATCH] =?UTF-8?q?suite=20check:=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=BA=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D1=80=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=B0=D0=BA=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - заголовок правило объявляет, а не ссылается на него: при опечатке в префиксе одна ошибка формы давала вторую находку о неразрешённой ссылке - числительные в итоге согласованы с существительным --- internal/check/check_test.go | 14 ++++++++++++++ internal/check/refs.go | 9 ++++++++- internal/cli/suitecheck.go | 19 +++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/internal/check/check_test.go b/internal/check/check_test.go index b80a77e..3b47730 100644 --- a/internal/check/check_test.go +++ b/internal/check/check_test.go @@ -106,6 +106,20 @@ func TestCleanSuite(t *testing.T) { } } +// TestRuleHeadingIsNotAReference держит границу между объявлением и ссылкой. +// Заголовок с чужим префиксом — ошибка формы, и только она: разрешать заголовок +// по манифесту не к чему, иначе одна опечатка даёт две находки о разном. +func TestRuleHeadingIsNotAReference(t *testing.T) { + f := base() + f["conventions/time.md"] = strings.Replace(baseTime, "### TIME-1.", "### GTIM-1.", 1) + + for _, got := range run(t, f) { + if got.Family == check.Links { + t.Errorf("заголовок разобран как ссылка: %s", got.Msg) + } + } +} + // rule собирает правило целиком, чтобы тесты не повторяли его форму. func rule(id, title, norm, rationale string) string { return "\n### " + id + ". " + title + "\n\n**ДОЛЖЕН.** " + norm + "\n\n**ПОЧЕМУ.** " + rationale + "\n" diff --git a/internal/check/refs.go b/internal/check/refs.go index 794b3e9..67f9af4 100644 --- a/internal/check/refs.go +++ b/internal/check/refs.go @@ -26,10 +26,17 @@ type Ref struct { // бэктиках идентификатор стоит образцом записи, а не ссылкой на утверждение, — // иначе строка «на конкретное правило ссылаются идентификатором (`SLOG-27`)» // требовала бы, чтобы правило SLOG-27 существовало. +// +// Заголовки правил пропускаются: заголовок правило объявляет, а не ссылается +// на него, и разрешать его по манифесту не к чему. func refsIn(d *doc.Document, from, to int) []Ref { + heading := make(map[int]bool, len(d.Rules)) + for _, r := range d.Rules { + heading[r.Line] = true + } var out []Ref for n := from; n <= to; n++ { - if d.Fenced(n) { + if d.Fenced(n) || heading[n] { continue } out = append(out, refsInLine(n, doc.StripInline(d.Line(n)))...) diff --git a/internal/cli/suitecheck.go b/internal/cli/suitecheck.go index a6d9956..48d0d28 100644 --- a/internal/cli/suitecheck.go +++ b/internal/cli/suitecheck.go @@ -77,8 +77,10 @@ func printReport(w io.Writer, rep *check.Report, s *suite.Suite, quiet bool) { if len(findings) > 0 { fmt.Fprintln(w) } - fmt.Fprintf(w, "набор: %d файлов, %d тем, версия языка %d (%s)\n", - len(s.Docs), len(s.Manifest.LiveTopics()), s.Manifest.Language.Version, s.Manifest.Language.Lang) + fmt.Fprintf(w, "набор: %s, %s, версия языка %d (%s)\n", + plural(len(s.Docs), "файл", "файла", "файлов"), + plural(len(s.Manifest.LiveTopics()), "тема", "темы", "тем"), + s.Manifest.Language.Version, s.Manifest.Language.Lang) switch { case rep.Errors() > 0: fmt.Fprintf(w, "ошибок: %d, предупреждений: %d\n", rep.Errors(), rep.Warnings()) @@ -89,6 +91,19 @@ func printReport(w io.Writer, rep *check.Report, s *suite.Suite, quiet bool) { } } +// plural согласует существительное с числом: 1 файл, 2 файла, 5 файлов. +func plural(n int, one, few, many string) string { + word := many + switch { + case n%100 >= 11 && n%100 <= 14: + case n%10 == 1: + word = one + case n%10 >= 2 && n%10 <= 4: + word = few + } + return fmt.Sprintf("%d %s", n, word) +} + func label(s check.Severity) string { if s == check.Warning { return "предупреждение"