rules.odin (6263B)
1 package check 2 3 // The rules are what an agent is handed. A finding cites one by id, and 4 // the id has to lead somewhere: the description of the deterministic check 5 // that measured it. Printing them from the binary is what makes the rules 6 // travel with it. 7 8 import "core:fmt" 9 import "core:strings" 10 11 // Rule describes one deterministic check, for the reader who met its id 12 // in a finding. 13 Rule :: struct { 14 id: string, 15 description: string, 16 } 17 18 // rules are the deterministic checks, in the order the readme lists 19 // them. Each id here is one a finding can carry with job "static". 20 rules := []Rule { 21 { 22 "go-build", 23 "the Go compiler cannot build a package the change touched; must-fix wherever the error lands", 24 }, 25 { 26 "go-vet/<analyzer>", 27 "go vet's finding, on a line the change adds — with review-vet's extra analysers where it is installed; nilness and vet's own set are must-fix, shadow and unusedwrite consider, modernize note", 28 }, 29 { 30 "staticcheck/<code>", 31 "the Go analyser's finding, on a line the change adds; SA is must-fix, S and U consider, the rest note", 32 }, 33 { 34 "odin-check", 35 "odin check -vet -strict-style cannot check a package the change touched: a type error is must-fix wherever it lands, a vet failure consider, a style failure note", 36 }, 37 { 38 "tsc/<code>", 39 "the TypeScript compiler's error under the project's own tsconfig; must-fix wherever it lands; working tree only", 40 }, 41 { 42 "ruff/<code>", 43 "ruff's finding on a line the change adds; pyflakes codes consider, style codes note", 44 }, 45 { 46 "mypy/<code>", 47 "mypy's type error on a line the change adds, missing imports ignored; working tree only", 48 }, 49 { 50 "cargo/<code>", 51 "cargo check's — or clippy's — diagnostic: an error is must-fix wherever it lands, a warning consider on a line the change adds", 52 }, 53 { 54 "semgrep/<rule>", 55 "a semgrep match on a line the change adds, under the repository's own .semgrep.yml or the registry's p/default pack; the rule's severity is kept: ERROR must-fix, WARNING consider, INFO note", 56 }, 57 { 58 "message-low-entropy", 59 "the commit message's Shannon entropy is under 3.2 bits per byte: a phrase repeated rather than a description", 60 }, 61 { 62 "message-boilerplate", 63 "the commit message keeps under 20% of its length after zlib: a block of text pasted or repeated whole", 64 }, 65 { 66 "message-common-words", 67 "the commit message is made only of the repository's commonest commit words and names nothing the change touches", 68 }, 69 { 70 "message-frustration", 71 "the commit message is an exclamation — oops, whoops, damn — where a description should be", 72 }, 73 { 74 "message-not-imperative", 75 "the subject's first word after its package prefix is past tense, a gerund, or the author", 76 }, 77 { 78 "message-no-body", 79 "a change over 50 lines carries no body, and the diff does not only move text around", 80 }, 81 {"message-long-body", "the body is over 150 words, listing what the diff already shows"}, 82 { 83 "history-coupled-file", 84 "history ties a changed file to a partner the change does not touch: Jaccard at least 0.7 over at least 5 shared commits", 85 }, 86 { 87 "message-names-unknown", 88 "the commit message names an identifier, path or call that is in neither the diff nor the repository at the end of the change", 89 }, 90 { 91 "formatting-mixed-in", 92 "half or more of the change's lines change only whitespace, and at least 10 change something else: a reformatting with logic in it", 93 }, 94 { 95 "suppression-added", 96 "the change adds its own review:ignore dismissal, before the readers have run", 97 }, 98 {"test-deleted", "the change deletes a test function that no added test renames"}, 99 {"no-stutter", "an exported name's first word is its package: ico.IcoEntry says ico twice"}, 100 { 101 "no-shadow", 102 "a new name is a predeclared identifier, a standard library package, or a runtime global", 103 }, 104 {"abbreviation", "a new name carries an invented abbreviation: cfg, mgr, hdlr, svc, btn, cnt"}, 105 { 106 "test-no-assertion", 107 "an added or altered test has no call that could fail it; it can only fail by crashing", 108 }, 109 { 110 "assertion-always-true", 111 "an assertion in an added or altered test holds whatever the code does: a literal true, two literals, or a value against itself", 112 }, 113 { 114 "duplicate-body", 115 "a new function's body already exists, token for token (must-fix) or in shape with every name changed (consider)", 116 }, 117 { 118 "function-too-long", 119 "a new function is over 150 lines; 95% of measured functions fit in 109", 120 }, 121 { 122 "nesting-too-deep", 123 "a new function nests blocks more than 5 deep; 99% of measured functions stay within 6", 124 }, 125 { 126 "comment-restates-code", 127 "every content word of an added comment is in the line of code below it", 128 }, 129 { 130 "todo-without-reference", 131 "an added TODO, FIXME, XXX or HACK names no issue, ticket, link or person", 132 }, 133 { 134 "commented-out-code", 135 "an added comment run holds code: two statement-shaped lines, or one beyond doubt", 136 }, 137 { 138 "debug-leftover", 139 "an added line is a debugger statement, a breakpoint, dbg!, spew.Dump, console.log, or a DEBUG print", 140 }, 141 { 142 "error-swallowed", 143 "an added line drops an error: _ = err, an empty catch, an except that passes", 144 }, 145 { 146 "new-symbol-unreferenced", 147 "nothing in the repository refers to a new declaration but its own line", 148 }, 149 { 150 "code-without-tests", 151 "the change adds 50 or more lines of code, touches no test, and the repository keeps tests", 152 }, 153 } 154 155 // catalogue is every rule, as review rules prints them. 156 catalogue :: proc(allocator := context.allocator) -> string { 157 b := strings.builder_make(allocator) 158 strings.write_string(&b, "# Deterministic checks\n\n") 159 for r in rules { 160 fmt.sbprintf(&b, "- `%s` — %s\n", r.id, r.description) 161 } 162 return strings.to_string(b) 163 } 164 165 // describe is one rule's description, by its id or by the family a 166 // tool's rules share: go-vet/anything is go-vet/<analyzer>. 167 describe :: proc(id: string) -> (description: string, ok: bool) { 168 family := id 169 if i := strings.index_byte(id, '/'); i >= 0 { 170 family = id[:i] 171 } 172 for r in rules { 173 if r.id == id || 174 (strings.contains(id, "/") && 175 strings.has_prefix( 176 r.id, 177 strings.concatenate({family, "/"}, context.temp_allocator), 178 )) { 179 return r.description, true 180 } 181 } 182 return "", false 183 }