diff --git a/checks/words/main.go b/checks/words/main.go index 3cc58c9..345e86b 100644 --- a/checks/words/main.go +++ b/checks/words/main.go @@ -4,7 +4,8 @@ // own code that is not a test (a tool's description, the notes and errors it answers with) and every // description in its manifest. Comments are not read: an agent never sees them. // -// A word is matched whole and in any case, a space in it matching any run of white space. A vendor word is +// A word is matched whole and in any case, a space in it matching any run of white space, and its plural +// (`s` or `es` on its last part) is matched as the word. A vendor word is // never on the list (the glossary does not retire one for the tools), so a wrapped program's own objects — // an identity provider's users, a media manager's releases — are never findings. // @@ -123,7 +124,9 @@ func pattern(word string) *regexp.Regexp { for i, p := range parts { parts[i] = regexp.QuoteMeta(p) } - return regexp.MustCompile(`(?i)(?:^|[^\w-])(` + strings.Join(parts, `\s+`) + `)(?:$|[^\w-])`) + // The plural is the word too: `s` or `es` on its last part, so "control planes" is found as "control + // plane" is. Anything else joined on is another word. + return regexp.MustCompile(`(?i)(?:^|[^\w-])(` + strings.Join(parts, `\s+`) + `(?:e?s)?)(?:$|[^\w-])`) } func check(words []string, texts []text) []string { diff --git a/checks/words/main_test.go b/checks/words/main_test.go new file mode 100644 index 0000000..949d746 --- /dev/null +++ b/checks/words/main_test.go @@ -0,0 +1,26 @@ +package main + +import "testing" + +// A retired word's plural is the word; anything else joined on is another word. +func TestPatternMatchesTheWordAndItsPlural(t *testing.T) { + cases := []struct { + word, text string + want bool + }{ + {"control plane", "ask the control plane", true}, + {"control plane", "two control planes", true}, + {"control plane", "two control\n\tplanes", true}, + {"substrate", "the substrates", true}, + {"flavor", "flavors of a module", true}, + {"flavor", "flavored", false}, + {"node tools", "node toolsets", false}, + {"mesh-console", "the mesh-consoles", true}, + {"mesh-console", "mesh-console-x", false}, + } + for _, c := range cases { + if got := pattern(c.word).MatchString(c.text); got != c.want { + t.Errorf("pattern(%q) on %q = %v, want %v", c.word, c.text, got, c.want) + } + } +} diff --git a/merge-check.sh b/merge-check.sh index 1a1b713..00d094d 100755 --- a/merge-check.sh +++ b/merge-check.sh @@ -18,10 +18,10 @@ # 3. the words (novox/hq ADR 0244): no word the glossary retired for the tools, as copied in # retired-words, in any text a module's tools can show an agent — every string in its own code that is # not a test, and every description in its manifest. Run over every module, not only the touched ones, -# so a word newly retired is found everywhere it stands. +# so a word newly retired is found everywhere it stands. The check's own test runs first. set -eu -(cd checks/words && go run . ../..) +(cd checks/words && go test -count=1 . && go run . ../..) if [ -n "${MESH_GATE:-}" ]; then checked=$("$MESH_GATE" module check modules/*/module.json) || { printf '%s\n' "$checked"; exit 1; }