ExQuality.Custom (ExQuality v0.13.0)
View SourceProject-defined stages, declared in .quality.exs under custom:.
A project with a house check, a schema linter, a custom mix task or a shell
script gate could have ExQuality's parallelism, timing, report and printer,
or it could have its own check, but not both. Custom stages are the way to
have both, and a check that runs inside mix quality is a check the JSON
report can route on.
Two layers, one mechanism:
custom: [
# A command. This is the case most projects want.
[
key: :nullability,
name: "Nullability",
command: "mix",
args: ["schema.nullability", "--format", "json"],
env: [{"MIX_ENV", "test"}],
kind: :reader
],
# A module, for anything the command form cannot express.
[key: :house_rules, name: "House rules", module: MyApp.Quality.HouseRules]
]Every entry is a keyword list with key and name, plus either module or
command. Registration data lives in the entry rather than in callbacks on a
module so that the config file alone says what a run will contain: a stage
that only announces itself once it has run cannot be reported as skipped, and
a run that says nothing about a stage reads as a run where the stage passed.
Module entries
module: names a module exporting run/1 and returning an
ExQuality.Stage.result(), which is the contract every built-in stage
already satisfies. It may also export stage_kind/1; a module that does not
is a :reader. See ExQuality.Stage.
Command entries
command: is run by ExQuality.Stages.Command, which documents the options
and the JSON finding contract.
What custom stages are not for
Filling gaps in built-in stages. Routing a second mix credo config through
a custom command would work and would be a mistake: the output comes back as
text instead of per-check findings, and per-check routing is the reason the
JSON report exists. If a built-in stage cannot express something its tool
supports, that is a bug in the stage.
Summary
Functions
Returns whether an entry reads the build or writes to it.
Returns the function that runs an entry, given the run's config.
Returns why a custom stage will not run, or nil when it will.
Returns the custom stage entries in a loaded config, in declaration order.
Raises unless every custom entry in config is well formed.
Functions
@spec kind(keyword(), keyword()) :: ExQuality.Stage.kind()
Returns whether an entry reads the build or writes to it.
An entry that declares kind: is taken at its word. Otherwise a module entry
is asked, via ExQuality.Stage.kind/2, and a command entry is a :reader.
iex> ExQuality.Custom.kind([key: :a, name: "A", command: "true"], [])
:reader
@spec runner(keyword()) :: (keyword() -> ExQuality.Stage.result())
Returns the function that runs an entry, given the run's config.
Returns why a custom stage will not run, or nil when it will.
--skip <key> is read through ExQuality.Config.skip_reason/2, exactly as a
built-in stage's switch is. enabled: false in the entry itself is the
config-file spelling.
Returns the custom stage entries in a loaded config, in declaration order.
iex> ExQuality.Custom.stages([])
[]
@spec validate!(keyword()) :: :ok
Raises unless every custom entry in config is well formed.
Custom stages are the place a config file can be wrong in ways that silently weaken a run - a stage that never registers is a check nobody is told is not running - so this fails the run at load time and names the offending entry rather than letting it go quiet.