complexity/cyclomatic.bend
bend-lint-pack@0.1.1.0 · 105 lines · 13 definitions
1
2
3
4
5
6
7
8
9
10
11
12import Base
13import bend-lint-pack@0.1.1.0/vendor/lint.bend as Lint
14import bend-lint-pack@0.1.1.0/common.bend as Common
15
16def id() -> String:
17 "complexity/cyclomatic"
18
19def facts() -> Lint.Want:
20 Lint.Want{Lint.File{}, [], [], []}
21
22
23type Tally is Data:
24 Tally{mats: U32, efqs: U32}
25
26def bump.kind(+kind: String, t: Tally) -> Tally:
27 match t:
28 case Tally{+mats, +efqs}:
29 Bool.pick(
30 Tally,
31 String.eq(kind, "Mat"),
32 Tally{U32.add(mats, 1), efqs},
33 Bool.pick(
34 Tally,
35 String.eq(kind, "Efq"),
36 Tally{mats, U32.add(efqs, 1)},
37 Tally{mats, efqs}
38 )
39 )
40
41
42
43@unsafe
44def walk(n: Lint.Node, t: Tally) -> IO(Tally):
45 do IO<Tally>:
46 s : Lint.Shape <- Lint.shape(n)
47 walk.shape(s, t)
48
49def walk.all(cs: List<&2, Lint.Node>, t: Tally) -> IO(Tally):
50 match cs:
51 case Nil{}:
52 IO.pure(Tally, t)
53 case Con{c, rest}:
54 IO.bind(Tally, Tally, walk(c, t), t2 => walk.all(rest, t2))
55
56def walk.shape(s: Lint.Shape, t: Tally) -> IO(Tally):
57 match s:
58 case Lint.Shape{kind, name, span, children}:
59 walk.all(children, bump.kind(kind, t))
60
61
62def complexity(t: Tally) -> U32:
63 match t:
64 case Tally{+mats, efqs}:
65 U32.add(1, U32.sub(mats, U32.min(mats, efqs)))
66
67def score.of(body: Maybe<&2, Lint.Node>) -> IO(U32):
68 match body:
69 case None{}:
70 IO.pure(U32, 0)
71 case Some{n}:
72 IO.bind(Tally, U32, walk(n, Tally{0, 0}), t => IO.pure(U32, complexity(t)))
73
74def message(+name: String, score: U32, max: U32) -> String:
75 "def " ++ name ++ " has cyclomatic complexity " ++ U32.show(score) ++ "; the limit is " ++ U32.show(max) ++ ". Split it into smaller defs."
76
77def judge(+max: U32, seen: List<&2, Common.Seen>, out: List<&2, Lint.Diag>) -> IO(List<&2, Lint.Diag>):
78 match seen:
79 case Nil{}:
80 IO.pure(List<&2, Lint.Diag>, List.reverse(&2, Lint.Diag, out))
81 case Con{Common.Seen{+owner, span}, rest}:
82 do IO<List<&2, Lint.Diag>>:
83 body : Maybe<&2, Lint.Node> <- Lint.body(owner)
84 +score : U32 <- score.of(body)
85 judge(
86 max,
87 rest,
88 Common.onto(
89 Common.when(
90 U32.is_gt(score, max),
91 Common.diag(Lint.Warning{}, message(owner, score, max), span)
92 ),
93 out
94 )
95 )
96
97def run(input: Lint.Input) -> IO(List<&2, Lint.Diag>):
98 match input:
99 case Lint.Input{sources, options}:
100 do IO<List<&2, Lint.Diag>>:
101 owners : Common.Owners <- Lint.fold_facts(~Common.Owners, ~Common.Owners.collect, Common.Owners.empty())
102 judge(Lint.option_number(options, "max", 10), Common.Owners.ordered(owners), [])
103
104def main() -> IO(Unit):
105 Lint.serve(run)