graph/fan_in.bend

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

1# graph/fan_in: warn when more than `max` different defs of the program use one def (default 20).
2#
3# Fan-in is a def's weight: every caller depends on its contract, so a change to it spreads.
4# Counted: the different defs, in the linted file and the files it imports, that reference the def
5# (in their bodies or types). Not counted: itself, laws and their proofs (specs, not callers), Base,
6# hub packages. By default only the linted file's own defs are reported (`imports` reports every
7# local file of the program). Lint the entry file so that callers in imported files are seen.
8# Options: max (default 20), 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_in"
15
16def facts() -> Lint.Want:
17  Lint.Want{Lint.Program{}, [], [], []}
18
19def message(+name: String, d: U32, max: U32) -> String:
20  "def " ++ name ++ " is used by " ++ U32.show(d) ++ " different defs; the limit is " ++ U32.show(max) ++ ". Keep its contract small and stable, or split it by purpose."
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  +into: 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        into,
44        Common.onto(
45          judge.one(Common.degree(into, 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, into, [])
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", 20), Common.Scope.of(sources, options))
62
63def main() -> IO(Unit):
64  Lint.serve(run)