suite check: реализована проверка целостности набора
- разбор документа по языку конвенций: шапка, области правил, блоки под метками - проверки формы правила, распространения, ссылок и самого suite.toml - словарь языка живёт в бинаре реестром «версия × естественный язык», темы и префиксы берутся только из манифеста
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.vakhrushev.me/av/convy/internal/doc"
|
||||
"git.vakhrushev.me/av/convy/internal/suite"
|
||||
)
|
||||
|
||||
// refRe ловит идентификатор правила: четыре заглавные латинские буквы, дефис,
|
||||
// номер и необязательный номер строки таблицы.
|
||||
var refRe = regexp.MustCompile(`\b([A-Z]{4})-(\d+)(?:\.(\d+))?`)
|
||||
|
||||
// Ref — ссылка на правило, найденная в тексте.
|
||||
type Ref struct {
|
||||
Prefix string
|
||||
Num int
|
||||
Sub int
|
||||
Line int
|
||||
Text string
|
||||
}
|
||||
|
||||
// refsIn собирает ссылки в диапазоне строк документа. Инлайн-код вырезан: в
|
||||
// бэктиках идентификатор стоит образцом записи, а не ссылкой на утверждение, —
|
||||
// иначе строка «на конкретное правило ссылаются идентификатором (`SLOG-27`)»
|
||||
// требовала бы, чтобы правило SLOG-27 существовало.
|
||||
func refsIn(d *doc.Document, from, to int) []Ref {
|
||||
var out []Ref
|
||||
for n := from; n <= to; n++ {
|
||||
if d.Fenced(n) {
|
||||
continue
|
||||
}
|
||||
out = append(out, refsInLine(n, doc.StripInline(d.Line(n)))...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func refsInLine(n int, text string) []Ref {
|
||||
var out []Ref
|
||||
for _, m := range refRe.FindAllStringSubmatch(text, -1) {
|
||||
num, err := strconv.Atoi(m[2])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ref := Ref{Prefix: m[1], Num: num, Line: n, Text: m[0]}
|
||||
if m[3] != "" {
|
||||
ref.Sub, _ = strconv.Atoi(m[3])
|
||||
}
|
||||
out = append(out, ref)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// checkLinks проверяет, что каждая ссылка разрешается. Неразрешённый
|
||||
// идентификатор всегда ошибка: с заглушками на месте снятых правил третьего
|
||||
// исхода нет — ссылка ведёт либо к правилу, либо к объяснению, почему его
|
||||
// сняли (META-31, META-32).
|
||||
func checkLinks(s *suite.Suite, d *doc.Document, rep *Report) {
|
||||
for _, ref := range refsIn(d, d.Body, d.Len()) {
|
||||
if strings.HasPrefix(ref.Prefix, "X") {
|
||||
// Префикс потребителя: локальные правила чужого репозитория
|
||||
// набору не видны и разрешению не подлежат.
|
||||
continue
|
||||
}
|
||||
if s.Manifest.PrefixRetired(ref.Prefix) {
|
||||
rep.Errorf(Links, d.Path, ref.Line,
|
||||
"ссылка %s ведёт на выбывший префикс %s", ref.Text, ref.Prefix)
|
||||
continue
|
||||
}
|
||||
target, ok := s.ByPrefix[ref.Prefix]
|
||||
if !ok {
|
||||
if _, declared := s.Manifest.PathOf(ref.Prefix); declared {
|
||||
// Файл объявлен, но не прочитан — о нём уже сказано
|
||||
// проверкой манифеста, второй раз не повторяем.
|
||||
continue
|
||||
}
|
||||
rep.Errorf(Links, d.Path, ref.Line,
|
||||
"ссылка %s ведёт на префикс %s, которого в манифесте набора нет", ref.Text, ref.Prefix)
|
||||
continue
|
||||
}
|
||||
rule, ok := ruleByNum(target, ref.Num)
|
||||
if !ok {
|
||||
rep.Errorf(Links, d.Path, ref.Line,
|
||||
"ссылка %s не разрешается: в %s правила с номером %d нет", ref.Text, target.Path, ref.Num)
|
||||
continue
|
||||
}
|
||||
if ref.Sub > 0 && !mentions(target, rule, ref.Text) {
|
||||
rep.Errorf(Links, d.Path, ref.Line,
|
||||
"ссылка %s не разрешается: в области %s такой строки нет", ref.Text, rule.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mentions отвечает, встречается ли текст ссылки в области правила. Так
|
||||
// проверяется номер строки таблицы: сама строка его и несёт.
|
||||
func mentions(d *doc.Document, rule doc.Rule, text string) bool {
|
||||
for n := rule.Line; n <= rule.End; n++ {
|
||||
if strings.Contains(d.Line(n), text) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ruleByNum(d *doc.Document, num int) (doc.Rule, bool) {
|
||||
for _, r := range d.Rules {
|
||||
if r.Num == num {
|
||||
return r, true
|
||||
}
|
||||
}
|
||||
return doc.Rule{}, false
|
||||
}
|
||||
Reference in New Issue
Block a user