Current section

Files

Jump to
credence lib semantic.ex
Raw

lib/semantic.ex

defmodule Credence.Semantic do
@moduledoc """
Semantic phase — fixes compiler warnings.
Uses `Code.with_diagnostics/1` to compile the source and capture
warnings without permanently loading modules. Delegates to rules
implementing `Credence.Semantic.Rule` behaviour.
"""
alias Credence.RuleHelpers
@spec analyze(String.t(), keyword()) :: [Credence.Issue.t()]
def analyze(source, _opts \\ []) do
case compile_and_capture(source) do
{:ok, diagnostics} ->
diagnostics
|> Enum.filter(&(&1.severity == :warning))
|> Enum.flat_map(&match_rules/1)
_error ->
[]
end
end
@spec fix(String.t(), keyword()) :: String.t()
def fix(source, _opts \\ []) do
case compile_and_capture(source) do
{:ok, diagnostics} ->
warnings = Enum.filter(diagnostics, &(&1.severity == :warning))
Enum.reduce(warnings, source, fn diagnostic, src ->
case find_matching_rule(diagnostic) do
nil -> src
rule -> rule.fix(src, diagnostic)
end
end)
_error ->
source
end
end
defp compile_and_capture(source) do
{result, diagnostics} =
Code.with_diagnostics(fn ->
try do
Code.compile_string(source, "credence_check.ex")
rescue
_ -> :error
end
end)
case result do
:error ->
{:error, diagnostics}
modules when is_list(modules) ->
cleanup_modules(modules)
{:ok, diagnostics}
end
end
defp cleanup_modules(modules) do
for {mod, _binary} <- modules do
:code.purge(mod)
:code.delete(mod)
end
end
defp match_rules(diagnostic) do
case find_matching_rule(diagnostic) do
nil -> []
rule -> [rule.to_issue(diagnostic)]
end
end
defp find_matching_rule(diagnostic) do
Enum.find(rules(), fn rule -> rule.match?(diagnostic) end)
end
defp rules do
RuleHelpers.discover_rules(Credence.Semantic.Rule)
end
end