1 # rule doc: every top-level def, type and law has a comment block right above 2 # it: one or more column-0 `#` lines with no blank line between them and the 3 # item. A block of bare `#` lines counts, though its text is empty; a line 4 # that starts inside a multi-line string literal is text, never a comment 5 # line. Helpers 6 # (dotted names like `show.go`) ride on their parent's doc, `main` needs none, 7 # PROOF.bend fills laws that LAWS.bend already documents, and a test (under 8 # tests/) is documented by its header and its check names. 9 import Base 10 import ../../src.bend as Src 11 import ../../finding.bend as F 12 import ../../syntax/outline.bend as Outline 13 import ../../lazy/lazy.bend as Lazy 14 import ../../paths.bend as Paths 15 import ../../syntax/lex.bend as Lex 16 17 # does an item of this kind need a comment? 18 def needs(kk: Outline.ItemKind) -> Bool: 19 match kk: 20 case Outline.IDef{}: 21 True{} 22 case Outline.IType{}: 23 True{} 24 case Outline.ILaw{}: 25 True{} 26 case other: 27 False{} 28 29 # `main`, and helpers named `x.go` 30 def exempt(+name: String) -> Bool: 31 Bool.or(String.eq(name, "main"), String.contains(name, ".")) 32 33 # an item's kind, for the message 34 def what(kk: Outline.ItemKind) -> String: 35 match kk: 36 case Outline.IType{}: 37 "Type" 38 case Outline.ILaw{}: 39 "Law" 40 case other: 41 "Def" 42 43 # is line nn of the lines a comment line (its first char `#`)? 44 def check.hash(lines: List<&2, String>, +nn: U32) -> Bool: 45 match lines: 46 case Nil{}: 47 False{} 48 case Con{hh, tt}: 49 Lazy.stop(Bool, U32.is_eq(nn, 0), String.starts_with(hh, "#"), _u => check.hash(tt, U32.sub(nn, 1))) 50 51 # is there a comment line right above line nn? A block of bare `#` lines 52 # counts, though its doc text is empty. An item's comment block always ends 53 # on the line right above it, so this test alone decides: the doc text, what 54 # the comments say, is never read 55 def check.above(+lines: List<&2, String>, +nn: U32) -> Bool: 56 Lazy.and_then(Bool.not(U32.is_eq(nn, 0)), _u => check.hash(lines, U32.sub(nn, 1))) 57 58 def check.go(items: List<&2, Outline.Item>, +path: String, +lines: List<&2, String>) -> List<&2, F.Finding>: 59 match items: 60 case Nil{}: 61 Nil{} 62 case Con{Outline.Item{+k, +name, +line, _sig, _doc, _p}, rest}: 63 +more = check.go(rest, path, lines) 64 Bool.pick(List<&2, F.Finding>, 65 Lazy.and_then(Bool.and(needs(k), Bool.not(exempt(name))), _u => Bool.not(check.above(lines, line))), 66 F.Finding{path, line, 0, 0, "doc", what(k) ++ " " ++ name ++ " has no comment above it."} <> more, more) 67 68 def lines.go(ls: List<&2, String>, +ins: List<&2, Bool>) -> List<&2, String>: 69 match ls: 70 case Nil{}: 71 Nil{} 72 case Con{hh, tt}: 73 Bool.pick(String, Outline.inside.hit(ins), "", hh) <> lines.go(tt, Outline.inside.rest(ins)) 74 75 # the lines of a source, each one that starts inside a string literal read 76 # as empty: it is text, never a comment line 77 def lines(text: String, toks: List<&2, Lex.Tok>) -> List<&2, String>: 78 lines.go(String.lines(text), Outline.inside(toks)) 79 80 # the rule 81 def check(ss: Src.Src) -> List<&2, F.Finding>: 82 Src.Src{+path, text, toks, tree, bound, items} = ss 83 Lazy.stop(List<&2, F.Finding>, Bool.or(Paths.is_proof(path), Paths.is_test(path)), 84 [], _u => check.go(items, path, lines(text, toks)))