AshCredo.Check.Refactor.UseCodeInterface (ash_credo v0.13.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

When both the resource and action name are literal values, prefer calling a code interface function instead of a raw Ash.* API call.

This check queries Ash's runtime introspection (Ash.Resource.Info and Ash.Domain.Info) to produce precise suggestions:

  • names the exact existing code interface function when one is defined on the resource or the domain;
  • suggests defining a code interface when the action exists but no interface targets it yet.

By default the check is domain-aware: if the caller and the resource share a domain it prefers the resource-level interface, otherwise it points at the domain-level interface.

# In-domain caller, resource has `define :published`
# Flagged
Ash.read!(MyApp.Post, action: :published)
# Preferred
MyApp.Post.published!()

# Outside-domain caller, domain has `define :list_posts, action: :read`
# Flagged
Ash.read!(MyApp.Post)
# Preferred
MyApp.Blog.list_posts()

Builder calls (Ash.Query.for_read/3, Ash.Changeset.for_*/4, Ash.ActionInput.for_action/3) are flagged with the matching query_to_* / changeset_to_* / input_to_* helper that Ash generates for code interfaces.

Detection of references to actions that do not exist on the resource lives in AshCredo.Check.Warning.UnknownAction - enable that check separately if you want typo detection.

Configuration

Three params let you adapt the check to a team's code-interface conventions:

  • enforce_code_interface_in_domain (default true) - when false, the check leaves callers that share a domain with the resource alone. Useful for teams that consider raw Ash.* calls inside Change/Preparation/Validation modules acceptable.
  • enforce_code_interface_outside_domain (default true) - when false, the check silences every case where the caller is not confirmed to be in the resource's domain: different known domain, plain caller (controller, LiveView, worker), caller that is an Ash.Resource with no :domain, and resources that cannot be loaded.
  • prefer_interface_scope (:auto | :resource | :domain, default :auto) - overrides which interface the check points at. :auto follows the domain-aware heuristic above. :resource always suggests a resource-level interface (useful if you only define code interfaces on resources). :domain always suggests a domain-level interface.

Example - a team that allows raw calls inside their domain and only defines interfaces on resources:

{AshCredo.Check.Refactor.UseCodeInterface,
 [enforce_code_interface_in_domain: false, prefer_interface_scope: :resource]}

Requirements

The check calls Code.ensure_compiled/1 on every referenced resource to query Ash's introspection API. This means your project must be compiled before running mix credo - typically mix compile && mix credo or a Mix alias that chains the two.

If Ash is not available in the VM running Credo, the check is a no-op and emits a single diagnostic.

Known limitations

  • Calls made via import Ash; read!(...) are not traced - only fully qualified Ash.* (or aliased) module calls are detected.
  • Records obtained via pattern matching (e.g. {:ok, post} = Ash.get(...)) or helper functions are not traced through bindings; only direct post = Ash.get!(...) / Ash.get(...) assignments and pipe chains are recognised.

Check-Specific Parameters

Use the following parameters to configure this check:

:enforce_code_interface_in_domain

Flag raw Ash.* calls whose caller shares a domain with the resource. Set to false to leave same-domain callers alone (useful for teams that consider raw calls inside Change/Preparation/Validation modules acceptable).

This parameter defaults to true.

:enforce_code_interface_outside_domain

Flag raw Ash.* calls whose caller is not in the resource's domain. This covers different known domains, plain callers (controller, LiveView, worker), callers that are an Ash.Resource with no :domain, and resources that cannot be loaded. Set to false to silence all of them.

This parameter defaults to true.

:prefer_interface_scope

Controls which interface the check points at. :auto (default) follows the "in-domain → resource, outside-domain → domain" heuristic. :resource always suggests a resource-level interface. :domain always suggests a domain-level interface.

This parameter defaults to :auto.

General Parameters

Like with all checks, general params can be applied.

Parameters can be configured via the .credo.exs config file.