Skip to content

Commit 67fd982

Browse files
viniciusmullerjosevalim
authored andcommitted
Improve diagnostics for unclosed heredocs (#13182)
1 parent 1878e30 commit 67fd982

File tree

4 files changed

+52
-6
lines changed

4 files changed

+52
-6
lines changed

lib/elixir/lib/exception.ex

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,15 @@ defmodule Exception do
794794
end
795795
end
796796

797+
@doc false
798+
def format_expected_delimiter(opening_delimiter) do
799+
terminator = :elixir_tokenizer.terminator(opening_delimiter)
800+
801+
if terminator |> Atom.to_string() |> String.contains?(["\"", "'"]),
802+
do: terminator,
803+
else: ~s("#{terminator}")
804+
end
805+
797806
@doc false
798807
def format_snippet(
799808
{start_line, _start_column} = start_pos,
@@ -1183,10 +1192,10 @@ defmodule MismatchedDelimiterError do
11831192
start_pos = {start_line, start_column}
11841193
end_pos = {end_line, end_column}
11851194
lines = String.split(snippet, "\n")
1186-
expected_delimiter = :elixir_tokenizer.terminator(opening_delimiter)
1195+
expected_delimiter = Exception.format_expected_delimiter(opening_delimiter)
11871196

11881197
start_message = "└ unclosed delimiter"
1189-
end_message = ~s/└ mismatched closing delimiter (expected "#{expected_delimiter}")/
1198+
end_message = ~s/└ mismatched closing delimiter (expected #{expected_delimiter})/
11901199

11911200
snippet =
11921201
Exception.format_snippet(
@@ -1306,10 +1315,10 @@ defmodule TokenMissingError do
13061315

13071316
start_pos = {line, column}
13081317
end_pos = {end_line, end_column}
1309-
expected_delimiter = :elixir_tokenizer.terminator(opening_delimiter)
1318+
expected_delimiter = Exception.format_expected_delimiter(opening_delimiter)
13101319

13111320
start_message = ~s/└ unclosed delimiter/
1312-
end_message = ~s/└ missing closing delimiter (expected "#{expected_delimiter}")/
1321+
end_message = ~s/└ missing closing delimiter (expected #{expected_delimiter})/
13131322

13141323
snippet =
13151324
Exception.format_snippet(

lib/elixir/src/elixir_tokenizer.erl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,16 @@ extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) ->
10301030
{ok, NewLine, NewColumn, tokens_to_binary(Parts2), Rest, NewScope};
10311031

10321032
{error, Reason} ->
1033-
{error, interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line])}
1033+
{Position, Message, List} = interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line]),
1034+
{line, EndLine} = lists:keyfind(line, 1, Position),
1035+
Meta = [
1036+
{error_type, unclosed_delimiter},
1037+
{opening_delimiter, '"""'},
1038+
{line, Line},
1039+
{column, Column},
1040+
{end_line, EndLine}
1041+
],
1042+
{error, {Meta, Message, List}}
10341043
end;
10351044

10361045
error ->
@@ -1481,6 +1490,7 @@ terminator('do') -> 'end';
14811490
terminator('(') -> ')';
14821491
terminator('[') -> ']';
14831492
terminator('{') -> '}';
1493+
terminator('"""') -> '"""';
14841494
terminator('<<') -> '>>'.
14851495

14861496
%% Keywords checking

lib/elixir/test/elixir/kernel/diagnostics_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,33 @@ defmodule Kernel.DiagnosticsTest do
361361
"""
362362
end
363363

364+
test "missing heredoc terminator" do
365+
output =
366+
capture_raise(
367+
"""
368+
a = \"""
369+
test string
370+
371+
IO.inspect(10 + 20)
372+
""",
373+
TokenMissingError
374+
)
375+
376+
assert output == """
377+
** (TokenMissingError) token missing on nofile:4:20:
378+
error: missing terminator: \""" (for heredoc starting at line 1)
379+
380+
1 │ a = \"""
381+
│ └ unclosed delimiter
382+
2 │ test string
383+
3 │
384+
4 │ IO.inspect(10 + 20)
385+
│ └ missing closing delimiter (expected \""")
386+
387+
└─ nofile:4:20\
388+
"""
389+
end
390+
364391
test "shows in between lines if EOL is not far below" do
365392
output =
366393
capture_raise(

lib/elixir/test/elixir/kernel/parser_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ defmodule Kernel.ParserTest do
528528
test "heredoc with incomplete interpolation" do
529529
assert_token_missing(
530530
[
531-
"nofile:2:1:",
531+
"nofile:1:4:",
532532
~s/missing interpolation terminator: "}" (for heredoc starting at line 1)/
533533
],
534534
~c"\"\"\"\n\#{\n"

0 commit comments

Comments
 (0)