11defmodule Gradient.ElixirFmt do
22 @ moduledoc """
3- Module that handles formatting and printing error messages produced by Gradient in Elixir.
3+ Module that handles formatting and printing error messages produced by Gradualizer in Elixir.
4+
5+ Options:
6+ - `ex_colors`: list of color options:
7+ - {`use_colors`, boolean()}: - wheather to use the colors, default: true
8+ - {`expression`, ansicode()}: color of the expressions, default: :yellow
9+ - {`type`, ansicode()}: color of the types, default: :cyan
10+ - {`underscored_line`, ansicode()}: color of the underscored line pointed the error in code, default: :red
11+
12+ - `ex_fmt_expr_fun`: function to pretty print an expression AST in Elixir `(abstract_expr()) -> iodata()`.
13+
14+ - `ex_fmt_type_fun`: function to pretty print an type AST in Elixir `(abstract_type() -> iodata())`.
15+
16+ - Gradualizer options, but some of them are overwritten by Gradient.
417 """
518 @ behaviour Gradient.Fmt
619
720 alias :gradualizer_fmt , as: FmtLib
821 alias Gradient.ElixirType
922 alias Gradient.ElixirExpr
23+ alias Gradient.Types
24+
25+ @ type colors_opts ( ) :: [
26+ use_colors: boolean ( ) ,
27+ expression: IO.ANSI . ansicode ( ) ,
28+ type: IO.ANSI . ansicode ( ) ,
29+ underscored_line: IO.ANSI . ansicode ( )
30+ ]
31+ @ type options ( ) :: [
32+ ex_colors: colors_opts ( ) ,
33+ ex_fmt_type_fun: ( Types . abstract_type ( ) -> iodata ( ) ) ,
34+ ex_fmt_expr_fun: ( Types . abstract_expr ( ) -> iodata ( ) )
35+ ]
36+
37+ @ default_colors [ use_colors: true , expression: :yellow , type: :cyan , underscored_line: :red ]
1038
1139 def print_errors ( errors , opts ) do
1240 for { file , e } <- errors do
@@ -29,8 +57,9 @@ defmodule Gradient.ElixirFmt do
2957 end
3058
3159 def format_error ( error , opts ) do
32- opts = Keyword . put_new ( opts , :fmt_type_fun , & ElixirType . pretty_print / 1 )
33- opts = Keyword . put_new ( opts , :fmt_expr_fun , & ElixirExpr . pp_expr / 1 )
60+ opts = Keyword . put_new ( opts , :color , false )
61+ opts = Keyword . put_new ( opts , :fmt_type_fun , pp_type_fun ( opts ) )
62+ opts = Keyword . put_new ( opts , :fmt_expr_fun , pp_expr_fun ( opts ) )
3463 format_type_error ( error , opts )
3564 end
3665
@@ -120,7 +149,7 @@ defmodule Gradient.ElixirFmt do
120149 def format_expr_type_error ( expression , actual_type , expected_type , opts ) do
121150 { inline_expr , fancy_expr } =
122151 case try_highlight_in_context ( expression , opts ) do
123- { :error , _e } -> { " " <> pp_expr ( expression , opts ) , "" }
152+ { :error , _e } -> { [ " " | pp_expr ( expression , opts ) ] , "" }
124153 { :ok , fancy } -> { "" , fancy }
125154 end
126155
@@ -145,34 +174,56 @@ defmodule Gradient.ElixirFmt do
145174 end
146175 end
147176
148- def pp_expr ( expression , opts ) do
149- fmt = Keyword . get ( opts , :fmt_expr_fun , & ElixirExpr . pp_expr / 1 )
177+ def pp_expr_fun ( opts ) do
178+ fmt = Keyword . get ( opts , :ex_fmt_expr_fun , & ElixirExpr . pp_expr_format / 1 )
179+ colors = get_colors_with_default ( opts )
180+ { :ok , use_colors } = Keyword . fetch ( colors , :use_colors )
181+ { :ok , expr_color } = Keyword . fetch ( colors , :expression )
150182
151- if Keyword . get ( opts , :colors , true ) do
152- IO.ANSI . blue ( ) <> fmt . ( expression ) <> IO.ANSI . reset ( )
153- else
154- fmt . ( expression )
183+ fn expression ->
184+ IO.ANSI . format ( [ expr_color , fmt . ( expression ) ] , use_colors )
155185 end
156186 end
157187
158- def pp_type ( type , opts ) do
159- fmt = Keyword . get ( opts , :fmt_type_fun , & ElixirType . pretty_print / 1 )
188+ def pp_type_fun ( opts ) do
189+ fmt = Keyword . get ( opts , :ex_fmt_type_fun , & ElixirType . pp_type_format / 1 )
190+ colors = get_colors_with_default ( opts )
191+ { :ok , use_colors } = Keyword . fetch ( colors , :use_colors )
192+ { :ok , type_color } = Keyword . fetch ( colors , :type )
160193
161- if Keyword . get ( opts , :colors , true ) do
162- IO.ANSI . cyan ( ) <> fmt . ( type ) <> IO.ANSI . reset ( )
163- else
164- fmt . ( type )
194+ fn type ->
195+ IO.ANSI . format ( [ type_color , fmt . ( type ) ] , use_colors )
165196 end
166197 end
167198
199+ def get_colors_with_default ( opts ) do
200+ case Keyword . fetch ( opts , :ex_colors ) do
201+ { :ok , colors } ->
202+ colors ++ @ default_colors
203+
204+ _ ->
205+ @ default_colors
206+ end
207+ end
208+
209+ def pp_expr ( expression , opts ) do
210+ pp_expr_fun ( opts ) . ( expression )
211+ end
212+
213+ def pp_type ( type , opts ) do
214+ pp_type_fun ( opts ) . ( type )
215+ end
216+
217+ @ spec try_highlight_in_context ( Types . abstract_expr ( ) , options ( ) ) ::
218+ { :ok , iodata ( ) } | { :error , term ( ) }
168219 def try_highlight_in_context ( expression , opts ) do
169220 forms = Keyword . get ( opts , :forms )
170221
171222 with :ok <- has_location? ( expression ) ,
172223 { :ok , path } <- get_ex_file_path ( forms ) ,
173224 { :ok , code } <- File . read ( path ) do
174225 code_lines = String . split ( code , ~r/ \R / )
175- { :ok , highlight_in_context ( expression , code_lines ) }
226+ { :ok , highlight_in_context ( expression , code_lines , opts ) }
176227 end
177228 end
178229
@@ -184,14 +235,14 @@ defmodule Gradient.ElixirFmt do
184235 end
185236 end
186237
187- @ spec highlight_in_context ( tuple ( ) , [ String . t ( ) ] ) :: String . t ( )
188- def highlight_in_context ( expression , context ) do
238+ @ spec highlight_in_context ( tuple ( ) , [ String . t ( ) ] , options ( ) ) :: iodata ( )
239+ def highlight_in_context ( expression , context , opts ) do
189240 line = elem ( expression , 1 )
190241
191242 context
192243 |> Enum . with_index ( 1 )
193244 |> filter_context ( line , 2 )
194- |> underscore_line ( line )
245+ |> underscore_line ( line , opts )
195246 |> Enum . join ( "\n " )
196247 end
197248
@@ -202,10 +253,19 @@ defmodule Gradient.ElixirFmt do
202253 Enum . filter ( lines , fn { _ , number } -> number in range end )
203254 end
204255
205- def underscore_line ( lines , line ) do
256+ def underscore_line ( lines , line , opts ) do
206257 Enum . map ( lines , fn { str , n } ->
207258 if ( n == line ) do
208- IO.ANSI . underline ( ) <> IO.ANSI . red ( ) <> to_string ( n ) <> " " <> str <> IO.ANSI . reset ( )
259+ colors = get_colors_with_default ( opts )
260+ { :ok , use_colors } = Keyword . fetch ( colors , :use_colors )
261+ { :ok , color } = Keyword . fetch ( colors , :underscored_line )
262+ line_str = to_string ( n ) <> " " <> str
263+
264+ [
265+ IO.ANSI . underline ( ) ,
266+ IO.ANSI . format_fragment ( [ color , line_str ] , use_colors ) ,
267+ IO.ANSI . reset ( )
268+ ]
209269 else
210270 to_string ( n ) <> " " <> str
211271 end
@@ -225,11 +285,6 @@ defmodule Gradient.ElixirFmt do
225285 end
226286 end
227287
228- # defp warning_error_not_handled(error) do
229- # msg = "\nElixir formatter not exist for #{inspect(error, pretty: true)} using default \n"
230- # String.to_charlist(IO.ANSI.light_yellow() <> msg <> IO.ANSI.reset())
231- # end
232-
233288 @ spec describe_expr ( :gradualizer_type . abstract_expr ( ) ) :: binary ( )
234289 def describe_expr ( { :atom , _ , _ } ) , do: "atom"
235290 def describe_expr ( { :bc , _ , _ , _ } ) , do: "binary comprehension"
0 commit comments