diff --git a/lib/elixir/src/elixir_interpolation.erl b/lib/elixir/src/elixir_interpolation.erl index e0035559a87..bb632461667 100644 --- a/lib/elixir/src/elixir_interpolation.erl +++ b/lib/elixir/src/elixir_interpolation.erl @@ -78,7 +78,7 @@ extract_char(Rest, Buffer, Output, Line, Column, Scope, Interpol, Last) -> Token = io_lib:format("\\u~4.16.0B", [Char]), Pre = "invalid bidirectional formatting character in string: ", Pos = io_lib:format(". If you want to use such character, use it in its escaped ~ts form instead", [Token]), - {error, {?LOC(Line, Column), {Pre, Pos}, Token}}; + {error, {[{error_type, invalid_bidi} | ?LOC(Line, Column)], {Pre, Pos}, Token}}; [Char | NewRest] -> extract(NewRest, [Char | Buffer], Output, Line, Column + 1, Scope, Interpol, Last); diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index f4cf79f3972..43e5ec2f647 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -774,7 +774,16 @@ handle_heredocs(T, Line, Column, H, Scope, Tokens) -> handle_strings(T, Line, Column, H, Scope, Tokens) -> case elixir_interpolation:extract(Line, Column, Scope, true, T, H) of {error, Reason} -> - interpolation_error(Reason, [H | T], Scope, Tokens, " (for string starting at line ~B)", [Line]); + {error, {Meta, Message, Token}, NewRest, NewScope, NewTokens} = interpolation_error(Reason, [H | T], Scope, Tokens, " (for string starting at line ~B)", [Line]), + NewMeta = case lists:keyfind(error_type, 1, Meta) of + % Don't override other errors that may have happened downstream already + {error_type, mismatched_delimiter} -> Meta; + {error_type, invalid_bidi} -> lists:keydelete(error_type, 1, Meta); + _ -> + {line, EndLine} = lists:keyfind(line, 1, Meta), + [{line, Line}, {column, Column - 1}, {error_type, unclosed_delimiter}, {end_line, EndLine}, {opening_delimiter, '"'}] + end, + {error, {NewMeta, Message, Token}, NewRest, NewScope, NewTokens}; {NewLine, NewColumn, Parts, [$: | Rest], InterScope} when ?is_space(hd(Rest)) -> NewScope = case is_unnecessary_quote(Parts, InterScope) of @@ -1493,6 +1502,7 @@ terminator('(') -> ')'; terminator('[') -> ']'; terminator('{') -> '}'; terminator('"""') -> '"""'; +terminator('"') -> '"'; terminator('<<') -> '>>'. %% Keywords checking diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 8cef777253c..c083cb46164 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -496,7 +496,13 @@ defmodule CodeTest do test "string_to_quoted returns error on incomplete escaped string" do assert Code.string_to_quoted("\"\\") == {:error, - {[line: 1, column: 3], "missing terminator: \" (for string starting at line 1)", ""}} + {[ + line: 1, + column: 1, + error_type: :unclosed_delimiter, + end_line: 1, + opening_delimiter: :"\"" + ], "missing terminator: \" (for string starting at line 1)", ""}} end test "compile source" do diff --git a/lib/elixir/test/elixir/kernel/diagnostics_test.exs b/lib/elixir/test/elixir/kernel/diagnostics_test.exs index bf177ebfc9d..a27d535d813 100644 --- a/lib/elixir/test/elixir/kernel/diagnostics_test.exs +++ b/lib/elixir/test/elixir/kernel/diagnostics_test.exs @@ -388,6 +388,31 @@ defmodule Kernel.DiagnosticsTest do """ end + test "missing string terminator" do + output = + capture_raise( + """ + a = "test string + + IO.inspect(10 + 20) + """, + TokenMissingError + ) + + assert output == """ + ** (TokenMissingError) token missing on nofile:3:20: + error: missing terminator: " (for string starting at line 1) + │ + 1 │ a = "test string + │ └ unclosed delimiter + 2 │ + 3 │ IO.inspect(10 + 20) + │ └ missing closing delimiter (expected ") + │ + └─ nofile:3:20\ + """ + end + test "shows in between lines if EOL is not far below" do output = capture_raise(