Packages
ex_check_ng
1.0.0-rc.2
One task to efficiently run all code analysis & testing tools in an Elixir project. Community-maintained fork of ex_check.
Current section
Files
Jump to
Current section
Files
lib/ex_check/diagnostics/compiler.ex
defmodule ExCheck.Diagnostics.Compiler do
@moduledoc false
# Parses `mix compile` diagnostics. Handles the Elixir >= 1.16 block format (a
# `warning:`/`error:` headline followed by a `└─ file:line:col` trailer) and falls
# back to the legacy ` file.ex:line` location line for older output.
@behaviour ExCheck.Diagnostics
alias ExCheck.Diagnostics.Diagnostic
@anchor ~r/^[ \t]*(warning|error): (.+)$/
@location ~r/└─\s+([^\s:]+):(\d+)(?::(\d+))?/
@legacy ~r/^\s+([^\s:]+\.exs?):(\d+)(?::(\d+))?/m
@impl true
def parse(text) do
text
|> String.split("\n")
|> chunk_blocks()
|> Enum.map(&block_to_diagnostic/1)
end
defp chunk_blocks(lines) do
{blocks, current} =
Enum.reduce(lines, {[], nil}, fn line, {blocks, current} ->
case Regex.run(@anchor, line) do
[_, severity, message] ->
{prepend(blocks, current), {severity, message, []}}
_ ->
case current do
nil -> {blocks, nil}
{severity, message, acc} -> {blocks, {severity, message, [line | acc]}}
end
end
end)
blocks |> prepend(current) |> Enum.reverse()
end
defp prepend(blocks, nil), do: blocks
defp prepend(blocks, block), do: [block | blocks]
defp block_to_diagnostic({severity, message, acc}) do
{file, line, column} = acc |> Enum.reverse() |> Enum.join("\n") |> locate()
%Diagnostic{
file: file,
line: line,
column: column,
message: String.trim(message),
severity: severity(severity)
}
end
defp locate(text) do
cond do
match = Regex.run(@location, text) -> parse_loc(match)
match = Regex.run(@legacy, text) -> parse_loc(match)
true -> {nil, nil, nil}
end
end
defp parse_loc([_, file, line]), do: {file, to_int(line), nil}
defp parse_loc([_, file, line, ""]), do: {file, to_int(line), nil}
defp parse_loc([_, file, line, column]), do: {file, to_int(line), to_int(column)}
defp severity("warning"), do: :warning
defp severity(_), do: :error
defp to_int(str) do
case Integer.parse(str) do
{int, _} -> int
:error -> nil
end
end
end