Basics
This check is disabled by default.
Learn how to enable it via .credo.exs.
This check has a base priority of normal and works with any version of Elixir.
Explanation
Flags require/import/alias directives for configured modules
when they're declared inside a function body. These directives belong
at the top of the module so they apply once for the whole file
instead of being repeated in every function that needs them.
The default-and-canonical case is require Ash.Query and
require Ash.Expr: AI coding assistants frequently drop a fresh
require into every function that uses an Ash.Query.* or
Ash.Expr.* macro, instead of requiring the module once at the top
of the module. The result is files with three or four duplicated
directives, which is noisy, non-idiomatic, and a recognisable code
smell.
# Flagged
defmodule MyApp.PostQueries do
def published do
require Ash.Query
MyApp.Post |> Ash.Query.filter(Ash.Query.expr(state == :published))
end
def draft do
require Ash.Query
MyApp.Post |> Ash.Query.filter(Ash.Query.expr(state == :draft))
end
end
# Preferred
defmodule MyApp.PostQueries do
require Ash.Query
def published, do: MyApp.Post |> Ash.Query.filter(Ash.Query.expr(state == :published))
def draft, do: MyApp.Post |> Ash.Query.filter(Ash.Query.expr(state == :draft))
endThe check is purely syntactic. It walks the source AST tracking the
depth of def/defp/defmacro/defmacrop blocks and emits an issue
whenever it finds a target directive at depth >= 1.
Directives generated inside quote do ... end blocks are deliberately
ignored: a macro author writing quote do require Ash.Query end injects
the directive at the call site, not at the macro's own definition site,
so flagging that case would be a false positive.
Configuration
directive_modules defaults to [Ash.Query, Ash.Expr]. The check
is general - any module you put in the list is checked the same way.
Add Ash extension modules from your own codebase, or any third-party
module whose directives you want centralised:
{AshCredo.Check.Refactor.DirectiveInFunctionBody,
[directive_modules: [Ash.Query, Ash.Expr, MyApp.CustomMacros]]}The check matches the exact module specified. Only modules that
appear literally in the list are flagged; child modules are not.
For example, with [Ash.Query] configured, require Ash.Query
is flagged but require Ash.Query.Some.Child is not.
The default reflects the most common cases in Ash codebases (the
require Ash.Query and require Ash.Expr repetition AI assistants
love). The check itself is general; this plugin just ships
Ash-flavoured defaults.
Check-Specific Parameters
Use the following parameters to configure this check:
:directive_modules
List of modules whose require/import/alias directives must live at module level rather than inside function bodies. Defaults to [Ash.Query, Ash.Expr] because those are the most common offenders in Ash codebases. The check itself is general - add any module whose directives your team wants centralised.
This parameter defaults to [Ash.Query, Ash.Expr].
General Parameters
Like with all checks, general params can be applied.
Parameters can be configured via the .credo.exs config file.