KogenCredo.UnusedPublic (KogenCredo v0.1.0)

View Source

Advisory finder for public functions that nothing else in the project calls.

It answers "which defs have no caller?" from the expanded code in compiled BEAM debug info, not from source text, so aliases, imports and use-injected code are already resolved. The pure core works on {module, beam_binary} pairs; Mix.Tasks.Kogen.UnusedPublic feeds it the project's compiled modules.

Findings

  • :no_callers: nothing in the analysed modules calls, captures or references the function.
  • :only_local: only other functions of the same module call it, so it may belong in a defp.

What is excluded

  • behaviour callbacks: functions marked @impl, and any function that is a callback of a declared @behaviour (loaded through behaviour_info/1);
  • __name__ functions, module_info, child_spec/1, main/1;
  • macros (defmacro): a macro call is expanded away, so it leaves no call to find;
  • modules matching :entrypoint_modules (default: controllers, routers, channels, sockets, Mix.Tasks.*) and any Module.fun/arity listed in :entrypoint_functions;
  • modules matching :ignore_modules.

Functions with default arguments are treated as one group: the generated lower-arity heads are not reported and calls between them do not count.

What this does NOT prove

This is a heuristic over the modules you give it, not whole-program proof. A function can still be live when:

  • it is called by name at runtime (apply/3 with computed values, String.to_existing_atom, Oban/telemetry/config MFAs stored in a database or non-literal data); literal apply(Mod, :fun, [..]) calls, {Mod, :fun, [..]} tuples and &Mod.fun/n captures are counted;
  • it is part of a library's public API for other projects (do not run this on a library, or list its public modules in :ignore_modules);
  • it is called only from tests, other umbrella apps you did not include, templates compiled elsewhere, or code in priv/;
  • a framework dispatches to it (LiveView events, controller actions outside the default patterns): add the module to :entrypoint_modules.

Conversely a function can look used because a same-named call exists in a dead function; the analysis is one level deep and does not compute reachability from entry points.

Summary

Functions

Finds public functions without callers.

Reads module infos from {module, beam_binary} pairs.

Renders findings as lines file:line Mod.fun/arity (kind).

Types

finding()

@type finding() :: %{
  module: module(),
  name: atom(),
  arity: arity(),
  line: pos_integer() | nil,
  file: String.t() | nil,
  kind: :no_callers | :only_local
}

mfa_key()

@type mfa_key() :: {module(), atom(), arity()}

module_info()

@type module_info() :: %{
  module: module(),
  file: String.t() | nil,
  behaviours: [module()],
  defs: [
    %{
      name: atom(),
      arity: arity(),
      line: pos_integer() | nil,
      impl: boolean(),
      defaults: non_neg_integer()
    }
  ],
  calls: [
    {caller :: {atom(), arity()}, callee :: mfa_key() | {:dynamic, mfa_key()}}
  ]
}

Functions

analyze(infos, opts \\ [])

@spec analyze(
  [module_info()],
  keyword()
) :: [finding()]

Finds public functions without callers.

Options:

  • :entrypoint_modules - list of Regex or module-name strings (exact match on inspect(module)); default controllers, routers, channels, sockets and Mix.Tasks.*
  • :entrypoint_functions - list of "Mod.fun/arity" strings or {mod, fun, arity}
  • :ignore_modules - same shape as :entrypoint_modules, default []

Returns findings sorted by file and line.

collect(beams)

@spec collect([{module(), binary() | charlist()}]) ::
  {[module_info()], [{module(), term()}]}

Reads module infos from {module, beam_binary} pairs.

Returns {infos, skipped} where skipped lists {module, reason} for beams without debug info (compiled with debug_info: false) or unreadable chunks. Those modules are neither analysed nor counted as callers, which can create false findings; callers should surface skipped to the user.

format(findings)

@spec format([finding()]) :: [String.t()]

Renders findings as lines file:line Mod.fun/arity (kind).