prose.odin (2271B)
1 package check 2 3 // A comment whose words are the code's own words says nothing the code 4 // does not. It is never a claim, so the claims job should not be paying to 5 // read it, and the habit of narrating each line is the habit this 6 // catches. 7 8 import "core:fmt" 9 import "core:strings" 10 11 import "../change" 12 import "../finding" 13 14 // min_restated_words is the fewest content words a comment needs before 15 // it can be said to restate anything: one word is a label. 16 min_restated_words :: 2 17 18 // restates is whether a comment's content words all appear in the code 19 // it sits above. A directive, a dismissal, a task marker and a link are 20 // not prose about the code and are never said to restate it. 21 restates :: proc(comment: change.Located) -> bool { 22 if comment.below == "" || directive(comment.text) { 23 return false 24 } 25 words := content_words(comment.text, context.temp_allocator) 26 if len(words) < min_restated_words { 27 return false 28 } 29 code := content_words(subject_of(comment.below), context.temp_allocator) 30 for w in words { 31 if !code[w] { 32 return false 33 } 34 } 35 return true 36 } 37 38 // directive is whether a comment is one the tools read rather than a 39 // person: a compiler or linter instruction, a dismissal, a task marker, a 40 // link. 41 directive :: proc(text: string) -> bool { 42 trimmed := strings.trim_space(text) 43 for prefix in ([]string{"go:", "nolint", "eslint", "@ts-", "prettier", "review:ignore", "#!", "+build", "lint:"}) { 44 if strings.has_prefix(trimmed, prefix) { 45 return true 46 } 47 } 48 for marker in ([]string{"TODO", "FIXME", "XXX", "HACK", "http://", "https://"}) { 49 if strings.contains(trimmed, marker) { 50 return true 51 } 52 } 53 return false 54 } 55 56 // check_restating reports the comments the change adds whose every word 57 // the code below already says. 58 check_restating :: proc(s: Scope, out: ^[dynamic]finding.Finding) { 59 for comment in s.c.comments { 60 if !restates(comment) { 61 continue 62 } 63 append( 64 out, 65 static( 66 "comment-restates-code", 67 .Note, 68 fmt.aprintf( 69 "the comment %q says only what the line below it says; a comment that narrates the code is read twice and informs once", 70 first_line(comment.text, context.temp_allocator), 71 ), 72 "say why, or say nothing", 73 file = comment.file, 74 line = comment.line, 75 ), 76 ) 77 } 78 }