diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex index ddb682ffe6a..4cd0e2b64ac 100644 --- a/lib/elixir/lib/exception.ex +++ b/lib/elixir/lib/exception.ex @@ -916,6 +916,7 @@ defmodule MismatchedDelimiterError do :file, :line, :column, + :line_offset, :end_line, :end_column, :opening_delimiter, @@ -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, @@ -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, @@ -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 @@ -984,6 +1006,7 @@ defmodule MismatchedDelimiterError do defp format_snippet( {start_line, start_column}, {end_line, end_column}, + line_offset, description, file, lines, @@ -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 = @@ -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} @@ -1064,6 +1088,7 @@ defmodule MismatchedDelimiterError do lines, {start_line, start_column}, {end_line, end_column}, + line_offset, padding, max_digits, expected_delimiter @@ -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 diff --git a/lib/elixir/src/elixir_errors.erl b/lib/elixir/src/elixir_errors.erl index 2455d69ae55..c4382af4b3b 100644 --- a/lib/elixir/src/elixir_errors.erl +++ b/lib/elixir/src/elixir_errors.erl @@ -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', diff --git a/lib/elixir/test/elixir/kernel/diagnostics_test.exs b/lib/elixir/test/elixir/kernel/diagnostics_test.exs index a34dbb4e1d0..07b1f1a6033 100644 --- a/lib/elixir/test/elixir/kernel/diagnostics_test.exs +++ b/lib/elixir/test/elixir/kernel/diagnostics_test.exs @@ -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( @@ -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( @@ -103,7 +149,9 @@ defmodule Kernel.DiagnosticsTest do │ └─ nofile:5:5\ """ + end + test "many-line span with offset" do output = capture_raise( """ @@ -111,20 +159,21 @@ defmodule Kernel.DiagnosticsTest do 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 @@ -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( """ @@ -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( """ @@ -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 @@ -419,7 +506,7 @@ defmodule Kernel.DiagnosticsTest do 1 - """, TokenMissingError, - fake_stacktrace + stacktrace: fake_stacktrace ) assert output == expected @@ -448,7 +535,7 @@ defmodule Kernel.DiagnosticsTest do 1 - """, TokenMissingError, - fake_stacktrace + stacktrace: fake_stacktrace ) assert output == expected @@ -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