Skip to content

Commit e35fd23

Browse files
authored
Merge pull request #43 from esl/improve-errors-formatting
Make ElixirFmt colors configurable and improve formatting
2 parents d7eb581 + 2e6eea3 commit e35fd23

File tree

8 files changed

+149
-84
lines changed

8 files changed

+149
-84
lines changed

lib/gradient/elixir_expr.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Gradient.ElixirExpr do
1111
@doc """
1212
Convert abstract expressions to Elixir code and format output with formatter.
1313
"""
14-
@spec pp_expr_format([expr()], keyword()) :: iodata()
14+
@spec pp_expr_format(expr() | [expr()], keyword()) :: iodata()
1515
def pp_expr_format(exprs, fmt_opts \\ []) do
1616
exprs
1717
|> pp_expr()
@@ -35,7 +35,7 @@ defmodule Gradient.ElixirExpr do
3535
def pp_expr({:atom, _, val}) do
3636
case Atom.to_string(val) do
3737
"Elixir." <> mod -> mod
38-
str -> ":" <> str
38+
str -> ":\"" <> str <> "\""
3939
end
4040
end
4141

@@ -420,7 +420,7 @@ defmodule Gradient.ElixirExpr do
420420

421421
if shortand_syntax do
422422
{:atom, _, key} = key
423-
Atom.to_string(key) <> ": " <> value
423+
"\"" <> Atom.to_string(key) <> "\": " <> value
424424
else
425425
pp_expr(key) <> " => " <> value
426426
end

lib/gradient/elixir_fmt.ex

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
11
defmodule 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"

lib/gradient/elixir_type.ex

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
defmodule Gradient.ElixirType do
22
@moduledoc """
3-
Module to format types.
4-
5-
Seems that:
6-
- record type
7-
- constrained function type
8-
are not used by Elixir so the pp support has not been added.
3+
Convert the Erlang abstract types to the Elixir code.
94
"""
105

116
alias Gradient.ElixirFmt
127

138
@type abstract_type() :: Gradient.Types.abstract_type()
149

10+
@doc """
11+
Convert abstract type to Elixir code and format output with formatter.
12+
"""
13+
@spec pp_type_format(abstract_type(), keyword()) :: iodata()
14+
def pp_type_format(type, fmt_opts \\ []) do
15+
type
16+
|> pretty_print()
17+
|> Code.format_string!(fmt_opts)
18+
end
19+
1520
@doc """
1621
Take type and prepare a pretty string representation.
1722
"""
@@ -84,7 +89,10 @@ defmodule Gradient.ElixirType do
8489
end
8590

8691
def pretty_print({:atom, _, val}) do
87-
":" <> Atom.to_string(val)
92+
case Atom.to_string(val) do
93+
"Elixir." <> mod -> mod
94+
str -> ":\"" <> str <> "\""
95+
end
8896
end
8997

9098
def pretty_print({:integer, _, val}) do

lib/gradient/types.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule Gradient.Types do
22
@type token :: tuple()
33
@type tokens :: [tuple()]
44
@type abstract_type :: :erl_parse.abstract_type()
5+
@type abstract_expr :: :erl_parse.abstract_expr()
56
@type form ::
67
:erl_parse.abstract_clause()
78
| :erl_parse.abstract_expr()

test/gradient/elixir_expr_test.exs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ defmodule Gradient.ElixirExprTest do
4545
end
4646
|> ElixirExpr.pp_expr()
4747

48-
assert "fn {:ok, v} -> v; {:error, _} -> :error end" == actual
48+
assert ~s(fn {:"ok", v} -> v; {:"error", _} -> :"error" end) == actual
4949
end
5050

5151
test "binary comprehension" do
@@ -79,7 +79,7 @@ defmodule Gradient.ElixirExprTest do
7979
end
8080
|> ElixirExpr.pp_expr()
8181

82-
assert "receive do {:hello, msg} -> msg end" == actual
82+
assert ~s(receive do {:"hello", msg} -> msg end) == actual
8383
end
8484

8585
test "receive after" do
@@ -93,7 +93,7 @@ defmodule Gradient.ElixirExprTest do
9393
end
9494
|> ElixirExpr.pp_expr()
9595

96-
assert ~s(receive do {:hello, msg} -> msg after 1000 -> "nothing happened" end) == actual
96+
assert ~s(receive do {:"hello", msg} -> msg after 1000 -> "nothing happened" end) == actual
9797
end
9898

9999
test "call pipe" do
@@ -123,7 +123,7 @@ defmodule Gradient.ElixirExprTest do
123123
end
124124
|> ElixirExpr.pp_expr()
125125

126-
assert "map = %{a: 12, b: 0}; case :maps.find(:a, map) do {:ok, a} -> case :maps.find(:b, map) do {:ok, b} -> a + b; _gen -> case _gen do :error -> 0; _gen -> raise {:with_clause, _gen} end end; _gen -> case _gen do :error -> 0; _gen -> raise {:with_clause, _gen} end end" ==
126+
assert ~s(map = %{"a": 12, "b": 0}; case :maps.find(:"a", map\) do {:"ok", a} -> case :maps.find(:"b", map\) do {:"ok", b} -> a + b; _gen -> case _gen do :"error" -> 0; _gen -> raise {:"with_clause", _gen} end end; _gen -> case _gen do :"error" -> 0; _gen -> raise {:"with_clause", _gen} end end) ==
127127
actual
128128
end
129129

