Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/credo/check.ex
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,38 @@ defmodule Credo.Check do
@__default_enabled_checks__
end

@doc false
def all_loaded_checks do
# Source credo's own checks from the curated `standard_checks/0` list
# rather than `:code.all_loaded/0` — until something references an
# unused check module the BEAM hasn't loaded it, so the all-loaded set
# silently misses checks that nothing has yet pulled in. That's the
# root of `MissingCheckInConfig` failing to flag unused checks (#1278).
#
# We supplement with check modules from any *other* loaded application
# (plugins, custom check libraries) by reading their module list from
# `:application.get_key/2`, which doesn't depend on prior code loading.
Enum.uniq(standard_checks() ++ external_check_modules())
end

defp external_check_modules do
for {app, _, _} <- Application.loaded_applications(),
app != :credo,
{:ok, modules} <- [:application.get_key(app, :modules)],
module <- modules,
Code.ensure_loaded?(module),
implements_credo_check?(module),
do: module
end

defp implements_credo_check?(module) do
function_exported?(module, :module_info, 1) and
module.module_info(:attributes)
|> Keyword.get_values(:behaviour)
|> List.flatten()
|> Enum.member?(__MODULE__)
end

@doc false
# TODO: decide whether this should live here or in `Credo.Execution`.
def mentioned_checks(exec) do
Expand Down
25 changes: 25 additions & 0 deletions test/credo/check_all_loaded_checks_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Credo.Check.AllLoadedChecksTest do
use ExUnit.Case, async: true

# `all_loaded_checks/0` used to source its result from `:code.all_loaded/0`,
# so any check module nothing had yet referenced was silently missing —
# which is exactly what made `MissingCheckInConfig` fail to flag unused
# checks (#1278). The fix sources credo's own checks from the curated
# `standard_checks/0` list and supplements with check modules discovered
# via `:application.get_key/2` on every other loaded application, so the
# result is independent of which modules happen to be loaded.

test "returns every standard credo check regardless of load order" do
all = Credo.Check.all_loaded_checks()
missing = Credo.Check.standard_checks() -- all

assert missing == [],
"expected all standard checks to be included in `all_loaded_checks/0`, " <>
"but these were missing: #{inspect(missing)}"
end

test "result contains no duplicates" do
all = Credo.Check.all_loaded_checks()
assert all == Enum.uniq(all)
end
end