presubmit
A linter for git commits. It checks the commit, the staged index, or the working tree against rules that understand Elixir, such as "an added controller is routed", "an added migration sorts last", and "a move contains nothing but the move".
The idea and the name come from Chromium's PRESUBMIT.py.
Setup
# mix.exs
{:presubmit, "~> 0.1.0", only: [:dev, :test], runtime: false}
mix presubmit.install # commit-msg hook: checks each commit (staged changes + message) before it exists
mix presubmit # by hand: the working tree if dirty, else HEAD
mix presubmit --list # what is configured
Add import_deps: [:presubmit] to .formatter.exs. In CI, check every
commit (HEAD on a pull request is a synthetic merge, so use the range):
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: mix presubmit --range ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
mix presubmit --help lists the sources (--head, --rev, --staged,
--worktree, --range) and options (--format json, --warnings-as-errors).
Configuration
Without .presubmit.exs the defaults apply: every rule that does not fail an
ordinary commit, plus the Phoenix and Ecto sets when those libraries are
present, with the opinionated rules as warnings. The output says what was
enabled and why. To choose, the file is a list of rule sets:
[
Presubmit.Rules.Elixir,
{Presubmit.Rules.Ecto, except: [:migrations_reversible], since: "origin/main"},
{Presubmit.Rules.ExUnit, only: [:behaviour_changes_tested], warn: [:behaviour_changes_tested]},
{Presubmit.Rules.Message, subject: ~r/^\[[a-z_-]+\] /},
{Presubmit.Rules.Shape, in: ~r{^apps/core/}},
MyApp.CommitRules
]
Each entry takes only:/except: to select rules, warn: to report without
failing, in: to restrict the set to changes under a path, and the set's own
options. A commit can exempt itself with a Presubmit-Skip: rule, rule or
No-Presubmit: true trailer, which is announced on every run.
Rules
Each set documents its rules and options:
Presubmit.Rules.Elixir: specs, moduledocs, deprecation before removal, pure movesPresubmit.Rules.Phoenix: added controllers and LiveViews are routedPresubmit.Rules.Ecto: migrations ordered, immutable, concurrent, reversible; schema columns migratedPresubmit.Rules.OTP: added processes are supervisedPresubmit.Rules.ExUnit: tests move with codePresubmit.Rules.Mix:mix.lockin syncPresubmit.Rules.Changelog: API changes and releases loggedPresubmit.Rules.Message: subject shape, scope, trailersPresubmit.Rules.Hygiene: no debug calls, conflict markers, or artifactsPresubmit.Rules.Shape: size ceilings
Your own rules are a module with use Presubmit.RuleSet and rule declarations
over the verbs in Presubmit.Assertions
and the queries in Presubmit.Query;
see Presubmit.RuleSet.
Rules see source shapes, not macro output: what a macro generates is
invisible to them (see each adapter under Presubmit.Adapters).
License
MIT