@@ -140,7 +140,7 @@ defmodule Gradient.ElixirExprTest do
140140
end
141141
|> ElixirExpr.pp_expr()
142142

143-
assert ~s(try do raise "ok"; catch :error, e -> IO.puts(Exception.format(:error, e, __STACKTRACE__\)\); reraise e, __STACKTRACE__ end) ==
143+
assert ~s(try do raise "ok"; catch :"error", e -> IO.puts(Exception.format(:"error", e, __STACKTRACE__\)\); reraise e, __STACKTRACE__ end) ==
144144
actual
145145
end
146146

@@ -155,7 +155,7 @@ defmodule Gradient.ElixirExprTest do
155155
end
156156
|> ElixirExpr.pp_expr()
157157

158-
assert ~s(try do raise "oops"; catch :error, %RuntimeError{} = _ -> "Error!" end) ==
158+
assert ~s(try do raise "oops"; catch :"error", %RuntimeError{} = _ -> "Error!" end) ==
159159
actual
160160
end
161161

@@ -170,7 +170,7 @@ defmodule Gradient.ElixirExprTest do
170170
end
171171
|> ElixirExpr.pp_expr()
172172

173-
assert "try do :ok; catch :error, _ -> :ok end" == actual
173+
assert ~s(try do :"ok"; catch :"error", _ -> :"ok" end) == actual
174174
end
175175

176176
test "simple after try" do
@@ -184,7 +184,7 @@ defmodule Gradient.ElixirExprTest do
184184
end
185185
|> ElixirExpr.pp_expr()
186186

187-
assert "try do :ok; after :ok end" == actual
187+
assert ~s(try do :"ok"; after :"ok" end) == actual
188188
end
189189

190190
test "try guard" do
@@ -215,7 +215,7 @@ defmodule Gradient.ElixirExprTest do
215215
end
216216
|> ElixirExpr.pp_expr()
217217

218-
assert ~s(try do throw "good"; :ok; else v when v == :ok -> :ok; v -> :nok; catch :error, %RuntimeError{} = e -> 11; e; :throw, val -> val; :throw, _ -> 0; after IO.puts("Cleaning!"\) end) ==
218+
assert ~s(try do throw "good"; :"ok"; else v when v == :"ok" -> :"ok"; v -> :"nok"; catch :"error", %RuntimeError{} = e -> 11; e; :"throw", val -> val; :"throw", _ -> 0; after IO.puts("Cleaning!"\) end) ==
219219
actual
220220
end
221221

@@ -235,7 +235,7 @@ defmodule Gradient.ElixirExprTest do
235235
end
236236
|> ElixirExpr.pp_expr()
237237

238-
assert "case {:ok, 10} do {:ok, v} when v > 0 and v > 1 or v < - 1 -> :ok; t when :erlang.is_tuple(t) -> :nok; _ -> :err end" ==
238+
assert ~s(case {:"ok", 10} do {:"ok", v} when v > 0 and v > 1 or v < - 1 -> :"ok"; t when :erlang.is_tuple(t\) -> :"nok"; _ -> :"err" end) ==
239239
actual
240240
end
241241

@@ -249,7 +249,7 @@ defmodule Gradient.ElixirExprTest do
249249
end
250250
|> ElixirExpr.pp_expr()
251251

252-
assert "case {:ok, 13} do {:ok, v} -> v; _err -> :error end" == actual
252+
assert ~s(case {:"ok", 13} do {:"ok", v} -> v; _err -> :"error" end) == actual
253253
end
254254

255255
test "if" do
@@ -263,7 +263,7 @@ defmodule Gradient.ElixirExprTest do
263263
end
264264
|> ElixirExpr.pp_expr()
265265

266-
assert "if :math.floor(1.9) == 1.0 do :ok else :error end" == actual
266+
assert ~s(if :math.floor(1.9\) == 1.0 do :"ok" else :"error" end) == actual
267267
end
268268

269269
test "unless" do
@@ -277,7 +277,7 @@ defmodule Gradient.ElixirExprTest do
277277
end
278278
|> ElixirExpr.pp_expr()
279279

280-
assert "if :math.floor(1.9) == 1.0 do :error else :ok end" == actual
280+
assert ~s(if :math.floor(1.9\) == 1.0 do :"error" else :"ok" end) == actual
281281
end
282282

283283
test "cond" do
@@ -296,7 +296,7 @@ defmodule Gradient.ElixirExprTest do
296296
end
297297
|> ElixirExpr.pp_expr()
298298

299-
assert "cond do true == false -> :ok; :math.floor(1.9) == 1.0 -> :ok; true -> :error end" ==
299+
assert ~s(cond do true == false -> :"ok"; :math.floor\(1.9\) == 1.0 -> :"ok"; true -> :"error" end) ==
300300
actual
301301
end
302302

@@ -322,7 +322,7 @@ defmodule Gradient.ElixirExprTest do
322322
|> ElixirExpr.pp_expr()
323323

324324
assert ~s(try do if true do throw "good" else raise "oops" end;) <>
325-
~s( catch :error, %RuntimeError{} = e -> 11; e; :throw, val -> 12; val end) ==
325+
~s( catch :"error", %RuntimeError{} = e -> 11; e; :"throw", val -> 12; val end) ==
326326
actual
327327
end
328328
end

0 commit comments

Comments
 (0)