diff --git a/commit_check/imperatives.py b/commit_check/imperatives.py index 3704ee79..f3af9e08 100644 --- a/commit_check/imperatives.py +++ b/commit_check/imperatives.py @@ -1,20 +1,38 @@ +# Imperative forms of verbs, seeded from # https://github.com/crate-ci/imperative/blob/master/assets/imperatives.txt -# Imperative forms of verbs +# and extended since. # -# This file contains the imperative form of frequently encountered -# docstring verbs. Some of these may be more commonly encountered as -# nouns, but blacklisting them for this may cause false positives. +# Some of these are more commonly encountered as nouns, but leaving them out +# rejects a subject that is written correctly, which is the worse failure: the +# contributor has to reword something that was never wrong, and the only way +# they learn which words are acceptable is by trial and error. +# +# For the same reason both spellings of every -ize/-ise verb are listed. A +# project writing British English is not making a mistake. +# +# Additions are welcome and cheap. The list can only ever approximate "is this +# an English imperative verb", so treat a rejected-but-correct subject as a bug +# in this file rather than as something the author should work around. IMPERATIVES = { "abort", "absorb", + "abstract", "accept", "access", + "accommodate", + "acknowledge", + "activate", + "adapt", "add", + "address", "adjust", + "advertise", "aggregate", "align", "allow", + "amend", + "annotate", "append", "apply", "archive", @@ -22,38 +40,53 @@ "assign", "attach", "attempt", + "audit", "authenticate", + "authorise", "authorize", "auto", + "automate", + "avoid", + "await", "backport", + "balance", "batch", + "be", "bind", "block", "break", "broadcast", + "broaden", "build", "bump", + "bypass", "cache", "calculate", "call", "cancel", "capture", + "centralise", + "centralize", "change", "check", + "clarify", "clean", "clear", "close", + "collapse", "collect", "combine", "comment", "commit", "compare", + "complete", "compose", "compress", "compute", "configure", "confirm", "connect", + "consolidate", "construct", "consume", "control", @@ -62,36 +95,49 @@ "correct", "count", "create", + "customise", "customize", "debug", "declare", "decode", "decompress", "decorate", - "decrypt", "decrease", + "decrypt", + "deduplicate", + "defer", "define", "delegate", "delete", + "demonstrate", "deprecate", "derive", "describe", + "deselect", "detach", "detect", "determine", + "diagnose", + "die", + "differentiate", "disable", + "disallow", "discard", "disconnect", "dispatch", "display", "dispose", + "distinguish", "distribute", + "do", "document", - "download", "downgrade", + "download", "drop", - "duplicate", "dump", + "duplicate", + "elaborate", + "eliminate", "embed", "emit", "empty", @@ -114,24 +160,31 @@ "exit", "expand", "expect", + "expire", + "explain", "export", "expose", "extend", "extract", + "factor", "feed", "fetch", "fill", "filter", + "finalise", "finalize", "find", "fire", "fix", "flag", + "flatten", "flush", "fold", + "forbid", "force", "format", "forward", + "free", "freeze", "generate", "get", @@ -139,15 +192,21 @@ "go", "grant", "group", + "guard", "halt", "handle", + "harden", "hash", "help", "hide", "highlight", + "hoist", "hold", + "honor", + "honour", "identify", "ignore", + "illustrate", "implement", "import", "improve", @@ -162,9 +221,11 @@ "initialize", "initiate", "inject", + "inline", "input", "insert", "instantiate", + "instrument", "integrate", "intercept", "introduce", @@ -175,6 +236,8 @@ "join", "keep", "launch", + "let", + "lift", "link", "list", "listen", @@ -192,19 +255,32 @@ "marshal", "mask", "match", + "maximise", "maximize", "measure", + "memoise", + "memoize", + "mention", "merge", "migrate", + "minimise", "minimize", "mirror", "mock", + "modernise", + "modernize", "modify", + "modularise", + "modularize", "monitor", "mount", "move", "name", + "narrow", "navigate", + "neutralise", + "neutralize", + "normalise", "normalize", "note", "notify", @@ -212,45 +288,69 @@ "offset", "omit", "open", + "optimise", "optimize", + "orchestrate", + "organise", + "organize", "outline", "output", - "organize", - "orchestrate", + "overhaul", "override", "overwrite", "package", "pad", + "paginate", + "parameterise", "parameterize", "parse", "partial", "pass", "pause", "perform", + "permit", "persist", "pick", + "pin", "ping", "pipe", "plot", + "plug", + "pluralise", + "pluralize", "poll", "polyfill", "populate", + "port", "post", + "postpone", + "precompute", + "prefer", "prefix", "prepare", + "prepend", + "preserve", "prevent", "print", + "prioritise", + "prioritize", "process", "produce", - "prune", + "propagate", "provide", + "proxy", + "prune", "publish", "pull", "purge", "push", "put", + "qualify", + "quarantine", "query", + "quote", "raise", + "randomise", "randomize", "rank", "read", @@ -259,11 +359,13 @@ "rebuild", "recall", "receive", + "reclaim", "recommend", "reconcile", "reconnect", "record", "recover", + "redact", "redesign", "redirect", "reduce", @@ -272,35 +374,49 @@ "reformat", "refresh", "register", + "reinstate", "reject", "relate", + "relax", "release", "reload", "relocate", + "remap", "remove", "rename", "render", "reorder", + "reorganise", "reorganize", "repeat", + "rephrase", "replace", "replay", "reply", "report", "represent", + "repurpose", "request", "require", + "rescue", "reset", "resolve", + "respect", + "restore", "restrict", "resume", + "retarget", + "rethrow", + "retire", "retrieve", "retry", "return", "reuse", + "revalidate", "revamp", "revert", "revoke", + "rewire", "rework", "rewrite", "roll", @@ -309,10 +425,13 @@ "route", "run", "sample", + "sanitise", "sanitize", "save", "scan", "schedule", + "scope", + "seal", "search", "select", "send", @@ -321,9 +440,13 @@ "serialize", "serve", "set", + "settle", "setup", + "shard", + "shorten", "show", "shuffle", + "silence", "simplify", "simulate", "skip", @@ -332,42 +455,56 @@ "source", "spawn", "specify", + "speed", + "spell", "split", "spread", "squash", + "stabilise", + "stabilize", + "standardise", "standardize", "start", "step", "stop", "store", + "streamline", "strip", + "stub", + "subclass", "submit", "subscribe", "substitute", "suggest", "sum", - "suppress", + "supersede", "support", + "suppress", "suspend", "swap", "switch", "sync", "synchronise", "synchronize", - "terminate", + "tag", "take", + "teach", "tear", + "terminate", "test", - "throw", "throttle", + "throw", + "tighten", "time", "toggle", + "tolerate", "trace", "track", "transfer", "transform", "translate", "transmit", + "treat", "trigger", "trim", "truncate", @@ -382,10 +519,12 @@ "unmarshal", "unpack", "unsubscribe", + "untangle", "unwind", "unwrap", "update", "upgrade", + "uphold", "upload", "use", "validate", @@ -396,8 +535,11 @@ "walk", "warm", "warn", + "weaken", + "widen", "wire", "withdraw", + "work", "wrap", "write", "yield", diff --git a/tests/engine_test.py b/tests/engine_test.py index f5b514e3..3a1e7534 100644 --- a/tests/engine_test.py +++ b/tests/engine_test.py @@ -1349,6 +1349,58 @@ def test_validate_with_common_imperative_subjects(self, subject): assert result == ValidationResult.PASS + @pytest.mark.parametrize( + "subject", + [ + # Rejected before the list was extended, every one of them written + # in correct imperative mood. + "feat: settle the report format", + "fix: avoid a second lookup", + "docs: clarify the default value", + "refactor: factor out the helper", + "chore: teach the parser about tabs", + "fix: free the buffer on the error path", + "refactor: inline the wrapper", + "fix: restore the previous behaviour", + "chore: retire the legacy flag", + # British spelling is not a mistake. The last pair is the case the + # file used to get wrong most often: the -ize form was listed and + # the -ise one was not, so only half of a spelling pair worked. + "refactor: normalise the path separators", + "chore: prioritise the queue", + "feat: customise the template", + "feat: customize the template", + ], + ) + def test_correct_imperative_subjects_are_not_rejected(self, subject): + """Words a contributor would have had to reword around must pass. + + A whitelist can only approximate "is this an imperative verb", and the + cost of a gap falls on someone who wrote the subject correctly. + """ + rule = ValidationRule(check="subject_imperative") + validator = SubjectImperativeValidator(rule) + context = ValidationContext(stdin_text=subject) + + assert validator.validate(context) == ValidationResult.PASS + + @pytest.mark.parametrize( + "subject", + [ + "fix: updated the parser", + "feat: adding a new flag", + "fix: fixes the crash", + "chore: removed the dead code", + ], + ) + def test_wrong_verb_forms_still_fail(self, subject): + """Extending the list must not weaken what the rule is there to catch.""" + rule = ValidationRule(check="subject_imperative") + validator = SubjectImperativeValidator(rule) + context = ValidationContext(stdin_text=subject) + + assert validator.validate(context) == ValidationResult.FAIL + @pytest.mark.benchmark def test_validate_with_imperative_subject(self): """Test validation with proper imperative subject."""