Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds a github formatter #463

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 10 additions & 7 deletions lib/dialyxir/dialyzer.ex
Expand Up @@ -20,25 +20,28 @@ defmodule Dialyxir.Dialyzer do
formatter =
cond do
split[:format] == "dialyzer" ->
:dialyzer
Dialyxir.Formatter.Dialyzer

split[:format] == "dialyxir" ->
:dialyxir
Dialyxir.Formatter.Dialyxir

split[:format] == "github" ->
Dialyxir.Formatter.Github

split[:format] == "ignore_file" ->
:ignore_file
Dialyxir.Formatter.IgnoreFile

split[:format] == "raw" ->
:raw
Dialyxir.Formatter.Raw

split[:format] == "short" ->
:short
Dialyxir.Formatter.Short

split[:raw] ->
:raw
Dialyxir.Formatter.Raw

true ->
:dialyxir
Dialyxir.Formatter.Dialyxir
end

info("Starting Dialyzer")
Expand Down
132 changes: 10 additions & 122 deletions lib/dialyxir/formatter.ex
Expand Up @@ -9,13 +9,19 @@ defmodule Dialyxir.Formatter do

alias Dialyxir.FilterMap

@type warning() :: {tag :: term(), {file :: Path.t(), line :: pos_integer()}, {atom(), list()}}

@type t() :: module()

@callback format(warning()) :: String.t()

def formatted_time(duration_us) do
minutes = div(duration_us, 60_000_000)
seconds = (rem(duration_us, 60_000_000) / 1_000_000) |> Float.round(2)
"done in #{minutes}m#{seconds}s"
end

@spec format_and_filter([tuple], module, Keyword.t(), atom) :: tuple
@spec format_and_filter([tuple], module, Keyword.t(), t()) :: tuple
def format_and_filter(warnings, filterer, filter_map_args, formatter) do
filter_map = filterer.filter_map(filter_map_args)

Expand All @@ -24,7 +30,7 @@ defmodule Dialyxir.Formatter do
formatted_warnings =
filtered_warnings
|> filter_legacy_warnings(filterer)
|> Enum.map(&format_warning(&1, formatter))
|> Enum.map(&formatter.format/1)
|> Enum.uniq()

show_count_skipped(warnings, formatted_warnings, filter_map)
Expand All @@ -46,114 +52,6 @@ defmodule Dialyxir.Formatter do
end
end

defp format_warning(warning, :raw) do
inspect(warning, limit: :infinity)
end

defp format_warning(warning, :dialyzer) do
# OTP 22 uses indented output, but that's incompatible with dialyzer.ignore-warnings format.
# Can be disabled, but OTP 21 and older only accept an atom, so only disable on OTP 22+.
opts =
if String.to_integer(System.otp_release()) < 22,
do: :fullpath,
else: [{:filename_opt, :fullpath}, {:indent_opt, false}]

warning
|> :dialyzer.format_warning(opts)
|> String.Chars.to_string()
|> String.replace_trailing("\n", "")
end

defp format_warning({_tag, {file, line}, message}, :short) do
{warning_name, arguments} = message
base_name = Path.relative_to_cwd(file)

warning = warning(warning_name)
string = warning.format_short(arguments)

"#{base_name}:#{line}:#{warning_name} #{string}"
end

