src/rules/suspicious/fromrev.bend
bolt@1.21.0.0 · 49 lines · 0 definitions
1# rule fromrev: a String read off a reversed list: among the significant
2# tokens (spaces, newlines and comments dropped), four tokens right after one
3# another whose texts are `String.from_list`, `(`, `List.reverse` and `(`;
4# one finding, on the `String.from_list` token, for each such run, and
5# nothing else. Anything between them, another `(` included
6# (`String.from_list((List.reverse(..)))`), is not the shape, and neither is
7# `List.reverse.go`. A buffer of chars consed on the front, then reversed
8# and read into a String, walks the buffer twice and builds a list only to
9# throw it away (and String.from_list, not a tail call, runs the JS lane out
10# of stack on a long buffer): fold the buffer onto a String with SCon in one
11# pass, `go(t, SCon{h, acc})` from `SNil{}` (src/rchars.bend). A LAWS.bend
12# or a PROOF.bend, which never runs, is exempt.
13import Base
14import bolt@1.21.0.0/src/src.bend as Src
15import bolt@1.21.0.0/src/paths.bend as Paths
16import bolt@1.21.0.0/src/lazy/lazy.bend as Lazy
17import bolt@1.21.0.0/src/finding.bend as F
18import bolt@1.21.0.0/src/syntax/lex.bend as Lex
19import bolt@1.21.0.0/src/rules/tokens.bend as T
20
21# are the four texts `String.from_list`, `(`, `List.reverse` and `(`?
22def shape(+tt: String, +aa: String, +bb: String, +dd: String) -> Bool:
23 Bool.and(String.eq(tt, "String.from_list"),
24 Bool.and(String.eq(aa, "("), Bool.and(String.eq(bb, "List.reverse"), String.eq(dd, "("))))
25
26# a token of text tt, then these: do the four open the shape?
27def at(+tt: String, toks: List<&2, Lex.Tok>) -> Bool:
28 match toks:
29 case Con{Lex.Tok{k1, +aa, l1, c1}, Con{Lex.Tok{k2, +bb, l2, c2}, Con{Lex.Tok{k3, +dd, l3, c3}, rest}}}:
30 shape(tt, aa, bb, dd)
31 case other:
32 False{}
33
34# every run of the shape among the tokens, in order
35def walk(toks: List<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>:
36 match toks:
37 case Nil{}:
38 Nil{}
39 case Con{Lex.Tok{k, +t, +l, +c}, +rest}:
40 +more = walk(rest, path)
41 Bool.pick(List<&2, F.Finding>, at(t, rest),
42 F.Finding{path, l, c, 16, "fromrev", "String.from_list(List.reverse(..)) walks the list twice and builds one it throws away; fold it onto a String with SCon in one pass."}
43 <> more,
44 more)
45
46# the rule
47def check(ss: Src.Src) -> List<&2, F.Finding>:
48 Src.Src{+path, text, toks, tree, bound, items} = ss
49 Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => walk(T.sig(toks), path))