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). 9 import Base 10 import ../vendor/lint.bend as Lint 11 import ../common.bend as Common 12 13 def id() -> String: 14 "graph/fan_out" 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 ++ " uses " ++ U32.show(d) ++ " different defs; the limit is " ++ U32.show(max) ++ ". Group related calls behind a helper def." 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 +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 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, edges, []) 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", 10), Common.Scope.of(sources, options)) 62 63 def main() -> IO(Unit): 64 Lint.serve(run)