defp format_warning({_tag, {file, _line}, {warning_name, _arguments}}, :ignore_file) do
~s({"#{file}", :#{warning_name}},)
end

defp format_warning(dialyzer_warning = {_tag, {file, line}, message}, :dialyxir) do
{warning_name, arguments} = message
base_name = Path.relative_to_cwd(file)

formatted =
try do
warning = warning(warning_name)
string = warning.format_long(arguments)

"""
#{base_name}:#{line}:#{warning_name}
#{string}
"""
rescue
e ->
message = """
Unknown error occurred: #{inspect(e)}
"""

wrap_error_message(message, dialyzer_warning)
catch
{:error, :unknown_warning, warning_name} ->
message = """
Unknown warning:
#{inspect(warning_name)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :lexing, warning} ->
message = """
Failed to lex warning:
#{inspect(warning)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :parsing, failing_string} ->
message = """
Failed to parse warning:
#{inspect(failing_string)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :pretty_printing, failing_string} ->
message = """
Failed to pretty print warning:
#{inspect(failing_string)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :formatting, code} ->
message = """
Failed to format warning:
#{inspect(code)}
"""

wrap_error_message(message, dialyzer_warning)
end

formatted <> String.duplicate("_", 80)
end

defp wrap_error_message(message, warning) do
"""
Please file a bug in https://github.com/jeremyjh/dialyxir/issues with this message.

#{message}

Legacy warning:
#{format_warning(warning, :dialyzer)}
"""
end

defp show_count_skipped(warnings, filtered_warnings, filter_map) do
warnings_count = Enum.count(warnings)
filtered_warnings_count = Enum.count(filtered_warnings)
Expand Down Expand Up @@ -189,16 +87,6 @@ defmodule Dialyxir.Formatter do
|> Enum.count()
end

defp warning(warning_name) do
warnings = Dialyxir.Warnings.warnings()

if Map.has_key?(warnings, warning_name) do
Map.get(warnings, warning_name)
else
throw({:error, :unknown_warning, warning_name})
end
end

defp filter_warnings(warnings, filterer, filter_map) do
{warnings, filter_map} =
Enum.map_reduce(warnings, filter_map, &filter_warning(filterer, &1, &2))
Expand All @@ -212,7 +100,7 @@ defmodule Dialyxir.Formatter do
{skip?, matching_filters} =
try do
filterer.filter_warning?(
{to_string(file), warning_type, line, format_warning(warning, :short)},
{to_string(file), warning_type, line, Dialyxir.Formatter.Short.format(warning)},
filter_map
)
rescue
Expand Down Expand Up @@ -242,7 +130,7 @@ defmodule Dialyxir.Formatter do
Enum.reject(warnings, fn warning ->
formatted_warnings =
warning
|> format_warning(:dialyzer)
|> Dialyxir.Formatter.Dialyzer.format()
|> List.wrap()

Enum.empty?(filterer.filter_legacy_warnings(formatted_warnings))
Expand Down
92 changes: 92 additions & 0 deletions lib/dialyxir/formatter/dialyxir.ex
@@ -0,0 +1,92 @@
defmodule Dialyxir.Formatter.Dialyxir do
@moduledoc false

@behaviour Dialyxir.Formatter

@impl Dialyxir.Formatter
def format(dialyzer_warning = {_tag, {file, line}, message}) do
{warning_name, arguments} = message
base_name = Path.relative_to_cwd(file)

formatted =
try do
warning = warning(warning_name)
string = warning.format_long(arguments)

"""
#{base_name}:#{line}:#{warning_name}
#{string}
"""
rescue
e ->
message = """
Unknown error occurred: #{inspect(e)}
"""

wrap_error_message(message, dialyzer_warning)
catch
{:error, :unknown_warning, warning_name} ->
message = """
Unknown warning:
#{inspect(warning_name)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :lexing, warning} ->
message = """
Failed to lex warning:
#{inspect(warning)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :parsing, failing_string} ->
message = """
Failed to parse warning:
#{inspect(failing_string)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :pretty_printing, failing_string} ->
message = """
Failed to pretty print warning:
#{inspect(failing_string)}
"""

wrap_error_message(message, dialyzer_warning)

{:error, :formatting, code} ->
message = """
Failed to format warning:
#{inspect(code)}
"""

wrap_error_message(message, dialyzer_warning)
end

formatted <> String.duplicate("_", 80)
end

defp wrap_error_message(message, warning) do
"""
Please file a bug in https://github.com/jeremyjh/dialyxir/issues with this message.

#{message}

Legacy warning:
#{Dialyxir.Formatter.Dialyzer.format(warning)}
"""
end

defp warning(warning_name) do
warnings = Dialyxir.Warnings.warnings()

if Map.has_key?(warnings, warning_name) do
Map.get(warnings, warning_name)
else
throw({:error, :unknown_warning, warning_name})
end
end
end
20 changes: 20 additions & 0 deletions lib/dialyxir/formatter/dialyzer.ex
@@ -0,0 +1,20 @@
defmodule Dialyxir.Formatter.Dialyzer do
@moduledoc false

@behaviour Dialyxir.Formatter

@impl Dialyxir.Formatter
def format(warning) do
# OTP 22 uses indented output, but that's incompatible with dialyzer.ignore-warnings format.
# Can be disabled, but OTP 21 and older only accept an atom, so only disable on OTP 22+.
opts =
if String.to_integer(System.otp_release()) < 22,
do: :fullpath,
else: [{:filename_opt, :fullpath}, {:indent_opt, false}]

warning
|> :dialyzer.format_warning(opts)
|> String.Chars.to_string()
|> String.replace_trailing("\n", "")
end
end
15 changes: 15 additions & 0 deletions lib/dialyxir/formatter/github.ex
@@ -0,0 +1,15 @@
defmodule Dialyxir.Formatter.Github do
@moduledoc false

@behaviour Dialyxir.Formatter

@impl Dialyxir.Formatter
def format({_tag, {file, line}, {warning_name, _arguments}}) do
base_name = Path.relative_to_cwd(file)

warning = warning(warning_name)
string = warning.format_short(arguments)

"::error file=#{base_name},line=#{line},title=#{warning_name}::#{string}"
jeremyjh marked this conversation as resolved.
Show resolved Hide resolved
end
end
10 changes: 10 additions & 0 deletions lib/dialyxir/formatter/ignore_file.ex
@@ -0,0 +1,10 @@
defmodule Dialyxir.Formatter.IgnoreFile do
@moduledoc false

@behaviour Dialyxir.Formatter

@impl Dialyxir.Formatter
def format({_tag, {file, _line}, {warning_name, _arguments}}) do
~s({"#{file}", :#{warning_name}},)
end
end
10 changes: 10 additions & 0 deletions lib/dialyxir/formatter/raw.ex
@@ -0,0 +1,10 @@
defmodule Dialyxir.Formatter.Raw do
@moduledoc false

@behaviour Dialyxir.Formatter

@impl Dialyxir.Formatter
def format(warning) do
inspect(warning, limit: :infinity)
end
end
25 changes: 25 additions & 0 deletions lib/dialyxir/formatter/short.ex
@@ -0,0 +1,25 @@
defmodule Dialyxir.Formatter.Short do
@moduledoc false

@behaviour Dialyxir.Formatter

@impl Dialyxir.Formatter
def format({_tag, {file, line}, {warning_name, arguments}}) do
base_name = Path.relative_to_cwd(file)

warning = warning(warning_name)
string = warning.format_short(arguments)

"#{base_name}:#{line}:#{warning_name} #{string}"
end

defp warning(warning_name) do
warnings = Dialyxir.Warnings.warnings()

if Map.has_key?(warnings, warning_name) do
Map.get(warnings, warning_name)
else
throw({:error, :unknown_warning, warning_name})
end
end
end