graph/fan_out.bend

bend-lint-pack@0.1.1.0 · 64 lines · 8 definitions

1# graph/fan_out: warn when a def uses more than `max` different defs of the program (default 10).
2#
3# Fan-out is a def's coupling: each def it calls or names in a type is something it breaks with.
4# Counted: the different defs, in the linted file and the files it imports, that the def references
5# (in its body or its types). Not counted: itself, Base, hub packages, types, constructors, laws.
6# By default only the linted file's own defs are reported (`imports` reports every local file of
7# the program). Lint the entry file so that defs in imported files are seen.
8# Options: max (default 10), imports (default false).
9import Base
10import bend-lint-pack@0.1.1.0/vendor/lint.bend as Lint
11import bend-lint-pack@0.1.1.0/common.bend as Common
12
13def id() -> String:
14  "graph/fan_out"
15
16def facts() -> Lint.Want:
17  Lint.Want{Lint.Program{}, [], [], []}
18
19def message(+name: String, d: U32, max: U32) -> String:
20  "def " ++ name ++ " uses " ++ U32.show(d) ++ " different defs; the limit is " ++ U32.show(max) ++ ". Group related calls behind a helper def."
21
22def judge.one(+d: U32, +max: U32, +owner: String, span: Maybe<&2, Lint.Span>) -> List<&2, Lint.Diag>:
23  Common.when(
24    U32.is_gt(d, max),
25    Common.diag(Lint.Warning{}, message(owner, d, max), span)
26  )
27
28def judge(
29  seen: List<&2, Common.Seen>,
30  +max: U32,
31  +nodes: Set(),
32  +edges: Map<&2, List<&2, String>>,
33  out: List<&2, Lint.Diag>
34) -> List<&2, Lint.Diag>:
35  match seen:
36    case Nil{}:
37      List.reverse(&2, Lint.Diag, out)
38    case Con{Common.Seen{+owner, span}, rest}:
39      judge(
40        rest,
41        max,
42        nodes,
43        edges,
44        Common.onto(
45          judge.one(Common.degree(edges, nodes, owner), max, owner, span),
46          out
47        )
48      )
49
50def verdict(p: Common.Prog, max: U32, scope: Common.Scope) -> List<&2, Lint.Diag>:
51  match p:
52    case Common.Prog{seen, edges, into, nodes, laws}:
53      judge(Common.scoped(scope, seen), max, nodes, edges, [])
54
55def run(input: Lint.Input) -> IO(List<&2, Lint.Diag>):
56  match input:
57    case Lint.Input{+sources, +options}:
58      do IO<List<&2, Lint.Diag>>:
59        g : Common.Graph <- Lint.fold_facts(~Common.Graph, ~Common.Graph.collect, Common.Graph.empty())
60        p : Common.Prog <- Common.Prog.of(sources, g)
61        return verdict(p, Lint.option_number(options, "max", 10), Common.Scope.of(sources, options))
62
63def main() -> IO(Unit):
64  Lint.serve(run)