vendor/lint.bend

bend-lint-pack@0.1.1.0 · 233 lines · 45 definitions

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.
14import 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.
18type Fact is Data:
19  Fact{id: U32}
20
21type Term is Data:
22  Term{id: U32}
23
24type Node is Data:
25  Node{id: U32}
26
27type 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.
31type Value is Data:
32  OptionNum{value: U32}
33  OptionFlag{value: Bool}
34  OptionText{value: String}
35
36type Option is Data:
37  Option{key: String, value: Value}
38
39type 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.
43type 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.
51type Want is Data:
52  NoFacts{}
53  Want{scope: Scope, kinds: List<&2, String>, defs: List<&2, String>, names: List<&2, String>}
54
55type Span is Data:
56  Span{path: String, beg: U32, end: U32}
57
58type Severity is Data:
59  Error{}
60  Warning{}
61  Information{}
62  Hint{}
63
64# Safe keeps behavior; Suggested may change it; Dangerous may break code.
65type Applicability is Data:
66  Safe{}
67  Suggested{}
68  Dangerous{}
69
70type Edit is Data:
71  Edit{span: Span, text: String}
72
73type Fix is Data:
74  Fix{title: String, applicability: Applicability, edits: List<&2, Edit>}
75
76type 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.
80type Quantity is Data:
81  Erased{}
82  Once{}
83  Many{}
84
85type 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.
93type 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.
98type Shape is Data:
99  Shape{kind: String, name: String, span: Maybe<&2, Span>, children: List<&2, Node>}
100
101def input() -> IO(Input):
102  import "./effects.js"
103
104def 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.
108def 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.
112def 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
123def fold_facts(~A: Type, ~step: A -> @+f: Fact -> IO(A), acc: A) -> IO(A):
124  do IO<A>:
125    m : Maybe<&2, Fact> <- next_fact()
126    fold_next(~A, ~step, m, acc, a => fold_facts(~A, ~step, a))
127
128def view(fact: Fact) -> IO(View):
129  import "./effects.js"
130
131# The type the fact's term was checked against, or inferred.
132def type_of(fact: Fact) -> IO(Term):
133  import "./effects.js"
134
135# The type of the binder a Var fact refers to.
136def 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.
141def same(fact: Fact, a: Term, b: Term) -> IO(Bool):
142  import "./effects.js"
143
144def show(fact: Fact, t: Term) -> IO(String):
145  import "./effects.js"
146
147# The normal form of a type, in the fact's scope.
148def 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.
152def uses(fact: Fact) -> IO(List<&2, Use>):
153  import "./effects.js"
154
155# The text under a span, in the file on disk.
156def text(span: Span) -> IO(String):
157  import "./effects.js"
158
159# A def's checked body, if it has one.
160def body(name: String) -> IO(Maybe<&2, Node>):
161  import "./effects.js"
162
163# The node a fact is about.
164def node(fact: Fact) -> IO(Node):
165  import "./effects.js"
166
167def shape(n: Node) -> IO(Shape):
168  import "./effects.js"
169
170# The node's fact, if the rule asked for it.
171def fact(n: Node) -> IO(Maybe<&2, Fact>):
172  import "./effects.js"
173
174def 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.
182def 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
189def 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.
201def option_number(opts: List<&2, Option>, key: String, default: U32) -> U32:
202  option_number_or(find(opts, key), default)
203
204def 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
215def option_flag(opts: List<&2, Option>, key: String, default: Bool) -> Bool:
216  option_flag_or(find(opts, key), default)
217
218def 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
229def option_text(opts: List<&2, Option>, key: String, default: String) -> String:
230  option_text_or(find(opts, key), default)
231
232def 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))