review

review patchsets using your default editor
Log | Files | Refs

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 }