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(defaulttrue) - whenfalse, the check leaves callers that share a domain with the resource alone. Useful for teams that consider rawAsh.*calls insideChange/Preparation/Validationmodules acceptable.enforce_code_interface_outside_domain(defaulttrue) - whenfalse, 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 anAsh.Resourcewith no:domain, and resources that cannot be loaded.prefer_interface_scope(:auto|:resource|:domain, default:auto) - overrides which interface the check points at.:autofollows the domain-aware heuristic above.:resourcealways suggests a resource-level interface (useful if you only define code interfaces on resources).:domainalways 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 qualifiedAsh.*(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 directpost = 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.