1 # The contract between bend-lint and a rule written in Bend. A rule file 2 # imports this file and defines: 3 # 4 # def id() -> String: # namespace/name, the code of its findings 5 # def facts() -> Lint.Want: # Lint.NoFacts{}, or the facts it needs 6 # def run(input: Lint.Input) -> IO(List<&2, Lint.Diag>): 7 # def main() -> IO(Unit): 8 # Lint.serve(run) 9 # 10 # A rule with facts reads them one at a time, with fold_facts or 11 # next_fact. Offsets count characters (code points) from the start of a 12 # file. The rule's options come from bend-lint.json; read them with 13 # option_number, option_flag and option_text, each with a default. 14 import Base 15 16 # A checked term, a type, and a node of a checked body: indexes into 17 # bend-lint's tables for one run. Only the effects below read them. 18 type Fact is Data: 19 Fact{id: U32} 20 21 type Term is Data: 22 Term{id: U32} 23 24 type Node is Data: 25 Node{id: U32} 26 27 type Source is Data: 28 Source{path: String, text: String, root: Bool} 29 30 # An option's value: a whole number (a U32), a boolean, or text. 31 type Value is Data: 32 OptionNum{value: U32} 33 OptionFlag{value: Bool} 34 OptionText{value: String} 35 36 type Option is Data: 37 Option{key: String, value: Value} 38 39 type Input is Data: 40 Input{sources: List<&2, Source>, options: List<&2, Option>} 41 42 # File: facts of the linted file. Program: of its imports too, never Base. 43 type Scope is Data: 44 File{} 45 Program{} 46 47 # The facts a rule needs. A fact must match each list; an empty list 48 # matches all. kinds: the term's kind, annotations stripped (Var, Ref, App, 49 # ...). defs: the def whose body holds the term (a template's instances 50 # count as the template). names: the name a Var or Ref points to. 51 type Want is Data: 52 NoFacts{} 53 Want{scope: Scope, kinds: List<&2, String>, defs: List<&2, String>, names: List<&2, String>} 54 55 type Span is Data: 56 Span{path: String, beg: U32, end: U32} 57 58 type Severity is Data: 59 Error{} 60 Warning{} 61 Information{} 62 Hint{} 63 64 # Safe keeps behavior; Suggested may change it; Dangerous may break code. 65 type Applicability is Data: 66 Safe{} 67 Suggested{} 68 Dangerous{} 69 70 type Edit is Data: 71 Edit{span: Span, text: String} 72 73 type Fix is Data: 74 Fix{title: String, applicability: Applicability, edits: List<&2, Edit>} 75 76 type Diag is Data: 77 Diag{severity: Severity, message: String, span: Maybe<&2, Span>, fixes: List<&2, Fix>} 78 79 # How many times a term may be used. 80 type Quantity is Data: 81 Erased{} 82 Once{} 83 Many{} 84 85 type Use is Data: 86 Use{name: String, quantity: Quantity} 87 88 # A fact as bend-lint sees it: the def it is in, whether that def is a 89 # template instance, the kind of its term once an annotation is stripped 90 # (Var, App, ...), the name a Var or Ref points to (else ""), how many 91 # times the term is demanded, and the spans of the checked term and of the 92 # stripped one. 93 type View is Data: 94 View{owner: String, inst: Bool, kind: String, name: String, quantity: Quantity, span: Maybe<&2, Span>, inner: Maybe<&2, Span>} 95 96 # A node: its kind, annotations kept (Ann, Var, App, ...), the name a Var 97 # or Ref points to (else ""), its span, and its children, in bend's order. 98 type Shape is Data: 99 Shape{kind: String, name: String, span: Maybe<&2, Span>, children: List<&2, Node>} 100 101 def input() -> IO(Input): 102 import "./effects.js" 103 104 def report(diags: List<&2, Diag>) -> IO(Unit): 105 import "./effects.js" 106 107 # The next fact the rule asked for, or None{} when none are left. 108 def next_fact() -> IO(Maybe<&2, Fact>): 109 import "./effects.js" 110 111 # One step of fold_facts: stop at None{}, else run step and go on. 112 def fold_next(~A: Type, ~step: A -> @+f: Fact -> IO(A), m: Maybe<&2, Fact>, acc: A, again: A -> IO(A)) -> IO(A): 113 match m: 114 case None{}: 115 IO.pure(A, acc) 116 case Some{f}: 117 IO.bind(A, A, step(acc, f), again) 118 119 # Runs step on each fact the rule asked for, in order, from acc; step is 120 # a def, given as ~name, that takes its fact as +f. Facts come one at a 121 # time, so a big file does not build one big list. 122 @unsafe 123 def fold_facts(~A: Type, ~step: A -> @+f: Fact -> IO(A), acc: A) -> IO(A): 124 do IO: 125 m : Maybe<&2, Fact> <- next_fact() 126 fold_next(~A, ~step, m, acc, a => fold_facts(~A, ~step, a)) 127 128 def view(fact: Fact) -> IO(View): 129 import "./effects.js" 130 131 # The type the fact's term was checked against, or inferred. 132 def type_of(fact: Fact) -> IO(Term): 133 import "./effects.js" 134 135 # The type of the binder a Var fact refers to. 136 def binder(fact: Fact) -> IO(Maybe<&2, Term>): 137 import "./effects.js" 138 139 # Whether two types are equal, as the checker compares them in the fact's 140 # scope. 141 def same(fact: Fact, a: Term, b: Term) -> IO(Bool): 142 import "./effects.js" 143 144 def show(fact: Fact, t: Term) -> IO(String): 145 import "./effects.js" 146 147 # The normal form of a type, in the fact's scope. 148 def normal(fact: Fact, t: Term) -> IO(Term): 149 import "./effects.js" 150 151 # The variables the fact's term uses, each with how many times. 152 def uses(fact: Fact) -> IO(List<&2, Use>): 153 import "./effects.js" 154 155 # The text under a span, in the file on disk. 156 def text(span: Span) -> IO(String): 157 import "./effects.js" 158 159 # A def's checked body, if it has one. 160 def body(name: String) -> IO(Maybe<&2, Node>): 161 import "./effects.js" 162 163 # The node a fact is about. 164 def node(fact: Fact) -> IO(Node): 165 import "./effects.js" 166 167 def shape(n: Node) -> IO(Shape): 168 import "./effects.js" 169 170 # The node's fact, if the rule asked for it. 171 def fact(n: Node) -> IO(Maybe<&2, Fact>): 172 import "./effects.js" 173 174 def pick(hit: Bool, v: Value, later: Maybe<&2, Value>) -> Maybe<&2, Value>: 175 match hit: 176 case True{}: 177 Some{v} 178 case False{}: 179 later 180 181 # The value of an option, if it is given. 182 def find(opts: List<&2, Option>, +key: String) -> Maybe<&2, Value>: 183 match opts: 184 case Nil{}: 185 None{} 186 case Con{Option{k, v}, rest}: 187 pick(String.eq(k, key), v, find(rest, key)) 188 189 def option_number_or(m: Maybe<&2, Value>, default: U32) -> U32: 190 match m: 191 case Some{OptionNum{n}}: 192 n 193 case Some{OptionFlag{b}}: 194 default 195 case Some{OptionText{t}}: 196 default 197 case None{}: 198 default 199 200 # An option's value, or `default` when it is not given or of another type. 201 def option_number(opts: List<&2, Option>, key: String, default: U32) -> U32: 202 option_number_or(find(opts, key), default) 203 204 def option_flag_or(m: Maybe<&2, Value>, default: Bool) -> Bool: 205 match m: 206 case Some{OptionFlag{b}}: 207 b 208 case Some{OptionNum{n}}: 209 default 210 case Some{OptionText{t}}: 211 default 212 case None{}: 213 default 214 215 def option_flag(opts: List<&2, Option>, key: String, default: Bool) -> Bool: 216 option_flag_or(find(opts, key), default) 217 218 def option_text_or(m: Maybe<&2, Value>, default: String) -> String: 219 match m: 220 case Some{OptionText{t}}: 221 t 222 case Some{OptionNum{n}}: 223 default 224 case Some{OptionFlag{b}}: 225 default 226 case None{}: 227 default 228 229 def option_text(opts: List<&2, Option>, key: String, default: String) -> String: 230 option_text_or(find(opts, key), default) 231 232 def serve(run: Input -> IO(List<&2, Diag>)) -> IO(Unit): 233 IO.bind(Input, Unit, input(), i => IO.bind(List<&2, Diag>, Unit, run(i), report))