layout/imports.bend
bend-lint-rules@0.2.0.0 · 245 lines · 21 definitions
1
2
3
4
5
6
7import Base
8import bend-lint-api@0.2.0.0/linter/lint.bend as Lint
9import bend-lint-api@0.2.0.0/shared/lists.bend as Lists
10import bend-lint-api@0.2.0.0/shared/names.bend as Names
11import bend-lint-api@0.2.0.0/shared/strings.bend as Strings
12
13
14
15
16
17
18type Entry is Data:
19 Entry{at: Lint.Span, base: Bool, path: String, alias: String, loads: String, text: String}
20
21
22
23type Deduped is Data:
24 Deduped{
25 seen: List<&2, Names.Named<String>>,
26 kept: List<&2, Entry>,
27 renames: List<&2, Lint.Operation>
28 }
29
30
31type Block is Data:
32 Block{beg: U32, end: U32, entries: List<&2, Entry>}
33
34
35
36def is_blank(s: String) -> Bool:
37 match s:
38 case SNil{}:
39 True{}
40 case SCon{' ', rest}:
41 is_blank(rest)
42 case SCon{'\t', rest}:
43 is_blank(rest)
44 case SCon{'\r', rest}:
45 is_blank(rest)
46 case _:
47 False{}
48
49def kept(+line: String) -> String:
50 Bool.pick(String, is_blank(line), "", line ++ "\n")
51
52
53
54def comments(s: String, line: String, acc: List<&2, String>) -> String:
55 match s:
56 case SNil{}:
57 Strings.onto(kept(String.reverse(line)) <> acc, "")
58 case SCon{'\n', rest}:
59 comments(rest, "", kept(String.reverse(line)) <> acc)
60 case SCon{c, rest}:
61 comments(rest, SCon{c, line}, acc)
62
63def ended(+s: String) -> String:
64 Bool.pick(String, String.ends_with(s, "\n"), s, s ++ "\n")
65
66
67
68
69
70def add.line(
71 +text: String,
72 +beg: U32,
73 +end: U32,
74 at: Lint.Span,
75 base: Bool,
76 path: String,
77 alias: String,
78 loads: String,
79 b: Block
80) -> Block:
81 match b:
82 case Block{first, last, Nil{}}:
83 Block{beg, end, [Entry{at, base, path, alias, loads, ended(Strings.slice(text, beg, end))}]}
84 case Block{first, last, +entries}:
85 Block{
86 first,
87 end,
88 Entry{
89 at,
90 base,
91 path,
92 alias,
93 loads,
94 comments(Strings.slice(text, last, beg), "", []) ++ ended(Strings.slice(text, beg, end))
95 }
96 <> entries
97 }
98
99
100def add(
101 +text: String,
102 at: Lint.Span,
103 base: Bool,
104 path: String,
105 alias: String,
106 loads: String,
107 b: Block
108) -> Block:
109 match at:
110 case Lint.Span{p, +a, e}:
111 +beg = Strings.line_start(text, a)
112 add.line(
113 text,
114 beg,
115 Strings.line_end(text, beg),
116 Lint.Span{p, a, e},
117 base,
118 path,
119 alias,
120 loads,
121 b
122 )
123
124
125def block(nodes: List<&2, Lint.Node>, +text: String, b: Block) -> Block:
126 match nodes:
127 case Con{
128 Lint.Node{
129 Lint.Import{loads},
130 alias,
131 Some{at},
132 Con{Lint.Node{Lint.ImportPath{}, path, s, k, f}, ks},
133 fs
134 },
135 rest
136 }:
137 block(rest, text, add(text, at, False{}, path, alias, loads, b))
138 case Con{Lint.Node{Lint.Import{loads}, alias, Some{at}, k, f}, rest}:
139 block(rest, text, add(text, at, True{}, "Base", alias, loads, b))
140 case _:
141 b
142
143
144
145def loads_of(e: Entry) -> String:
146 match e:
147 case Entry{at, base, path, alias, loads, text}:
148 loads
149
150
151
152def dedupe.kept(first: Maybe<&2, String>, e: Entry, d: Deduped) -> Deduped:
153 match first e d:
154 case None{} Entry{at, base, path, +alias, +loads, text} Deduped{seen, kept, renames}:
155 Deduped{
156 Names.Named{loads, alias} <> seen,
157 Entry{at, base, path, alias, loads, text} <> kept,
158 renames
159 }
160 case Some{a} Entry{at, False{}, path, alias, loads, text} Deduped{seen, kept, renames}:
161 Deduped{seen, kept, Lint.RenameUses{at, a} <> renames}
162 case Some{a} Entry{at, True{}, path, alias, loads, text} _:
163 d
164
165def dedupe.add(+e: Entry, d: Deduped) -> Deduped:
166 match d:
167 case Deduped{+seen, kept, renames}:
168 dedupe.kept(Names.lookup(String, loads_of(e), seen), e, Deduped{seen, kept, renames})
169
170
171def dedupe(es: List<&2, Entry>, d: Deduped) -> Deduped:
172 match es:
173 case Nil{}:
174 d
175 case Con{e, rest}:
176 dedupe(rest, dedupe.add(e, d))
177
178
179def is_before(a: Entry, b: Entry) -> Bool:
180 match a b:
181 case Entry{i, True{}, p, x, l, t} _:
182 True{}
183 case Entry{i, False{}, p, x, l, t} Entry{j, True{}, q, y, m, u}:
184 False{}
185 case Entry{i, False{}, +p, x, l, t} Entry{j, False{}, +q, y, m, u}:
186 Bool.pick(Bool, String.eq(p, q), String.is_le(x, y), String.is_lt(p, q))
187
188
189def texts(es: List<&2, Entry>, acc: List<&2, String>) -> String:
190 match es:
191 case Nil{}:
192 Strings.onto(acc, "")
193 case Con{Entry{at, base, path, alias, loads, text}, rest}:
194 texts(rest, text <> acc)
195
196
197
198def verdict(
199 same: Bool,
200 +span: Lint.Span,
201 ordered: String,
202 renames: List<&2, Lint.Operation>
203) -> List<&2, Lint.Finding>:
204 match same:
205 case True{}:
206 []
207 case False{}:
208 [
209 Lint.Finding{
210 Lint.Warning{},
211 span,
212 "Order the imports: import Base first, then by path and alias, each file once.",
213 [Lint.Fix{"Order the imports", Lint.Replace{span, ordered} <> renames}]
214 }
215 ]
216
217
218def judged(span: Lint.Span, written: String, d: Deduped) -> List<&2, Lint.Finding>:
219 match d:
220 case Deduped{seen, kept, renames}:
221 +ordered = texts(Lists.sort(~Entry, ~is_before, List.reverse(&2, Entry, kept)), [])
222 verdict(String.eq(ordered, written), span, ordered, renames)
223
224def judge(path: String, text: String, b: Block) -> List<&2, Lint.Finding>:
225 match b:
226 case Block{beg, end, Nil{}}:
227 []
228 case Block{+beg, +end, entries}:
229 judged(
230 Lint.Span{path, beg, end},
231 Strings.slice(text, beg, end),
232 dedupe(List.reverse(&2, Entry, entries), Deduped{[], [], []})
233 )
234
235def run(+here: Lint.Here) -> Lint.Ask(List<&2, Lint.Finding>):
236 Lint.done(
237 judge(
238 Lint.Here.path(here),
239 Lint.Here.text(here),
240 block(Lint.Here.nodes(here), Lint.Here.text(here), Block{0, 0, []})
241 )
242 )
243
244def rule() -> Lint.Rule:
245 Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}