1 # src/rules: every rule, and its group. A rule is a module under 2 # rules// with `check(src) -> List`, run over each file read 3 # once (src/src.bend); a project rule has `check(ds) -> List` and 4 # sees every file at once, as the digests of rules/digest.bend. The directory 5 # names the group, and codes.bend says it again for the config. 6 # Adding one is adding it here and a row in codes.bend. One rule is not 7 # listed here, `noqa` (rules/style/noqa.bend): it reads the other rules' 8 # graded findings, so the planner runs it after them (lint/plan.bend). Nor 9 # is `setting` (rules/correctness/setting.bend): it reads each bolt.bend that 10 # grades the run, not a source, so the planner runs it between the per-file 11 # and the project rules. 12 # The groups are what a bolt.bend switches at once: correctness (what will 13 # fail or blow up), suspicious (what is probably a slip), style, laws, and 14 # pedantic (advice that is noisy on idiomatic code: off unless a project 15 # asks for it). 16 import Base 17 import ./finding.bend as F 18 import ./codes.bend as Codes 19 import ./src.bend as Src 20 import ./rules/digest.bend as Digest 21 import ./config.bend as Config 22 import ./rules/style/doc.bend as Doc 23 import ./rules/suspicious/unused.bend as Unused 24 import ./rules/correctness/hole.bend as Hole 25 import ./rules/style/space.bend as Space 26 import ./rules/style/wrap.bend as Wrap 27 import ./rules/style/param.bend as Param 28 import ./rules/correctness/pick.bend as Pick 29 import ./rules/laws/law.bend as Law 30 import ./rules/suspicious/strict.bend as Strict 31 import ./rules/suspicious/eager.bend as Eager 32 import ./rules/pedantic/tail.bend as Tail 33 import ./rules/suspicious/concat.bend as Concat 34 import ./rules/suspicious/index.bend as Index 35 import ./rules/suspicious/table.bend as Table 36 import ./rules/suspicious/hoist.bend as Hoist 37 import ./rules/suspicious/ring.bend as Ring 38 import ./rules/suspicious/rewalk.bend as Rewalk 39 import ./rules/suspicious/unit.bend as Unit 40 import ./rules/suspicious/scan.bend as Scan 41 import ./rules/suspicious/thunk.bend as Thunk 42 import ./rules/suspicious/fromrev.bend as Fromrev 43 import ./rules/correctness/put.bend as Put 44 import ./rules/correctness/escape.bend as Escape 45 import ./rules/correctness/strings.bend as Strings 46 import ./rules/correctness/chars.bend as Chars 47 import ./rules/correctness/twice.bend as Twice 48 import ./rules/correctness/arms.bend as Arms 49 import ./rules/correctness/foreign.bend as Foreign 50 import ./rules/suspicious/fuel.bend as Fuel 51 import ./rules/laws/closed.bend as Closed 52 import ./rules/laws/trace.bend as Trace 53 import ./rules/laws/unsafe.bend as Unsafe 54 55 # the file a Src was read from 56 def on.path(ss: Src.Src) -> String: 57 Src.Src{path, _text, _toks, _tree, _bound, _items} = ss 58 path 59 60 # findings put at a path: a per-file rule reports on its own file, so this 61 # keeps what each rule gave and makes it hold by construction 62 def on.at(+path: String, fs: List<&2, F.Finding>) -> List<&2, F.Finding>: 63 match fs: 64 case Nil{}: 65 Nil{} 66 case Con{F.Finding{_path, line, col, len, rule, msg}, rest}: 67 F.Finding{path, line, col, len, rule, msg} <> on.at(path, rest) 68 69 # every rule's findings on a file read once, as each rule gives them 70 def on.rules(+ss: Src.Src) -> List<&2, F.Finding>: 71 List.concat(&2, F.Finding, [ 72 Doc.check(ss), 73 Unused.check(ss), 74 Hole.check(ss), 75 Space.check(ss), 76 Wrap.check(ss), 77 Param.check(ss), 78 Pick.check(ss), 79 Strict.check(ss), 80 Eager.check(ss), 81 Tail.check(ss), 82 Concat.check(ss), 83 Index.check(ss), 84 Table.check(ss), 85 Hoist.check(ss), 86 Ring.check(ss), 87 Rewalk.check(ss), 88 Unit.check(ss), 89 Scan.check(ss), 90 Thunk.check(ss), 91 Fromrev.check(ss), 92 Put.check(ss), 93 Escape.check(ss), 94 Strings.check(ss), 95 Chars.check(ss), 96 Twice.check(ss), 97 Arms.check(ss), 98 Foreign.check(ss), 99 Fuel.check(ss), 100 Closed.check(ss)]) 101 102 # every rule's findings on a file read once, each at that file 103 def on(+ss: Src.Src) -> List<&2, F.Finding>: 104 on.at(on.path(ss), on.rules(ss)) 105 106 # every rule's findings on a file 107 def all(+path: String, +text: String) -> List<&2, F.Finding>: 108 on(Src.of(path, text)) 109 110 # every project rule's findings on the files and SPEC.md's text (None when 111 # there is none): each file is read down to its digest once 112 # (rules/digest.bend), and the rules run over those 113 def project(srcs: List<&2, Src.Src>, whole: Bool, spec: Maybe<&2, String>) -> List<&2, F.Finding>: 114 +ds = Digest.all(srcs) 115 List.concat(&2, F.Finding, [Law.check(ds), Unsafe.check(ds), Trace.check(whole, spec, ds)]) 116 117 # the group a rule is in; an unknown slug grades with correctness 118 def group(+rule: String) -> String: 119 +named = Codes.group(rule) 120 Bool.pick(String, String.is_empty(named), "correctness", named) 121 122 # the findings at the levels a config gives their rules; the ones set off 123 # are dropped 124 def graded(+cfg: Config.Config, fs: List<&2, F.Finding>) -> List<&2, F.Graded>: 125 match fs: 126 case Nil{}: 127 Nil{} 128 case Con{F.Finding{path, line, col, len, +rule, msg}, rest}: 129 +more = graded(cfg, rest) 130 +l = Config.level(cfg, group(rule), rule) 131 Bool.pick(List<&2, F.Graded>, Config.is_off(l), more, 132 F.Graded{l, F.Finding{path, line, col, len, rule, msg}} <> more)