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). 9 import Base 10 import ../vendor/lint.bend as Lint 11 import ../common.bend as Common 12 13 def id() -> String: 14 "graph/fan_in" 15 16 def facts() -> Lint.Want: 17 Lint.Want{Lint.Program{}, [], [], []} 18 19 def 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 22 def 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 28 def 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 50 def 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 55 def run(input: Lint.Input) -> IO(List<&2, Lint.Diag>): 56 match input: 57 case Lint.Input{+sources, +options}: 58 do IO>: 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 63 def main() -> IO(Unit): 64 Lint.serve(run)