Skip to content

Commit

Permalink
Consider start line in MismatchedDelimiterError
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Nov 13, 2023
1 parent 51d23cb commit dfba5db
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 41 deletions.
37 changes: 31 additions & 6 deletions lib/elixir/lib/exception.ex
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ defmodule MismatchedDelimiterError do
:file,
:line,
:column,
:line_offset,
:end_line,
:end_column,
:opening_delimiter,
Expand All @@ -930,6 +931,7 @@ defmodule MismatchedDelimiterError do
column: start_column,
end_line: end_line,
end_column: end_column,
line_offset: line_offset,
description: description,
opening_delimiter: opening_delimiter,
closing_delimiter: _closing_delimiter,
Expand All @@ -941,13 +943,24 @@ defmodule MismatchedDelimiterError do
lines = String.split(snippet, "\n")
expected_delimiter = :elixir_tokenizer.terminator(opening_delimiter)

snippet = format_snippet(start_pos, end_pos, description, file, lines, expected_delimiter)
snippet =
format_snippet(
start_pos,
end_pos,
line_offset,
description,
file,
lines,
expected_delimiter
)

format_message(file, end_line, end_column, snippet)
end

defp format_snippet(
{start_line, _start_column} = start_pos,
{end_line, end_column} = end_pos,
line_offset,
description,
file,
lines,
Expand All @@ -960,12 +973,21 @@ defmodule MismatchedDelimiterError do

relevant_lines =
if end_line - start_line < @max_lines_shown do
line_range(lines, start_pos, end_pos, padding, max_digits, expected_delimiter)
line_range(
lines,
start_pos,
end_pos,
line_offset,
padding,
max_digits,
expected_delimiter
)
else
trimmed_inbetween_lines(
lines,
start_pos,
end_pos,
line_offset,
padding,
max_digits,
expected_delimiter
Expand All @@ -984,6 +1006,7 @@ defmodule MismatchedDelimiterError do
defp format_snippet(
{start_line, start_column},
{end_line, end_column},
line_offset,
description,
file,
lines,
Expand All @@ -994,7 +1017,7 @@ defmodule MismatchedDelimiterError do
general_padding = max(2, max_digits) + 1
padding = n_spaces(general_padding)

line = Enum.fetch!(lines, end_line - 1)
line = Enum.fetch!(lines, end_line - 1 - line_offset)
formatted_line = [line_padding(end_line, max_digits), to_string(end_line), " │ ", line]

mismatched_closing_line =
Expand Down Expand Up @@ -1042,14 +1065,15 @@ defmodule MismatchedDelimiterError do
lines,
{start_line, start_column},
{end_line, end_column},
line_offset,
padding,
max_digits,
expected_delimiter
) do
start_padding = line_padding(start_line, max_digits)
end_padding = line_padding(end_line, max_digits)
first_line = Enum.fetch!(lines, start_line - 1)
last_line = Enum.fetch!(lines, end_line - 1)
first_line = Enum.fetch!(lines, start_line - 1 - line_offset)
last_line = Enum.fetch!(lines, end_line - 1 - line_offset)

"""
#{start_padding}#{start_line}#{first_line}
Expand All @@ -1064,6 +1088,7 @@ defmodule MismatchedDelimiterError do
lines,
{start_line, start_column},
{end_line, end_column},
line_offset,
padding,
max_digits,
expected_delimiter
Expand All @@ -1072,7 +1097,7 @@ defmodule MismatchedDelimiterError do
end_line = end_line - 1

lines
|> Enum.slice(start_line..end_line)
|> Enum.slice((start_line - line_offset)..(end_line - line_offset))
|> Enum.zip_with(start_line..end_line, fn line, line_number ->
line_number = line_number + 1
start_line = start_line + 1
Expand Down
5 changes: 3 additions & 2 deletions lib/elixir/src/elixir_errors.erl
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,10 @@ parse_erl_term(Term) ->
Parsed.

raise_mismatched_delimiter(Location, File, Input, Message) ->
{InputString, _, _} = Input,
{InputString, StartLine, _} = Input,
InputBinary = elixir_utils:characters_to_binary(InputString),
raise('Elixir.MismatchedDelimiterError', Message, [{file, File}, {snippet, InputBinary} | Location]).
KV = [{file, File}, {line_offset, StartLine - 1}, {snippet, InputBinary} | Location],
raise('Elixir.MismatchedDelimiterError', Message, KV).

raise_reserved(Location, File, Input, Keyword) ->
raise_snippet(Location, File, Input, 'Elixir.SyntaxError',
Expand Down
155 changes: 122 additions & 33 deletions lib/elixir/test/elixir/kernel/diagnostics_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ defmodule Kernel.DiagnosticsTest do
"""
end

test "same line with offset" do
output =
capture_raise(
"""
[1, 2, 3, 4, 5, 6)
""",
MismatchedDelimiterError,
line: 3
)

assert output == """
** (MismatchedDelimiterError) mismatched delimiter found on nofile:3:18:
error: unexpected token: )
3 │ [1, 2, 3, 4, 5, 6)
│ │ └ mismatched closing delimiter (expected "]")
│ └ unclosed delimiter
└─ nofile:3:18\
"""
end

test "two-line span" do
output =
capture_raise(
Expand All @@ -76,6 +98,30 @@ defmodule Kernel.DiagnosticsTest do
"""
end

test "two-line span with offset" do
output =
capture_raise(
"""
[a, b, c
d, f, g}
""",
MismatchedDelimiterError,
line: 3
)

assert output == """
** (MismatchedDelimiterError) mismatched delimiter found on nofile:4:9:
error: unexpected token: }
3 │ [a, b, c
│ └ unclosed delimiter
4 │ d, f, g}
│ └ mismatched closing delimiter (expected "]")
└─ nofile:4:9\
"""
end

test "many-line span" do
output =
capture_raise(
Expand Down Expand Up @@ -103,28 +149,31 @@ defmodule Kernel.DiagnosticsTest do
└─ nofile:5:5\
"""
end

test "many-line span with offset" do
output =
capture_raise(
"""
fn always_forget_end ->
IO.inspect(2 + 2) + 2
)
""",
MismatchedDelimiterError
MismatchedDelimiterError,
line: 3
)

assert output == """
** (MismatchedDelimiterError) mismatched delimiter found on nofile:3:1:
** (MismatchedDelimiterError) mismatched delimiter found on nofile:5:1:
error: unexpected token: )
1 │ fn always_forget_end ->
3 │ fn always_forget_end ->
│ └ unclosed delimiter
2 │ IO.inspect(2 + 2) + 2
3 │ )
4 │ IO.inspect(2 + 2) + 2
5 │ )
│ └ mismatched closing delimiter (expected "end")
└─ nofile:3:1\
└─ nofile:5:1\
"""
end

Expand Down Expand Up @@ -290,16 +339,6 @@ defmodule Kernel.DiagnosticsTest do

describe "compile-time exceptions" do
test "SyntaxError (snippet)" do
expected = """
** (SyntaxError) invalid syntax found on nofile:1:17:
error: syntax error before: '*'
1 │ [1, 2, 3, 4, 5, *]
│ ^
└─ nofile:1:17\
"""

output =
capture_raise(
"""
Expand All @@ -308,20 +347,39 @@ defmodule Kernel.DiagnosticsTest do
SyntaxError
)

assert output == expected
assert output == """
** (SyntaxError) invalid syntax found on nofile:1:17:
error: syntax error before: '*'
1 │ [1, 2, 3, 4, 5, *]
│ ^
└─ nofile:1:17\
"""
end

test "TokenMissingError (snippet)" do
expected = """
** (TokenMissingError) token missing on nofile:1:4:
error: syntax error: expression is incomplete
1 │ 1 +
│ ^
└─ nofile:1:4\
"""
test "SyntaxError (snippet) with offset" do
output =
capture_raise(
"""
[1, 2, 3, 4, 5, *]
""",
SyntaxError,
line: 3
)

assert output == """
** (SyntaxError) invalid syntax found on nofile:3:17:
error: syntax error before: '*'
3 │ [1, 2, 3, 4, 5, *]
│ ^
└─ nofile:3:17\
"""
end

test "TokenMissingError (snippet)" do
output =
capture_raise(
"""
Expand All @@ -330,7 +388,36 @@ defmodule Kernel.DiagnosticsTest do
TokenMissingError
)

assert output == expected
assert output == """
** (TokenMissingError) token missing on nofile:1:4:
error: syntax error: expression is incomplete
1 │ 1 +
│ ^
└─ nofile:1:4\
"""
end

test "TokenMissingError (snippet) with offset" do
output =
capture_raise(
"""
1 +
""",
TokenMissingError,
line: 3
)

assert output == """
** (TokenMissingError) token missing on nofile:3:4:
error: syntax error: expression is incomplete
3 │ 1 +
│ ^
└─ nofile:3:4\
"""
end

test "TokenMissingError (no snippet)" do
Expand Down Expand Up @@ -419,7 +506,7 @@ defmodule Kernel.DiagnosticsTest do
1 -
""",
TokenMissingError,
fake_stacktrace
stacktrace: fake_stacktrace
)

assert output == expected
Expand Down Expand Up @@ -448,7 +535,7 @@ defmodule Kernel.DiagnosticsTest do
1 -
""",
TokenMissingError,
fake_stacktrace
stacktrace: fake_stacktrace
)

assert output == expected
Expand Down Expand Up @@ -1104,14 +1191,16 @@ defmodule Kernel.DiagnosticsTest do
end)
end

defp capture_raise(source, exception, mock_stacktrace \\ []) do
defp capture_raise(source, exception, opts \\ []) do
{stacktrace, opts} = Keyword.pop(opts, :stacktrace, [])

e =
assert_raise exception, fn ->
ast = Code.string_to_quoted!(source, columns: true)
ast = Code.string_to_quoted!(source, [columns: true] ++ opts)
Code.eval_quoted(ast)
end

Exception.format(:error, e, mock_stacktrace)
Exception.format(:error, e, stacktrace)
end

defp purge(module) when is_atom(module) do
Expand Down

0 comments on commit dfba5db

Please sign in to comment.