suite check: закрыты пробелы в проверках шапки, иерархии и словаря
- документ без темы: ошибка, если такой не один или если он несёт ключи слоя — иначе конвенция, потерявшая topic, молча лишалась всех проверок распространения - иерархия заголовков: один заголовок первого уровня, без перескоков через уровень - имя темы: латиница и пригодность для имени файла — ошибкой, kebab-case — предупреждением - сценарные связки чужого словаря ловятся в начале строки, где их ставит сценарный блок, а не в середине фразы, где это чаще SQL
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"git.vakhrushev.me/av/convy/internal/manifest"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
func Suite(s *suite.Suite) *Report {
|
||||
rep := &Report{}
|
||||
checkManifest(s, rep)
|
||||
checkTopicNames(s, rep)
|
||||
checkBaseLayers(s, rep)
|
||||
checkSelfGoverning(s, rep)
|
||||
|
||||
for _, d := range s.Docs {
|
||||
checkForm(s, d, rep)
|
||||
@@ -87,6 +90,64 @@ func checkManifest(s *suite.Suite, rep *Report) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// topicNameRe is the hard bound: a topic name reaches the file system of
|
||||
// a consumer, so it has to be usable as a file name — Latin letters,
|
||||
// digits and the plain separators.
|
||||
topicNameRe = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`)
|
||||
// kebabRe is the recommended shape, and only a recommendation.
|
||||
kebabRe = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)
|
||||
)
|
||||
|
||||
// checkTopicNames checks the shape of topic names. A name lands both in a
|
||||
// consumer's file system and in their manifest, which is where the hard bound
|
||||
// comes from; lower kebab-case on top of that is a recommendation and speaks as
|
||||
// a warning.
|
||||
func checkTopicNames(s *suite.Suite, rep *Report) {
|
||||
for _, topic := range s.Manifest.LiveTopics() {
|
||||
switch {
|
||||
case !topicNameRe.MatchString(topic):
|
||||
rep.Errorf(Manifest, manifest.Name, 0,
|
||||
"the topic name %q is not usable as a file name: a name is written in Latin letters and travels into the file system of a consumer", topic)
|
||||
case !kebabRe.MatchString(topic):
|
||||
rep.Warnf(Manifest, manifest.Name, 0,
|
||||
"the topic name %q is not lower kebab-case, which is the recommended shape", topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkSelfGoverning guards the document without a topic.
|
||||
//
|
||||
// A topic-less document is the one the suite governs itself by: it travels
|
||||
// nowhere and cannot be subscribed to, so it gets no spread checks. Nothing in
|
||||
// the manifest tells it from a convention that lost its topic key, and the
|
||||
// silent loss is the expensive one — the file keeps every check of form while
|
||||
// quietly dropping every check about travelling to a consumer. Two markers make
|
||||
// that loss visible: such a document is one per suite, and it has no layer of
|
||||
// its own, hence neither axis keys nor a base.
|
||||
func checkSelfGoverning(s *suite.Suite, rep *Report) {
|
||||
var topicless []string
|
||||
for _, d := range s.Docs {
|
||||
if d.Front.Topic != "" {
|
||||
continue
|
||||
}
|
||||
topicless = append(topicless, d.Path)
|
||||
|
||||
if d.Front.Axis() || d.Front.Extends != "" {
|
||||
rep.Errorf(Manifest, d.Path, d.Front.At["prefix"],
|
||||
"the file declares no topic yet carries the keys of a layer: a document without a topic is the one the suite governs itself by, and it is nobody's layer — the topic key looks lost")
|
||||
}
|
||||
}
|
||||
if len(topicless) > 1 {
|
||||
sort.Strings(topicless)
|
||||
for _, path := range topicless[1:] {
|
||||
rep.Errorf(Manifest, path, 1,
|
||||
"the suite holds more than one document without a topic (%v): only the one the suite governs itself by may lack a topic, so the rest have lost the key",
|
||||
topicless)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkBaseLayers checks that a topic holds no more than one layer without axis
|
||||
// keys. The base layer is the only one of its kind: it reaches every copy, and a
|
||||
// second such layer would mean two base texts in one assembled file.
|
||||
|
||||
Reference in New Issue
Block a user