AshCredo.Check.Refactor.DirectiveInFunctionBody (ash_credo v0.14.0)

Copy Markdown View Source

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))
end

The 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.