Configuration
View SourceExQuality runs without configuration. Everything below is for overriding what it decides on its own.
Precedence
Four sources, merged in order, later wins:
- Defaults
- Auto-detection - a stage is available when the project depends on its tool
.quality.exs- read from the project root, not the working directory- CLI flags
Auto-detection only ever sets availability. A stage that is available can still
be turned off by .quality.exs or a flag, and one that is unavailable can be
forced on with enabled: true - which will fail if the tool really is missing.
CLI flags
mix quality --quick # skip dialyzer and coverage enforcement
mix quality --skip-credo # skip static analysis
mix quality --skip-dialyzer # skip type checking
mix quality --skip-doctor # skip documentation coverage
mix quality --skip-gettext # skip translation checks
mix quality --skip-sobelow # skip security analysis
mix quality --skip-dependencies # skip unused deps and the security audit
mix quality --skip KEY # skip any stage by key; repeatable
mix quality --verbose # print full output even for stages that passed
mix quality --report PATH # also write a JSON report to PATH
mix quality --format json # JSON report on stdout, human output on stderr
Flags combine: mix quality --quick --skip-credo.
A skipped stage is still reported, with the flag as its reason:
○ Credo: skipped (--skip-credo)--skip KEY takes a stage key rather than being one switch per stage, so it
also reaches a custom stage, which has no --skip-<key> of its own. It is
repeatable, and it names itself as the reason:
mix quality --skip nullability --skip credo
○ Nullability: skipped (--skip nullability)
○ Credo: skipped (--skip credo).quality.exs
A keyword list at the project root. Every key is optional.
[
# Global
quick: false,
compile: [
warnings_as_errors: true
],
credo: [
enabled: :auto,
strict: true,
all: false,
# Names of the .credo.exs configs to run, in order. nil is one run with no
# --config-name, which is credo's own default. See docs/stages.md.
configs: nil
],
dialyzer: [
enabled: :auto
],
doctor: [
enabled: :auto,
summary_only: false
],
gettext: [
enabled: :auto,
# The locale the source is written in, whose .po files are not checked.
source_locale: "en",
# Basenames to skip. Phoenix generates errors.po with empty entries.
exclude: ["errors.po"],
# Run `mix gettext.extract --merge` first. It writes to the repository and
# recompiles the project, so the run serialises this stage when it is on.
extract: false
],
sobelow: [
enabled: :auto,
# Confidence level that blocks a run, used only when .sobelow-conf sets no
# `exit:` of its own.
exit: "medium",
# Render the findings below that level as well as counting them.
show_informational: false
],
dependencies: [
enabled: :auto,
check_unused: true,
audit: :auto
],
test: [
# :auto measures coverage when the project's own config asks for it.
coverage: :auto,
# Extra arguments for mix test / mix coveralls.
args: []
]
]enabled
Every stage takes one of three values:
| Value | Meaning |
|---|---|
:auto | run when the tool is installed (the default) |
true | always run; errors if the tool is missing |
false | never run; reported as ○ Credo: skipped (disabled in .quality.exs) |
Custom stages
A project's own check - a house rule, a schema linter, a custom mix task, a
shell script gate - runs as a stage of the run rather than beside it, declared
under custom:. It then has the same parallelism, timing, printer and JSON
report as a built-in stage, which is what lets a caller route its findings.
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 lives in the entry rather than in callbacks on a module
so 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.
Module entries
module: names a module exporting run/1 and returning an
ExQuality.Stage.result() - the contract every built-in stage already
satisfies. It may also export stage_kind/1; a module that does not is a
reader.
Command entries
| Key | Default | Meaning |
|---|---|---|
command, args | required | passed to System.cmd/3 |
env | [] | extra environment |
cd | project root | working directory |
kind | :reader | :writer runs it serialized, ahead of the readers |
parse | :json | :json attempts the finding contract, :none never tries |
skip_exit_code | none | exit code meaning "not applicable" |
enabled | true | false skips the stage, reported with the file as the reason |
Exit code 0 is a pass, anything else is a failure. The command runs with
stderr_to_stdout: true. The JSON finding contract, and the reader/writer
warning, are in stages.md.
Validation
A malformed entry fails the run at load time and names itself, because a stage that never registers is a check nobody is told is not running. The rules:
keyandnameare both required.- exactly one of
moduleorcommand. keyandnamemay not collide with a built-in stage. A custom stage shadowing Credo is the same class of bug as an aliased mix task.keymust be unique across entries.kindmust be:readeror:writer;parsemust be:jsonor:none;skip_exit_codemust be an integer.modulemust be loadable and exportrun/1.
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 - credo.configs exists for exactly that reason.
Nor are they a way to get out from under a failing check. Converting a stage into a custom one so it can be skipped moves the problem rather than fixing it.
Test arguments
Extra arguments reach mix test or mix coveralls two ways. On the command
line, after --:
mix quality -- --only integration
mix quality --quick -- --include slow --seed 0
Or in .quality.exs:
[test: [args: ["--only", "integration"]]]CLI arguments replace the configured ones rather than merging with them.
Thresholds are not configured here
Coverage and security thresholds are deliberately absent from .quality.exs.
They are read from the tool that owns them - coveralls.json, test_coverage
in mix.exs, .sobelow-conf - so a project has one place to change what
"passing" means, and changing it is visible as a change to that file. See
stages.md.