View Source mix bond.audit (Bond v1.19.0)
Which public functions carry a Bond contract, and which carry none.
The one thing Bond.Coverage structurally cannot tell you. Its table reports
assertions that ran, so it is a report about contracts that exist: a
function nobody contracted appears nowhere in it, and neither does the module
it lives in.
That absence is the failure mode the contract-writing rules single out as the dangerous one, precisely because nothing shows it:
The failure mode is one-sided: a codebase with too few contracts looks exactly like one that never needed them, so careful screening under-contracts by default and nobody notices.
This turns it into a number.
mix bond.audit # the summary
mix bond.audit --verbose # and every uncontracted function
mix bond.audit --only Tidal # restrict to matching module names
mix bond.audit --min 40 # exit non-zero below 40%, for a ratchetIt reads the compiler, not the source
Every module that use Bond exports a __bond_contracted__/0 reflection
listing the functions it contracted, built by the compiler from what it
actually annotated. Nothing here parses Elixir.
That matters more than it sounds. A source-parsing audit cannot see a function
generated by a macro, a module that acquired use Bond through some other
__using__, or a defdelegate — and it has to guess at elixirc_paths and
umbrella layouts. The compiler already knows.
A module that uses Bond and contracts nothing exports %{}, which is a
different answer from not using Bond at all, and the more interesting one.
What it counts, and what it deliberately does not
The denominator is public, non-callback functions in modules that use Bond. Three exclusions, each because including it would measure something else:
- A module that does not use Bond cannot carry a contract. Adding
use Bondto a module is a separate decision from contracting a function in it. defpis reported but not counted. Private functions can and should carry contracts; they are not part of a module's promise to its callers, so a density over them answers a different question.@implcallbacks are counted apart. What a callback promises is the behaviour's to state — seeBond.Behaviour— so their absence from an implementation's own contracts is not the same finding. They are detected from the module's@behaviourattributes rather than from@impl, which is both more accurate and available at runtime.
A function that appears in the reflection carrying only a module
@invariant is counted apart as well. Bond checks that invariant around it, so
it is real coverage; but an invariant is a law about the type rather than a
promise about any one function, and merging the two would flatter the number.
It is a prompt, not a verdict
A bare function is a question — "what does this promise?" — and some honest
answers are "nothing beyond its @spec". The reasons to decline a contract are
short: it is unsound, it is unreachable, or the specification does not warrant
it. "There are a lot of them" is not among them, so a large count here is a
backlog rather than a defect.