From 789f40f84c3d6c0a784578e05a1a7c7398c4d0ba Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 10:56:56 -0700 Subject: [PATCH 1/3] Say What the Vocabulary Check's is_word Accepts The docstring said letters alone, while the pattern also joins letter runs with single spaces, hyphens, or apostrophes, so "had had" and "sentence-length" are words. Co-Authored-By: Claude Opus 5.5 --- tests/test_prose_lint.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_prose_lint.py b/tests/test_prose_lint.py index e67b5796..052d89e2 100755 --- a/tests/test_prose_lint.py +++ b/tests/test_prose_lint.py @@ -7493,7 +7493,11 @@ def test_the_gate_reads_the_resolved_base(self) -> None: def is_word(text: str) -> bool: - """Whether `text` is letters alone, so flags, paths, and environment variable names are not.""" + """Whether `text` is letter runs joined by single spaces, hyphens, or apostrophes. + + So "had had" and "sentence-length" are words, while flags, paths, and environment variable names + are not. + """ return re.fullmatch(r"[A-Za-z]+(?:[ '-][A-Za-z]+)*", text) is not None From b1f32b60397487767a20b548540eb9f48fc4b548 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 10:59:55 -0700 Subject: [PATCH 2/3] Report a Word Tuple Keying a Mapping in the Vocabulary Check A key holding words beside a value holding none was marked nested and so never reported, since the mapping does not count it. Only a key the mapping counts is nested now. The is_word docstring names which paths and environment variable names are not words, since an all-letter name such as HOME is one. Co-Authored-By: Claude Opus 5.5 --- tests/test_prose_lint.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_prose_lint.py b/tests/test_prose_lint.py index 052d89e2..84aa2527 100755 --- a/tests/test_prose_lint.py +++ b/tests/test_prose_lint.py @@ -7495,8 +7495,8 @@ def test_the_gate_reads_the_resolved_base(self) -> None: def is_word(text: str) -> bool: """Whether `text` is letter runs joined by single spaces, hyphens, or apostrophes. - So "had had" and "sentence-length" are words, while flags, paths, and environment variable names - are not. + So "had had" and "sentence-length" are words, while flags, paths with a separator, and + environment variable names with an underscore are not. """ return re.fullmatch(r"[A-Za-z]+(?:[ '-][A-Za-z]+)*", text) is not None @@ -7540,7 +7540,11 @@ def vocabulary_in(source: str, data: dict[str, dict[str, Any]]) -> list[str]: found.append(f"{node.lineno}: {node.value!r} is a data file entry") if not isinstance(node, COLLECTIONS): continue - children = node.elts if not isinstance(node, ast.Dict) else [*node.keys, *node.values] + if isinstance(node, ast.Dict): + counted = [key for key, value in zip(node.keys, node.values) if words_in(value)] + children: list[ast.expr | None] = [*counted, *node.values] + else: + children = list(node.elts) nested.update(id(child) for child in children if isinstance(child, COLLECTIONS)) words = words_in(node) if id(node) not in nested and words > VOCABULARY_CAP: @@ -7589,6 +7593,7 @@ def test_nested_word_lists_count_once_at_the_outermost(self) -> None: ('PAIRS = (("grey", "gray"), ("kerb", "curb"), ("tyre", "tire"))\n', 6), ('GROUPS = {"a": ["w", "x", "y"], "b": ["z", "v", "u"]}\n', 8), ('INNER = [["a", "b", "c", "d", "e", "f"]]\n', 6), + ('KEYED = {("aa", "bb", "cc", "dd", "ee", "ff"): len}\n', 6), ): with self.subTest(source=source): found = vocabulary_in(source, self.DATA) From 0999fc81b3c75de84bb05a7ea8592b4c442bd244 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 11:01:30 -0700 Subject: [PATCH 3/3] Prove a Counted Key Reports Once in the Vocabulary Check Adds a case whose word-tuple key sits beside a value holding words, so dropping the key from the nested set would report it twice. The docstring now says the report lands on the outermost literal that counts it, and the underscore test's name says what it covers. Co-Authored-By: Claude Opus 5.5 --- tests/test_prose_lint.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_prose_lint.py b/tests/test_prose_lint.py index 84aa2527..8b4f8986 100755 --- a/tests/test_prose_lint.py +++ b/tests/test_prose_lint.py @@ -7530,7 +7530,7 @@ def vocabulary_in(source: str, data: dict[str, dict[str, Any]]) -> list[str]: """Where `source` holds spellings or exemptions that belong in `data`, the gate's data file. A literal of more than `VOCABULARY_CAP` words reads as a vocabulary rather than as logic. It is - reported once, at the outermost literal holding it. + reported once, at the outermost literal that counts it. """ entries = data_entries(data) nested: set[int] = set() @@ -7594,6 +7594,7 @@ def test_nested_word_lists_count_once_at_the_outermost(self) -> None: ('GROUPS = {"a": ["w", "x", "y"], "b": ["z", "v", "u"]}\n', 8), ('INNER = [["a", "b", "c", "d", "e", "f"]]\n', 6), ('KEYED = {("aa", "bb", "cc", "dd", "ee", "ff"): len}\n', 6), + ('COUNTED = {("aa", "bb", "cc", "dd", "ee", "ff"): ["x"]}\n', 7), ): with self.subTest(source=source): found = vocabulary_in(source, self.DATA) @@ -7605,7 +7606,7 @@ def test_a_dispatch_table_and_punctuation_are_logic(self) -> None: source = f"TABLE = {{{names}}}\nMARKS = ('.', '!', '?', ':', ';', ',', '|')\n" self.assertEqual([], vocabulary_in(source, self.DATA)) - def test_flags_and_environment_names_are_not_words(self) -> None: + def test_flags_and_environment_names_with_an_underscore_are_not_words(self) -> None: source = ( 'ARGS = ("--unified=0", "--no-color", "--no-ext-diff", "-z", "--name-only", "--cached")\n' 'GIT = frozenset({"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_PREFIX", '