Packages
credo
1.0.5
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.7-rc.0
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.2-rc.4
1.7.2-rc.3
1.7.2-rc.2
1.7.2-rc.1
1.7.2-rc.0
1.7.1
1.7.0
1.7.0-rc.2
1.7.0-rc.1
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.4.1
1.4.0
1.4.0-rc.2
1.4.0-rc.1
1.3.2
1.3.1
1.3.0
1.3.0-rc3
1.3.0-rc2
1.3.0-rc1
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc4
1.2.0-rc3
1.2.0-rc2
1.2.0-rc1
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.1.0-rc3
1.1.0-rc2
1.1.0-rc1
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.1-rc1
1.0.0
1.0.0-rc1
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.9.0-rc8
0.9.0-rc7
0.9.0-rc6
0.9.0-rc5
0.9.0-rc4
0.9.0-rc3
0.9.0-rc2
0.9.0-rc1
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.8.0-rc7
0.8.0-rc6
0.8.0-rc5
0.8.0-rc4
0.8.0-rc3
0.8.0-rc2
0.8.0-rc1
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.6.0-rc2
0.6.0-rc1
0.5.3
0.5.2
0.5.1
0.5.0
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.10-dev
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-beta5
0.4.0-beta4
0.4.0-beta3
0.4.0-beta2
0.4.0-beta1
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.3.0-dev2
0.3.0-dev
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.1-dev
A static code analysis tool with a focus on code consistency and teaching.
Current section
Files
Jump to
Current section
Files
lib/credo/check.ex
defmodule Credo.Check do
@moduledoc """
`Check` modules represent the checks which are run during Credo's analysis.
Example:
defmodule MyCheck do
use Credo.Check, category: :warning, base_priority: :high
def run(source_file, params) do
#
end
end
The `run/2` function takes two parameters: a source file and a list of parameters for the check.
It has to return a list of found issues.
"""
@type t :: module
@doc """
Returns the base priority for the check.
"""
@callback base_priority() :: integer
@doc """
Returns the category for the check.
"""
@callback category() :: atom
# @callback run(source_file :: Credo.SourceFile.t, params :: Keyword.t) :: List.t
@callback run_on_all?() :: boolean
@callback explanation() :: String.t()
@callback explanation_for_params() :: Keyword.t()
@callback format_issue(issue_meta :: IssueMeta, opts :: Keyword.t()) :: Issue.t()
@base_category_exit_status_map %{
consistency: 1,
design: 2,
readability: 4,
refactor: 8,
warning: 16
}
alias Credo.Check
alias Credo.Code.Scope
alias Credo.Issue
alias Credo.IssueMeta
alias Credo.Priority
alias Credo.Service.SourceFileScopes
alias Credo.Severity
alias Credo.SourceFile
@doc false
defmacro __using__(opts) do
quote do
@behaviour Credo.Check
@before_compile Credo.Check
alias Credo.Check
alias Credo.Check.CodeHelper
alias Credo.Check.Params
alias Credo.CLI.ExitStatus
alias Credo.Issue
alias Credo.IssueMeta
alias Credo.Priority
alias Credo.Severity
alias Credo.SourceFile
def base_priority do
unquote(Priority.to_integer(opts[:base_priority]))
end
def category do
unquote(category_body(opts[:category]) || :unknown)
end
def elixir_version do
unquote(opts[:elixir_version] || ">= 0.0.1")
end
def run_on_all? do
unquote(run_on_all_body(opts[:run_on_all]))
end
def explanation do
Check.explanation_for(@explanation, :check)
end
def explanation_for_params do
Check.explanation_for(@explanation, :params) || []
end
def format_issue(issue_meta, opts) do
Check.format_issue(
issue_meta,
opts,
category(),
base_priority(),
__MODULE__
)
end
end
end
@doc false
defmacro __before_compile__(env) do
quote do
unquote(default_params_module_attribute(env))
def params_defaults do
@default_params
end
def params_names do
Keyword.keys(params_defaults())
end
end
end
defp default_params_module_attribute(env) do
if env.module |> Module.get_attribute(:default_params) |> is_nil() do
quote do
@default_params []
end
end
end
def explanation_for(nil, _), do: nil
def explanation_for(keywords, key), do: keywords[key]
@doc """
format_issue takes an issue_meta and returns an issue.
The resulting issue can be made more explicit by passing the following
options to `format_issue/2`:
- `:priority` Sets the issue's priority.
- `:trigger` Sets the issue's trigger.
- `:line_no` Sets the issue's line number.
Tries to find `column` if `:trigger` is supplied.
- `:column` Sets the issue's column.
- `:exit_status` Sets the issue's exit_status.
- `:severity` Sets the issue's severity.
"""
def format_issue(issue_meta, opts, issue_category, issue_base_priority, check) do
source_file = IssueMeta.source_file(issue_meta)
params = IssueMeta.params(issue_meta)
priority =
case params[:priority] do
nil -> issue_base_priority
val -> Priority.to_integer(val)
end
exit_status =
case params[:exit_status] do
nil -> Check.to_exit_status(issue_category)
val -> Check.to_exit_status(val)
end
line_no = opts[:line_no]
trigger = opts[:trigger]
column = opts[:column]
severity = opts[:severity] || Severity.default_value()
%Issue{
priority: priority,
filename: source_file.filename,
message: opts[:message],
trigger: trigger,
line_no: line_no,
column: column,
severity: severity,
exit_status: exit_status
}
|> add_line_no_options(line_no, source_file)
|> add_column_if_missing(trigger, line_no, column, source_file)
|> add_check_and_category(check, issue_category)
end
defp add_check_and_category(issue, check, issue_category) do
%Issue{
issue
| check: check,
category: issue_category
}
end
defp add_column_if_missing(issue, trigger, line_no, column, source_file) do
if trigger && line_no && !column do
%Issue{
issue
| column: SourceFile.column(source_file, line_no, trigger)
}
else
issue
end
end
defp add_line_no_options(issue, line_no, source_file) do
if line_no do
{_def, scope} = scope_for(source_file, line: line_no)
%Issue{
issue
| priority: issue.priority + priority_for(source_file, scope),
scope: scope
}
else
issue
end
end
# Returns the scope for the given line as a tuple consisting of the call to
# define the scope (`:defmodule`, `:def`, `:defp` or `:defmacro`) and the
# name of the scope.
#
# Examples:
#
# {:defmodule, "Foo.Bar"}
# {:def, "Foo.Bar.baz"}
defp scope_for(source_file, line: line_no) do
source_file
|> scope_list
|> Enum.at(line_no - 1)
end
# Returns all scopes for the given source_file per line of source code as tuple
# consisting of the call to define the scope
# (`:defmodule`, `:def`, `:defp` or `:defmacro`) and the name of the scope.
#
# Examples:
#
# [
# {:defmodule, "Foo.Bar"},
# {:def, "Foo.Bar.baz"},
# {:def, "Foo.Bar.baz"},
# {:def, "Foo.Bar.baz"},
# {:def, "Foo.Bar.baz"},
# {:defmodule, "Foo.Bar"}
# ]
defp scope_list(%SourceFile{filename: filename} = source_file) do
case SourceFileScopes.get(filename) do
{:ok, value} ->
value
:notfound ->
ast = SourceFile.ast(source_file)
lines = SourceFile.lines(source_file)
result =
Enum.map(lines, fn {line_no, _} ->
Scope.name(ast, line: line_no)
end)
SourceFileScopes.put(filename, result)
result
end
end
defp priority_for(source_file, scope) do
scope_prio_map = Priority.scope_priorities(source_file)
scope_prio_map[scope] || 0
end
defp category_body(nil) do
quote do
name =
__MODULE__
|> Module.split()
|> Enum.at(2)
safe_name = name || :unknown
safe_name
|> to_string
|> String.downcase()
|> String.to_atom()
end
end
defp category_body(value), do: value
@doc "Converts a given category to an exit status"
def to_exit_status(nil), do: 0
def to_exit_status(atom) when is_atom(atom) do
to_exit_status(@base_category_exit_status_map[atom])
end
def to_exit_status(value), do: value
defp run_on_all_body(true), do: true
defp run_on_all_body(_), do: false
end