src/rules/style/doc.bend

bolt@1.17.0.0 · 92 lines · 0 definitions

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