formatter.ex (4106B)
1 defmodule Dialyxir.Formatter do 2 @moduledoc """ 3 Elixir-friendly dialyzer formatter. 4 5 Wrapper around normal Dialyzer warning messages that provides 6 example output for error messages. 7 """ 8 import Dialyxir.Output, only: [info: 1] 9 10 alias Dialyxir.FilterMap 11 12 @type warning() :: {tag :: term(), {file :: Path.t(), line :: pos_integer()}, {atom(), list()}} 13 14 @type t() :: module() 15 16 @callback format(warning()) :: String.t() 17 18 def formatted_time(duration_us) do 19 minutes = div(duration_us, 60_000_000) 20 seconds = (rem(duration_us, 60_000_000) / 1_000_000) |> Float.round(2) 21 "done in #{minutes}m#{seconds}s" 22 end 23 24 @spec format_and_filter([tuple], module, Keyword.t(), t()) :: tuple 25 def format_and_filter(warnings, filterer, filter_map_args, formatter) do 26 filter_map = filterer.filter_map(filter_map_args) 27 28 {filtered_warnings, filter_map} = filter_warnings(warnings, filterer, filter_map) 29 30 formatted_warnings = 31 filtered_warnings 32 |> filter_legacy_warnings(filterer) 33 |> Enum.map(&formatter.format/1) 34 |> Enum.uniq() 35 36 show_count_skipped(warnings, formatted_warnings, filter_map) 37 formatted_unnecessary_skips = format_unnecessary_skips(filter_map) 38 39 result(formatted_warnings, filter_map, formatted_unnecessary_skips) 40 end 41 42 defp result(formatted_warnings, filter_map, formatted_unnecessary_skips) do 43 cond do 44 FilterMap.unused_filters?(filter_map) && filter_map.unused_filters_as_errors? -> 45 {:error, formatted_warnings, {:unused_filters_present, formatted_unnecessary_skips}} 46 47 FilterMap.unused_filters?(filter_map) -> 48 {:warn, formatted_warnings, {:unused_filters_present, formatted_unnecessary_skips}} 49 50 true -> 51 {:ok, formatted_warnings, :no_unused_filters} 52 end 53 end 54 55 defp show_count_skipped(warnings, filtered_warnings, filter_map) do 56 warnings_count = Enum.count(warnings) 57 filtered_warnings_count = Enum.count(filtered_warnings) 58 skipped_count = warnings_count - filtered_warnings_count 59 unnecessary_skips_count = count_unnecessary_skips(filter_map) 60 61 info( 62 "Total errors: #{warnings_count}, Skipped: #{skipped_count}, " <> 63 "Unnecessary Skips: #{unnecessary_skips_count}" 64 ) 65 66 :ok 67 end 68 69 defp format_unnecessary_skips(filter_map = %FilterMap{list_unused_filters?: true}) do 70 unused_filters = FilterMap.unused_filters(filter_map) 71 72 if Enum.empty?(unused_filters) do 73 "" 74 else 75 unused_filters = Enum.map_join(unused_filters, "\n", &inspect/1) 76 "Unused filters:\n#{unused_filters}" 77 end 78 end 79 80 defp format_unnecessary_skips(_) do 81 "" 82 end 83 84 defp count_unnecessary_skips(filter_map) do 85 filter_map.counters 86 |> Enum.filter(&FilterMap.unused?/1) 87 |> Enum.count() 88 end 89 90 defp filter_warnings(warnings, filterer, filter_map) do 91 {warnings, filter_map} = 92 Enum.map_reduce(warnings, filter_map, &filter_warning(filterer, &1, &2)) 93 94 warnings = Enum.reject(warnings, &is_nil/1) 95 {warnings, filter_map} 96 end 97 98 defp filter_warning(filterer, warning = {_, {file, line}, {warning_type, _}}, filter_map) do 99 if Map.has_key?(Dialyxir.Warnings.warnings(), warning_type) do 100 {skip?, matching_filters} = 101 try do 102 filterer.filter_warning?( 103 {to_string(file), warning_type, line, Dialyxir.Formatter.Short.format(warning)}, 104 filter_map 105 ) 106 rescue 107 _ -> 108 {false, []} 109 catch 110 _ -> 111 {false, []} 112 end 113 114 filter_map = 115 Enum.reduce(matching_filters, filter_map, fn filter, filter_map -> 116 FilterMap.inc(filter_map, filter) 117 end) 118 119 if skip? do 120 {nil, filter_map} 121 else 122 {warning, filter_map} 123 end 124 else 125 {warning, filter_map} 126 end 127 end 128 129 defp filter_legacy_warnings(warnings, filterer) do 130 Enum.reject(warnings, fn warning -> 131 formatted_warnings = 132 warning 133 |> Dialyxir.Formatter.Dialyzer.format() 134 |> List.wrap() 135 136 Enum.empty?(filterer.filter_legacy_warnings(formatted_warnings)) 137 end) 138 end 139 end