Current section
Files
Jump to
Current section
Files
lib/credence.ex
defmodule Credence do
@moduledoc """
Credence (Semantic Linter for Elixir)
Main entry point for analyzing Elixir code.
"""
alias Credence.Issue
@doc """
Analyzes an Elixir code string and returns a deterministic pass/fail result.
"""
@spec analyze(String.t(), keyword()) :: %{valid: boolean(), issues: [Issue.t()]}
def analyze(code_string, opts \\ []) do
rules = Keyword.get(opts, :rules, default_rules())
case Code.string_to_quoted(code_string) do
{:ok, ast} ->
issues = run_rules(ast, rules, Keyword.put(opts, :source, code_string))
%{valid: Enum.empty?(issues), issues: issues}
{:error, {line, error_msg, token}} ->
%{valid: false, issues: [parse_error_issue(line, error_msg, token)]}
end
end
@doc """
Auto-fixes all fixable issues in the given code string.
Pipes the source through each fixable rule's `fix/2` in sequence,
then re-analyzes to report any remaining (unfixable) issues.
"""
@spec fix(String.t(), keyword()) :: %{code: String.t(), issues: [Issue.t()]}
def fix(code_string, opts \\ []) do
rules = Keyword.get(opts, :rules, default_rules())
{fixable, _unfixable} = Enum.split_with(rules, & &1.fixable?())
fixed_code =
Enum.reduce(fixable, code_string, fn rule, source ->
rule.fix(source, opts)
end)
%{issues: remaining} = analyze(fixed_code, Keyword.put(opts, :source, fixed_code))
%{code: fixed_code, issues: remaining}
end
defp run_rules(ast, rules, opts) do
Enum.flat_map(rules, & &1.check(ast, opts))
end
defp default_rules do
Application.spec(:credence, :modules)
|> Enum.filter(fn module ->
Credence.Rule in Keyword.get(module.__info__(:attributes), :behaviour, [])
end)
end
defp parse_error_issue(line, error_msg, token) do
%Issue{
rule: :parse_error,
message: "Syntax error: #{error_msg} at token #{inspect(token)}",
meta: %{line: line}
}
end
end