layout/directive_order.bend
bend-lint-rules@0.2.1.0 · 182 lines · 0 definitions
1# layout/directive_order: directives that stack on consecutive comment-only lines, out of order: by
2# rule, then severity, then keyword.
3# Fix: writes them in that order, over the same comments.
4# Heuristics: it reads the directives as written, and its fix writes the same comments over them, so a
5# fix keeps the program.
6import Base
7import bend-lint-rules@0.2.1.0/lib/linter/directives.bend as Directives
8import bend-lint-api@0.2.1.0/linter/lint.bend as Lint
9import bend-lint-api@0.2.1.0/shared/lists.bend as Lists
10
11# Types
12# =====
13
14# A directive of a stack: its key, where its comment is, and the comment's text.
15type Slot is Data:
16 Slot{key: String, beg: U32, end: U32, text: String}
17
18# A stack of directives: where it starts and ends, its last line, its last key, whether it is in order, and its
19# directives (last first).
20type Run is Data:
21 Run{beg: U32, end: U32, line: U32, key: String, sorted: Bool, slots: List<&2, Slot>}
22
23# The stacks so far: the open one, and the findings about the closed ones that are out of order (last first).
24type Runs is Data:
25 Runs{open: Maybe<&2, Run>, out: List<&2, Lint.Finding>}
26
27# Functions
28# =========
29
30# What a comment's directive is sorted by: its rule, then the severity it pins, then its keyword; none if it is no
31# directive.
32def key(draft: Maybe<&2, Maybe<&2, Directives.Draft>>) -> Maybe<&2, String>:
33 match draft:
34 case Some{
35 Some{Directives.Draft{Directives.Keyword{a, s}, Directives.Target{r, pinned}, reasons}}
36 }:
37 Some{
38 r
39 ++ "\t"
40 ++ Maybe.default(&2, String, pinned, "")
41 ++ "\t"
42 ++ Directives.Action.show(a)
43 ++ "-"
44 ++ Directives.Scope.show(s)
45 }
46 case _:
47 None{}
48
49def Slot.is_le(a: Slot, b: Slot) -> Bool:
50 match a b:
51 case Slot{k, x, y, t} Slot{j, v, w, u}:
52 String.is_le(k, j)
53
54# The edits that write the directives of `sorted` into the places of `slots` (both in order), onto `out`.
55def Slot.order(
56 slots: List<&2, Slot>,
57 sorted: List<&2, Slot>,
58 +path: String,
59 out: List<&2, Lint.Operation>
60) -> List<&2, Lint.Operation>:
61 match slots sorted:
62 case Con{Slot{k, beg, end, t}, rest} Con{Slot{j, b, e, text}, more}:
63 Slot.order(rest, more, path, Lint.Replace{Lint.Span{path, beg, end}, text} <> out)
64 case _ _:
65 List.reverse(&2, Lint.Operation, out)
66
67def Runs.close(
68 open: Maybe<&2, Run>,
69 +path: String,
70 out: List<&2, Lint.Finding>
71) -> List<&2, Lint.Finding>:
72 match open:
73 case Some{Run{beg, end, line, key, False{}, slots}}:
74 +ordered = List.reverse(&2, Slot, slots)
75 Lint.Finding{
76 Lint.Warning{},
77 Lint.Span{path, beg, end},
78 "Directives that stack must be in order: by rule, then severity, then keyword.",
79 [
80 Lint.Fix{
81 "Order the directives",
82 Slot.order(ordered, Lists.sort(~Slot, ~Slot.is_le, ordered), path, [])
83 }
84 ]
85 }
86 <> out
87 case _:
88 out
89
90# A stack of one directive: its key, and its comment's place, line and text after the #.
91def Run.first(+key: String, +beg: U32, +end: U32, line: U32, body: String) -> Run:
92 Run{beg, end, line, key, True{}, [Slot{key, beg, end, "#" ++ body}]}
93
94def Runs.extend(
95 continues: Bool,
96 run: Run,
97 comment: Directives.Comment,
98 +key: String,
99 path: String,
100 out: List<&2, Lint.Finding>
101) -> Runs:
102 match continues run comment:
103 case True{} Run{beg, end, line, last, sorted, slots} Directives.Comment{b, +e, l, t, body}:
104 Runs{
105 Some{
106 Run{
107 beg,
108 e,
109 l,
110 key,
111 sorted && String.is_le(last, key),
112 Slot{key, b, e, "#" ++ body} <> slots
113 }
114 },
115 out
116 }
117 case False{} _ Directives.Comment{b, e, l, t, body}:
118 Runs{Some{Run.first(key, b, e, l, body)}, Runs.close(Some{run}, path, out)}
119
120# The stacks, with a comment read: a directive on its own line, `key`, stacks on the one on the line before it.
121def Runs.step(
122 key: Maybe<&2, String>,
123 comment: Directives.Comment,
124 path: String,
125 runs: Runs
126) -> Runs:
127 match key comment runs:
128 case Some{
129 k
130 } Directives.Comment{b, e, +l, False{}, body} Runs{Some{Run{rb, re, +rl, rk, rs, slots}}, out}:
131 Runs.extend(
132 U32.is_eq(l, (rl + 1: U32)),
133 Run{rb, re, rl, rk, rs, slots},
134 Directives.Comment{b, e, l, False{}, body},
135 k,
136 path,
137 out
138 )
139 case Some{+k} Directives.Comment{b, e, l, False{}, body} Runs{None{}, out}:
140 Runs{Some{Run.first(k, b, e, l, body)}, out}
141 case _ _ _:
142 runs
143
144def Runs.go(
145 comments: List<&2, Directives.Comment>,
146 +path: String,
147 runs: Runs
148) -> List<&2, Lint.Finding>:
149 match comments runs:
150 case Nil{} Runs{open, out}:
151 List.reverse(&2, Lint.Finding, Runs.close(open, path, out))
152 case Con{Directives.Comment{b, e, l, t, +body}, rest} _:
153 Runs.go(
154 rest,
155 path,
156 Runs.step(
157 key(Directives.Draft.read(body)),
158 Directives.Comment{b, e, l, t, body},
159 path,
160 runs
161 )
162 )
163
164def stacks(scanned: Directives.Scanned, path: String) -> List<&2, Lint.Finding>:
165 match scanned:
166 case Directives.Scanned{comments, lines}:
167 Runs.go(comments, path, Runs{None{}, []})
168
169# The stacks out of order in a file that has a directive's mark.
170def found(marked: Bool, text: String, path: String) -> List<&2, Lint.Finding>:
171 match marked:
172 case True{}:
173 stacks(Directives.Scan.of(text), path)
174 case False{}:
175 []
176
177def run(+here: Lint.Here) -> Lint.Ask(List<&2, Lint.Finding>):
178 +text = Lint.Here.text(here)
179 Lint.done(found(Directives.has_mark(text, False{}), text, Lint.Here.path(here)))
180
181def rule() -> Lint.Rule:
182 Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}