Application environment must only be read or written from a config module.
Scattered Application.get_env/2 and Application.put_env/3 calls make
configuration unauditable and untestable. Wrap every environment read and write in
a single config module per app, and have the rest of the app call that module.
# BAD — env read in a service module
defmodule MyApp.Worker do
def provider, do: Application.get_env(:my_app, :provider)
end
# GOOD — config.ex owns the env, the worker calls it
defmodule MyApp.Config do
def provider, do: Application.get_env(:my_app, :provider)
end
defmodule MyApp.Worker do
def provider, do: MyApp.Config.provider()
endConfig modules are identified by filename via the :config_files param, so this
works the same in an umbrella (apps/my_app/lib/my_app/config.ex) and a single app
(lib/my_app/config.ex).
There are no other exemptions. Test files and application.ex are checked too — a
test reaching for Application.put_env/3 is exactly the case this rule exists to
catch.
Env access is caught through every spelling of the module:
alias Application, as: App
App.get_env(:my_app, :provider) # caught
Elixir.Application.get_env(:my_app, :x) # caught
:application.get_env(:my_app, :x) # caughtAliases are resolved from a flat, file-level table rather than a lexical scope stack. An alias declared inside one function is treated as applying to the whole file. Being wrong would require the same alias name to mean two different modules in two functions of one file.
Aliases injected by a macro (via __using__) are invisible to Credo and cannot be
resolved.