Current section
Files
Jump to
Current section
Files
lib/reach/check/finding.ex
defmodule Reach.Check.Finding do
@moduledoc false
@derive JSON.Encoder
@enforce_keys [:source, :kind, :fingerprint, :message]
defstruct [:source, :kind, :fingerprint, :message, :file, :line]
alias Reach.Smell.Finding, as: SmellFinding
def from_arch_violation(violation) do
data = Map.from_struct(violation)
file = Map.get(data, :file)
line = Map.get(data, :line)
kind = Map.get(data, :type, :architecture)
message = arch_message(data)
%__MODULE__{
source: :arch,
kind: kind,
file: file,
line: line,
message: message,
fingerprint: fingerprint(:arch, kind, file, line, message)
}
end
def from_smell(%SmellFinding{} = finding) do
{file, line} = location_parts(finding.location)
%__MODULE__{
source: :smells,
kind: finding.kind,
file: file,
line: line,
message: finding.message,
fingerprint: fingerprint(:smells, finding.kind, nil, nil, finding.message)
}
end
defp location_parts(%{file: file, line: line}), do: {file, line}
defp location_parts(%{file: file, start_line: line}), do: {file, line}
defp location_parts(location) when is_binary(location) do
case Regex.run(~r/^(.*):(\d+)$/, location) do
[_match, file, line] -> {file, String.to_integer(line)}
_ -> {location, nil}
end
end
defp location_parts(_location), do: {nil, nil}
defp arch_message(%{type: :layer_cycle, layers: layers}) when is_list(layers) do
"layer cycle: #{Enum.join(closed_cycle(layers), " -> ")}"
end
defp arch_message(%{type: :config_error, key: key, message: message}) do
"config #{key}: #{message}"
end
defp arch_message(%{type: :forbidden_dependency} = data) do
"#{data.caller_layer} -> #{data.callee_layer}: #{data.call}"
end
defp arch_message(%{type: :forbidden_call} = data) do
"#{data.caller_module} calls #{data.call}"
end
defp arch_message(%{type: :missing_layer, module: module}) do
"missing layer: #{module}"
end
defp arch_message(%{type: :multiple_layers, module: module, matched_layers: layers}) do
"matched multiple layers: #{module} (#{Enum.join(List.wrap(layers), ", ")})"
end
defp arch_message(%{type: type, module: module}) when not is_nil(module) do
"#{type}: #{module}"
end
defp arch_message(%{type: type}), do: to_string(type)
defp closed_cycle([]), do: []
defp closed_cycle([first | _] = layers), do: layers ++ [first]
defp fingerprint(source, kind, file, line, message) do
input = [to_string(source), to_string(kind), to_string(file), to_string(line), message]
["sha256:", Base.encode16(:crypto.hash(:sha256, Enum.join(input, "\0")), case: :lower)]
|> IO.iodata_to_binary()
end
end