diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 8150c8bfd5..3f044fafc1 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -239,8 +239,8 @@ defmodule Code.Formatter do {:ok, doc} -> doc - {:error, {line, error, token}} -> - :elixir_errors.parse_error(line, Keyword.get(opts, :file, "nofile"), error, token) + {:error, {location, error, token}} -> + :elixir_errors.parse_error(location, Keyword.get(opts, :file, "nofile"), error, token) end end diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex index afb7378df2..bcc010ddb4 100644 --- a/lib/elixir/lib/exception.ex +++ b/lib/elixir/lib/exception.ex @@ -642,6 +642,7 @@ defmodule Exception do @doc """ Formats the given `file` and `line` as shown in stacktraces. + If any of the values are `nil`, they are omitted. ## Examples @@ -657,14 +658,42 @@ defmodule Exception do """ def format_file_line(file, line, suffix \\ "") do - if file do - if line && line != 0 do - "#{file}:#{line}:#{suffix}" - else - "#{file}:#{suffix}" - end - else + cond do + is_nil(file) -> "" + is_nil(line) or line == 0 -> "#{file}:#{suffix}" + true -> "#{file}:#{line}:#{suffix}" + end + end + + @doc """ + Formats the given `file`, `line`, and `column` as shown in stacktraces. + + If any of the values are `nil`, they are omitted. + + ## Examples + + iex> Exception.format_file_line_column("foo", 1, 2) + "foo:1:2:" + + iex> Exception.format_file_line_column("foo", 1, nil) + "foo:1:" + + iex> Exception.format_file_line_column("foo", nil, nil) + "foo:" + + iex> Exception.format_file_line_column("foo", nil, 2) + "foo:" + + iex> Exception.format_file_line_column(nil, nil, nil) "" + + """ + def format_file_line_column(file, line, column, suffix \\ "") do + cond do + is_nil(file) -> "" + is_nil(line) or line == 0 -> "#{file}:#{suffix}" + is_nil(column) or column == 0 -> "#{file}:#{line}:#{suffix}" + true -> "#{file}:#{line}:#{column}:#{suffix}" end end @@ -770,21 +799,22 @@ defmodule SystemLimitError do end defmodule SyntaxError do - defexception [:file, :line, description: "syntax error"] + defexception [:file, :line, :column, description: "syntax error"] @impl true - def message(exception) do - Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <> - " " <> exception.description + def message(%{file: file, line: line, column: column, description: description}) do + Exception.format_file_line_column(Path.relative_to_cwd(file), line, column) <> + " " <> description end end defmodule TokenMissingError do - defexception [:file, :line, description: "expression is incomplete"] + defexception [:file, :line, :column, description: "expression is incomplete"] @impl true - def message(%{file: file, line: line, description: description}) do - Exception.format_file_line(file && Path.relative_to_cwd(file), line) <> " " <> description + def message(%{file: file, line: line, column: column, description: description}) do + Exception.format_file_line_column(file && Path.relative_to_cwd(file), line, column) <> + " " <> description end end diff --git a/lib/elixir/src/elixir.erl b/lib/elixir/src/elixir.erl index 92789945d7..04551deb4c 100644 --- a/lib/elixir/src/elixir.erl +++ b/lib/elixir/src/elixir.erl @@ -339,10 +339,12 @@ string_to_tokens(String, StartLine, StartColumn, File, Opts) when is_integer(Sta case elixir_tokenizer:tokenize(String, StartLine, StartColumn, [{file, File} | Opts]) of {ok, _Tokens} = Ok -> Ok; - {error, {Line, _, {ErrorPrefix, ErrorSuffix}, Token}, _Rest, _SoFar} -> - {error, {Line, {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}}; - {error, {Line, _, Error, Token}, _Rest, _SoFar} -> - {error, {Line, to_binary(Error), to_binary(Token)}} + {error, {Line, Column, {ErrorPrefix, ErrorSuffix}, Token}, _Rest, _SoFar} -> + Location = [{line, Line}, {column, Column}], + {error, {Location, {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}}; + {error, {Line, Column, Error, Token}, _Rest, _SoFar} -> + Location = [{line, Line}, {column, Column}], + {error, {Location, to_binary(Error), to_binary(Token)}} end. tokens_to_quoted(Tokens, File, Opts) -> @@ -352,9 +354,9 @@ tokens_to_quoted(Tokens, File, Opts) -> {ok, Forms} -> {ok, Forms}; {error, {Line, _, [{ErrorPrefix, ErrorSuffix}, Token]}} -> - {error, {parser_line(Line), {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}}; + {error, {parser_location(Line), {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}}; {error, {Line, _, [Error, Token]}} -> - {error, {parser_line(Line), to_binary(Error), to_binary(Token)}} + {error, {parser_location(Line), to_binary(Error), to_binary(Token)}} after erase(elixir_parser_file), erase(elixir_parser_columns), @@ -362,12 +364,18 @@ tokens_to_quoted(Tokens, File, Opts) -> erase(elixir_literal_encoder) end. -parser_line({Line, _, _}) -> - Line; -parser_line(Meta) -> - case lists:keyfind(line, 1, Meta) of - {line, L} -> L; - false -> 0 +parser_location({Line, Column, _}) -> + [{line, Line}, {column, Column}]; +parser_location(Meta) -> + Line = + case lists:keyfind(line, 1, Meta) of + {line, L} -> L; + false -> 0 + end, + + case lists:keyfind(column, 1, Meta) of + {column, C} -> [{line, Line}, {column, C}]; + false -> [{line, Line}] end. 'string_to_quoted!'(String, StartLine, StartColumn, File, Opts) -> diff --git a/lib/elixir/src/elixir_errors.erl b/lib/elixir/src/elixir_errors.erl index 29b0efd589..000bb59b25 100644 --- a/lib/elixir/src/elixir_errors.erl +++ b/lib/elixir/src/elixir_errors.erl @@ -43,7 +43,7 @@ form_warn(Meta, #{file := File} = E, Module, Desc) when is_list(Meta) -> do_form_warn(Meta, File, E, Module:format_error(Desc)). do_form_warn(Meta, GivenFile, E, Warning) -> - {File, Line} = meta_location(Meta, GivenFile), + [{file, File}, {line, Line}] = meta_location(Meta, GivenFile), Location = case E of @@ -63,34 +63,34 @@ do_form_warn(Meta, GivenFile, E, Warning) -> -spec compile_error(list(), binary(), string(), list()) -> no_return(). compile_error(Meta, File, Message) when is_binary(Message) -> - {MetaFile, MetaLine} = meta_location(Meta, File), - raise(MetaLine, MetaFile, 'Elixir.CompileError', Message); + MetaLocation = meta_location(Meta, File), + raise('Elixir.CompileError', Message, MetaLocation); compile_error(Meta, File, Message) when is_list(Message) -> - {MetaFile, MetaLine} = meta_location(Meta, File), - raise(MetaLine, MetaFile, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)). + MetaLocation = meta_location(Meta, File), + raise('Elixir.CompileError', elixir_utils:characters_to_binary(Message), MetaLocation). compile_error(Meta, File, Format, Args) when is_list(Format) -> compile_error(Meta, File, io_lib:format(Format, Args)). %% Tokenization parsing/errors. --spec parse_error(non_neg_integer(), binary() | {binary(), binary()}, +-spec parse_error(elixir:keyword(), binary() | {binary(), binary()}, binary(), binary()) -> no_return(). -parse_error(Line, File, Error, <<>>) -> +parse_error(Location, File, Error, <<>>) -> Message = case Error of <<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>; _ -> Error end, - raise(Line, File, 'Elixir.TokenMissingError', Message); + raise(Location, File, 'Elixir.TokenMissingError', Message); %% Show a nicer message for end of line -parse_error(Line, File, <<"syntax error before: ">>, <<"eol">>) -> - raise(Line, File, 'Elixir.SyntaxError', +parse_error(Location, File, <<"syntax error before: ">>, <<"eol">>) -> + raise(Location, File, 'Elixir.SyntaxError', <<"unexpectedly reached end of line. The current expression is invalid or incomplete">>); %% Show a nicer message for keywords pt1 (Erlang keywords show up wrapped in single quotes) -parse_error(Line, File, <<"syntax error before: ">>, Keyword) +parse_error(Location, File, <<"syntax error before: ">>, Keyword) when Keyword == <<"'not'">>; Keyword == <<"'and'">>; Keyword == <<"'or'">>; @@ -98,10 +98,10 @@ parse_error(Line, File, <<"syntax error before: ">>, Keyword) Keyword == <<"'after'">>; Keyword == <<"'catch'">>; Keyword == <<"'end'">> -> - raise_reserved(Line, File, binary_part(Keyword, 1, byte_size(Keyword) - 2)); + raise_reserved(Location, File, binary_part(Keyword, 1, byte_size(Keyword) - 2)); %% Show a nicer message for keywords pt2 (Elixir keywords show up as is) -parse_error(Line, File, <<"syntax error before: ">>, Keyword) +parse_error(Location, File, <<"syntax error before: ">>, Keyword) when Keyword == <<"fn">>; Keyword == <<"else">>; Keyword == <<"rescue">>; @@ -109,52 +109,52 @@ parse_error(Line, File, <<"syntax error before: ">>, Keyword) Keyword == <<"false">>; Keyword == <<"nil">>; Keyword == <<"in">> -> - raise_reserved(Line, File, Keyword); + raise_reserved(Location, File, Keyword); %% Produce a human-readable message for errors before a sigil -parse_error(Line, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) -> +parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full) -> {sigil, _, Sigil, [Content | _], _, _, _} = parse_erl_term(Full), Content2 = case is_binary(Content) of true -> Content; false -> <<>> end, Message = <<"syntax error before: sigil \~", Sigil, " starting with content '", Content2/binary, "'">>, - raise(Line, File, 'Elixir.SyntaxError', Message); + raise(Location, File, 'Elixir.SyntaxError', Message); %% Binaries (and interpolation) are wrapped in [<<...>>] -parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -> +parse_error(Location, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -> Term = case parse_erl_term(Full) of [H | _] when is_binary(H) -> <<$", H/binary, $">>; _ -> <<$">> end, - raise(Line, File, 'Elixir.SyntaxError', <>); + raise(Location, File, 'Elixir.SyntaxError', <>); %% Given a string prefix and suffix to insert the token inside the error message rather than append it -parse_error(Line, File, {ErrorPrefix, ErrorSuffix}, Token) when is_binary(ErrorPrefix), is_binary(ErrorSuffix), is_binary(Token) -> +parse_error(Location, File, {ErrorPrefix, ErrorSuffix}, Token) when is_binary(ErrorPrefix), is_binary(ErrorSuffix), is_binary(Token) -> Message = <>, - raise(Line, File, 'Elixir.SyntaxError', Message); + raise(Location, File, 'Elixir.SyntaxError', Message); %% Misplaced char tokens (for example, {char, _, 97}) are translated by Erlang into %% the char literal (i.e., the token in the previous example becomes $a), %% because {char, _, _} is a valid Erlang token for an Erlang char literal. We %% want to represent that token as ?a in the error, according to the Elixir %% syntax. -parse_error(Line, File, <<"syntax error before: ">>, <<$$, Char/binary>>) -> +parse_error(Location, File, <<"syntax error before: ">>, <<$$, Char/binary>>) -> Message = <<"syntax error before: ?", Char/binary>>, - raise(Line, File, 'Elixir.SyntaxError', Message); + raise(Location, File, 'Elixir.SyntaxError', Message); %% Everything else is fine as is -parse_error(Line, File, Error, Token) when is_binary(Error), is_binary(Token) -> +parse_error(Location, File, Error, Token) when is_binary(Error), is_binary(Token) -> Message = <>, - raise(Line, File, 'Elixir.SyntaxError', Message). + raise(Location, File, 'Elixir.SyntaxError', Message). parse_erl_term(Term) -> {ok, Tokens, _} = erl_scan:string(binary_to_list(Term)), {ok, Parsed} = erl_parse:parse_term(Tokens ++ [{dot, 1}]), Parsed. -raise_reserved(Line, File, Keyword) -> - raise(Line, File, 'Elixir.SyntaxError', +raise_reserved(Location, File, Keyword) -> + raise(Location, File, 'Elixir.SyntaxError', <<"syntax error before: ", Keyword/binary, ". \"", Keyword/binary, "\" is a " "reserved word in Elixir and therefore its usage is limited. For instance, " "it can't be used as a variable or be defined nor invoked as a regular function">>). @@ -180,15 +180,14 @@ file_format(Line, File) -> meta_location(Meta, File) -> case elixir_utils:meta_keep(Meta) of - {F, L} -> {F, L}; - nil -> {File, ?line(Meta)} + {F, L} -> [{file, F}, {line, L}]; + nil -> [{file, File}, {line, ?line(Meta)}] end. -raise(none, File, Kind, Message) -> - raise(0, File, Kind, Message); -raise({Line, _, _}, File, Kind, Message) when is_integer(Line) -> - raise(Line, File, Kind, Message); -raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_binary(Message) -> +raise(Location, File, Kind, Message) when is_binary(File) -> + raise(Kind, Message, [{file, File} | Location]). + +raise(Kind, Message, Opts) when is_binary(Message) -> Stacktrace = try throw(ok) catch _:_:Stack -> Stack end, - Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]), - erlang:raise(error, Exception, tl(Stacktrace)). + Exception = Kind:exception([{description, Message} | Opts]), + erlang:raise(error, Exception, tl(Stacktrace)). \ No newline at end of file diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 8211f75649..30379e8379 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -129,7 +129,9 @@ defmodule CodeTest do describe "string_to_quoted/2" do test "converts strings to quoted expressions" do assert Code.string_to_quoted("1 + 2") == {:ok, {:+, [line: 1], [1, 2]}} - assert Code.string_to_quoted("a.1") == {:error, {1, "syntax error before: ", "\"1\""}} + + assert Code.string_to_quoted("a.1") == + {:error, {[line: 1, column: 3], "syntax error before: ", "\"1\""}} end test "converts strings to quoted with column information" do @@ -143,24 +145,28 @@ defmodule CodeTest do test "returns an error tuple on hex errors" do assert Code.string_to_quoted(~S["\x"]) == - {:error, {1, "missing hex sequence after \\x, expected \\xHH", "\""}} + {:error, + {[line: 1, column: 2], "missing hex sequence after \\x, expected \\xHH", "\""}} assert Code.string_to_quoted(~S[:"\x"]) == - {:error, {1, "missing hex sequence after \\x, expected \\xHH", ":\""}} + {:error, + {[line: 1, column: 1], "missing hex sequence after \\x, expected \\xHH", ":\""}} assert Code.string_to_quoted(~S["\x": 123]) == - {:error, {1, "missing hex sequence after \\x, expected \\xHH", "\""}} + {:error, + {[line: 1, column: 2], "missing hex sequence after \\x, expected \\xHH", "\""}} assert Code.string_to_quoted(~s["""\n\\x\n"""]) == - {:error, {1, "missing hex sequence after \\x, expected \\xHH", "\"\"\""}} + {:error, + {[line: 1, column: 1], "missing hex sequence after \\x, expected \\xHH", "\"\"\""}} end test "returns an error tuple on interpolation in calls" do msg = "interpolation is not allowed when calling function/macro. Found interpolation in a call starting with: " - assert Code.string_to_quoted(".\"\#{}\"") == {:error, {1, msg, "\""}} - assert Code.string_to_quoted(".\"a\#{:b}\"c") == {:error, {1, msg, "\""}} + assert Code.string_to_quoted(".\"\#{}\"") == {:error, {[line: 1, column: 2], msg, "\""}} + assert Code.string_to_quoted(".\"a\#{:b}\"c") == {:error, {[line: 1, column: 2], msg, "\""}} end test "returns an error tuple on long atoms" do @@ -168,12 +174,14 @@ defmodule CodeTest do "@GR{+z]`_XrNla!d0ptDp(amr.oS&,UbT}v$L|rHHXGV{;W!>avHbD[T-G5xrzR6m?rQPot-37B@" assert Code.string_to_quoted(~s[:"#{atom}"]) == - {:error, {1, "atom length must be less than system limit: ", atom}} + {:error, + {[line: 1, column: 1], "atom length must be less than system limit: ", atom}} end test "returns an error tuple when no atom is found with :existing_atoms_only" do assert Code.string_to_quoted(":there_is_no_such_atom", existing_atoms_only: true) == - {:error, {1, "unsafe atom does not exist: ", "there_is_no_such_atom"}} + {:error, + {[line: 1, column: 1], "unsafe atom does not exist: ", "there_is_no_such_atom"}} end test "static_atoms_encoder encodes atoms" do @@ -229,7 +237,7 @@ defmodule CodeTest do {:error, "Invalid atom name"} end - assert {:error, {1, "Invalid atom name: ", "there_is_no_such_atom"}} = + assert {:error, {[line: 1, column: 1], "Invalid atom name: ", "there_is_no_such_atom"}} = Code.string_to_quoted(":there_is_no_such_atom", static_atoms_encoder: encoder) end @@ -239,7 +247,8 @@ defmodule CodeTest do encoder = fn atom, _meta -> {:ok, atom} end assert Code.string_to_quoted(atom, static_atoms_encoder: encoder) == - {:error, {1, "atom length must be less than system limit: ", atom}} + {:error, + {[line: 1, column: 1], "atom length must be less than system limit: ", atom}} end test "extended static_atoms_encoder" do diff --git a/lib/elixir/test/elixir/kernel/errors_test.exs b/lib/elixir/test/elixir/kernel/errors_test.exs index e62734fd8f..853f049cc0 100644 --- a/lib/elixir/test/elixir/kernel/errors_test.exs +++ b/lib/elixir/test/elixir/kernel/errors_test.exs @@ -33,17 +33,17 @@ defmodule Kernel.ErrorsTest do test "invalid token" do assert_eval_raise SyntaxError, - "nofile:1: unexpected token: \"\u200B\" (column 7, code point U+200B)", + "nofile:1:7: unexpected token: \"\u200B\" (column 7, code point U+200B)", '[foo: \u200B]\noops' end test "reserved tokens" do - assert_eval_raise SyntaxError, "nofile:1: reserved token: __aliases__", '__aliases__' - assert_eval_raise SyntaxError, "nofile:1: reserved token: __block__", '__block__' + assert_eval_raise SyntaxError, "nofile:1:1: reserved token: __aliases__", '__aliases__' + assert_eval_raise SyntaxError, "nofile:1:1: reserved token: __block__", '__block__' end test "invalid alias terminator" do - assert_eval_raise SyntaxError, ~r"nofile:1: unexpected \( after alias Foo", 'Foo()' + assert_eval_raise SyntaxError, ~r"nofile:1:5: unexpected \( after alias Foo", 'Foo()' end test "invalid __CALLER__" do @@ -64,29 +64,29 @@ defmodule Kernel.ErrorsTest do test "invalid quoted token" do assert_eval_raise SyntaxError, - "nofile:1: syntax error before: \"world\"", + "nofile:1:9: syntax error before: \"world\"", '"hello" "world"' assert_eval_raise SyntaxError, - "nofile:1: syntax error before: 'Foobar'", + "nofile:1:3: syntax error before: 'Foobar'", '1 Foobar' assert_eval_raise SyntaxError, - "nofile:1: syntax error before: foo", + "nofile:1:5: syntax error before: foo", 'Foo.:foo' assert_eval_raise SyntaxError, - "nofile:1: syntax error before: \"foo\"", + "nofile:1:5: syntax error before: \"foo\"", 'Foo.:"foo\#{:bar}"' assert_eval_raise SyntaxError, - "nofile:1: syntax error before: \"", + "nofile:1:5: syntax error before: \"", 'Foo.:"\#{:bar}"' end test "invalid identifier" do message = fn name -> - "nofile:1: invalid character \"@\" (code point U+0040) in identifier: #{name}" + "nofile:1:1: invalid character \"@\" (code point U+0040) in identifier: #{name}" end assert_eval_raise SyntaxError, message.("foo@"), 'foo@' @@ -94,20 +94,20 @@ defmodule Kernel.ErrorsTest do assert_eval_raise SyntaxError, message.("foo@bar"), 'foo@bar' message = fn name -> - "nofile:1: invalid character \"@\" (code point U+0040) in alias: #{name}" + "nofile:1:1: invalid character \"@\" (code point U+0040) in alias: #{name}" end assert_eval_raise SyntaxError, message.("Foo@"), 'Foo@' assert_eval_raise SyntaxError, message.("Foo@bar"), 'Foo@bar' - message = "nofile:1: invalid character \"!\" (code point U+0021) in alias: Foo!" + message = "nofile:1:1: invalid character \"!\" (code point U+0021) in alias: Foo!" assert_eval_raise SyntaxError, message, 'Foo!' - message = "nofile:1: invalid character \"?\" (code point U+003F) in alias: Foo?" + message = "nofile:1:1: invalid character \"?\" (code point U+003F) in alias: Foo?" assert_eval_raise SyntaxError, message, 'Foo?' message = - "nofile:1: invalid character \"ó\" (code point U+00F3) in alias (only ASCII characters are allowed): Foó" + "nofile:1:1: invalid character \"ó\" (code point U+00F3) in alias (only ASCII characters are allowed): Foó" assert_eval_raise SyntaxError, message, 'Foó' @@ -128,7 +128,7 @@ defmodule Kernel.ErrorsTest do end test "kw missing space" do - msg = "nofile:1: keyword argument must be followed by space after: foo:" + msg = "nofile:1:1: keyword argument must be followed by space after: foo:" assert_eval_raise SyntaxError, msg, "foo:bar" assert_eval_raise SyntaxError, msg, "foo:+" @@ -137,56 +137,56 @@ defmodule Kernel.ErrorsTest do test "invalid map start" do assert_eval_raise SyntaxError, - "nofile:1: expected %{ to define a map, got: %[", + "nofile:1:7: expected %{ to define a map, got: %[", "{:ok, %[], %{}}" end test "sigil terminator" do assert_eval_raise TokenMissingError, - "nofile:3: missing terminator: \" (for sigil ~r\" starting at line 1)", + "nofile:3:1: missing terminator: \" (for sigil ~r\" starting at line 1)", '~r"foo\n\n' assert_eval_raise TokenMissingError, - "nofile:3: missing terminator: } (for sigil ~r{ starting at line 1)", + "nofile:3:1: missing terminator: } (for sigil ~r{ starting at line 1)", '~r{foo\n\n' end test "dot terminator" do assert_eval_raise TokenMissingError, - "nofile:1: missing terminator: \" (for function name starting at line 1)", + "nofile:1:9: missing terminator: \" (for function name starting at line 1)", 'foo."bar' end test "string terminator" do assert_eval_raise TokenMissingError, - "nofile:1: missing terminator: \" (for string starting at line 1)", + "nofile:1:5: missing terminator: \" (for string starting at line 1)", '"bar' end test "heredoc start" do assert_eval_raise SyntaxError, - "nofile:1: heredoc allows only zero or more whitespace characters followed by a new line after \"\"\"", + "nofile:1:1: heredoc allows only zero or more whitespace characters followed by a new line after \"\"\"", '"""bar\n"""' end test "heredoc with incomplete interpolation" do assert_eval_raise TokenMissingError, - "nofile:2: missing interpolation terminator: \"}\" (for heredoc starting at line 1)", + "nofile:2:1: missing interpolation terminator: \"}\" (for heredoc starting at line 1)", '"""\n\#{\n"""' end test "heredoc terminator" do assert_eval_raise TokenMissingError, - "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)", + "nofile:2:1: missing terminator: \"\"\" (for heredoc starting at line 1)", '"""\nbar' assert_eval_raise SyntaxError, - "nofile:2: invalid location for heredoc terminator, please escape token or move it to its own line: \"\"\"", + "nofile:2:1: invalid location for heredoc terminator, please escape token or move it to its own line: \"\"\"", '"""\nbar"""' end test "unexpected end" do - assert_eval_raise SyntaxError, "nofile:1: unexpected reserved word: end", '1 end' + assert_eval_raise SyntaxError, "nofile:1:3: unexpected reserved word: end", '1 end' assert_eval_raise SyntaxError, ~r" HINT: it looks like the \"end\" on line 2 does not have a matching \"do\" defined before it", @@ -224,7 +224,7 @@ defmodule Kernel.ErrorsTest do test "missing end" do assert_eval_raise TokenMissingError, - "nofile:1: missing terminator: end (for \"do\" starting at line 1)", + "nofile:1:9: missing terminator: end (for \"do\" starting at line 1)", 'foo do 1' assert_eval_raise TokenMissingError, @@ -256,16 +256,16 @@ defmodule Kernel.ErrorsTest do test "syntax error" do assert_eval_raise SyntaxError, - "nofile:1: syntax error before: '.'", + "nofile:1:2: syntax error before: '.'", '+.foo' assert_eval_raise SyntaxError, - ~r"nofile:1: syntax error before: after. \"after\" is a reserved word", + ~r"nofile:1:1: syntax error before: after. \"after\" is a reserved word", 'after = 1' end test "syntax error before sigil" do - msg = fn x -> "nofile:1: syntax error before: sigil ~s starting with content '#{x}'" end + msg = fn x -> "nofile:1:9: syntax error before: sigil ~s starting with content '#{x}'" end assert_eval_raise SyntaxError, msg.("bar baz"), '~s(foo) ~s(bar baz)' assert_eval_raise SyntaxError, msg.(""), '~s(foo) ~s()' @@ -280,7 +280,7 @@ defmodule Kernel.ErrorsTest do end test "syntax error with do" do - assert_eval_raise SyntaxError, ~r/nofile:1: unexpected reserved word: do./, 'if true, do\n' + assert_eval_raise SyntaxError, ~r/nofile:1:10: unexpected reserved word: do./, 'if true, do\n' assert_eval_raise SyntaxError, ~r/nofile:1: unexpected keyword: do:./, 'if true do:\n' end @@ -326,7 +326,7 @@ defmodule Kernel.ErrorsTest do test "syntax error with no token" do assert_eval_raise TokenMissingError, - "nofile:1: missing terminator: ) (for \"(\" starting at line 1)", + "nofile:1:9: missing terminator: ) (for \"(\" starting at line 1)", 'case 1 (' end @@ -466,9 +466,9 @@ defmodule Kernel.ErrorsTest do end test "literal on map and struct" do - assert_eval_raise SyntaxError, "nofile:1: syntax error before: '}'", '%{:a}' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: '}'", '%{{:a, :b}}' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: '{'", '%{a, b}{a: :b}' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: '}'", '%{:a}' + assert_eval_raise SyntaxError, "nofile:1:11: syntax error before: '}'", '%{{:a, :b}}' + assert_eval_raise SyntaxError, "nofile:1:8: syntax error before: '{'", '%{a, b}{a: :b}' assert_eval_raise CompileError, "nofile:1: expected key-value pairs in a map, got: put_in(foo.bar.baz, nil)", @@ -710,12 +710,12 @@ defmodule Kernel.ErrorsTest do test "invalid fn args" do assert_eval_raise TokenMissingError, - "nofile:1: missing terminator: end (for \"fn\" starting at line 1)", + "nofile:1:5: missing terminator: end (for \"fn\" starting at line 1)", 'fn 1' end test "invalid escape" do - assert_eval_raise TokenMissingError, "nofile:1: invalid escape \\ at end of file", '1 \\' + assert_eval_raise TokenMissingError, "nofile:1:3: invalid escape \\ at end of file", '1 \\' end test "function local conflict" do @@ -1028,7 +1028,7 @@ defmodule Kernel.ErrorsTest do test "interpolation error" do assert_eval_raise SyntaxError, - "nofile:1: unexpected token: ). The \"do\" at line 1 is missing terminator \"end\"", + "nofile:1:17: unexpected token: ). The \"do\" at line 1 is missing terminator \"end\"", '"foo\#{case 1 do )}bar"' end @@ -1084,32 +1084,32 @@ defmodule Kernel.ErrorsTest do ''') # All invalid examples - assert_eval_raise SyntaxError, "nofile:1: syntax error before: ';'", '1+;\n2' + assert_eval_raise SyntaxError, "nofile:1:3: syntax error before: ';'", '1+;\n2' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: ';'", 'max(1, ;2)' + assert_eval_raise SyntaxError, "nofile:1:8: syntax error before: ';'", 'max(1, ;2)' end test "new line error" do assert_eval_raise SyntaxError, - "nofile:3: unexpectedly reached end of line. The current expression is invalid or incomplete", + "nofile:3:6: unexpectedly reached end of line. The current expression is invalid or incomplete", 'if true do\n foo = [],\n baz\nend' end test "characters literal are printed correctly in syntax errors" do - assert_eval_raise SyntaxError, "nofile:1: syntax error before: ?a", ':ok ?a' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: ?\\s", ':ok ?\\s' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: ?す", ':ok ?す' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: ?a", ':ok ?a' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: ?\\s", ':ok ?\\s' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: ?す", ':ok ?す' end test "numbers are printed correctly in syntax errors" do - assert_eval_raise SyntaxError, "nofile:1: syntax error before: \"12\"", ':ok 12' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: \"0b1\"", ':ok 0b1' - assert_eval_raise SyntaxError, "nofile:1: syntax error before: \"12.3\"", ':ok 12.3' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: \"12\"", ':ok 12' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: \"0b1\"", ':ok 0b1' + assert_eval_raise SyntaxError, "nofile:1:5: syntax error before: \"12.3\"", ':ok 12.3' end test "invalid \"fn do expr end\"" do assert_eval_raise SyntaxError, - "nofile:1: unexpected reserved word: do. Anonymous functions are written as:\n\n fn pattern -> expression end", + "nofile:1:4: unexpected reserved word: do. Anonymous functions are written as:\n\n fn pattern -> expression end", 'fn do :ok end' end diff --git a/lib/ex_unit/test/ex_unit/doc_test_test.exs b/lib/ex_unit/test/ex_unit/doc_test_test.exs index 95048c47fd..c95f5318ca 100644 --- a/lib/ex_unit/test/ex_unit/doc_test_test.exs +++ b/lib/ex_unit/test/ex_unit/doc_test_test.exs @@ -519,7 +519,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 1) doctest module ExUnit.DocTestTest.Invalid (1) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:155: syntax error before: '*' + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:155:6: syntax error before: '*' doctest: iex> 1 + * 1 1 @@ -610,7 +610,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 8) doctest ExUnit.DocTestTest.Invalid.a/0 (8) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:182: syntax error before: '*' + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:182:6: syntax error before: '*' doctest: iex> 1 + * 1 1 @@ -621,7 +621,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 9) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (9) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:212: unexpected token: "`" (column 5, code point U+0060) + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:212:5: unexpected token: "`" (column 5, code point U+0060) doctest: iex> 1 + 2 3 @@ -633,7 +633,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 10) doctest ExUnit.DocTestTest.Invalid.indented_not_enough/0 (10) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:196: unexpected token: "`" (column 1, code point U+0060) + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:196:1: unexpected token: "`" (column 1, code point U+0060) doctest: iex> 1 + 2 3 @@ -645,7 +645,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 11) doctest ExUnit.DocTestTest.Invalid.indented_too_much/0 (11) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:204: unexpected token: "`" (column 3, code point U+0060) + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:204:3: unexpected token: "`" (column 3, code point U+0060) doctest: iex> 1 + 2 3 @@ -665,7 +665,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 13) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (13) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:224: missing terminator: } (for "{" starting at line 224). If you are planning to assert on the result of an iex> expression which contains a value inspected as #Name<...>, please make sure the inspected value is placed at the beginning of the expression; otherwise Elixir will treat it as a comment due to the leading sign #. + Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:224:7: missing terminator: } (for "{" starting at line 224). If you are planning to assert on the result of an iex> expression which contains a value inspected as #Name<...>, please make sure the inspected value is placed at the beginning of the expression; otherwise Elixir will treat it as a comment due to the leading sign #. doctest: iex> {:ok, MapSet.new([1, 2, 3])} {:ok, #MapSet<[1, 2, 3]>} @@ -676,7 +676,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 14) doctest ExUnit.DocTestTest.Invalid.b/0 (14) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:188: syntax error before: '*' + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:188:6: syntax error before: '*' doctest: iex> 1 + * 1 1 @@ -687,7 +687,7 @@ defmodule ExUnit.DocTestTest do assert output =~ """ 15) doctest ExUnit.DocTestTest.Invalid.t/0 (15) (ExUnit.DocTestTest.ActuallyCompiled) test/ex_unit/doc_test_test.exs:#{doctest_line} - Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:230: syntax error before: '*' + Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:230:6: syntax error before: '*' doctest: iex> 1 + * 1 1 diff --git a/lib/iex/lib/iex/evaluator.ex b/lib/iex/lib/iex/evaluator.ex index 7e1a85ccde..73f5e288ed 100644 --- a/lib/iex/lib/iex/evaluator.ex +++ b/lib/iex/lib/iex/evaluator.ex @@ -230,7 +230,7 @@ defmodule IEx.Evaluator do end defp do_eval(@break_trigger, iex_state, _state) do - :elixir_errors.parse_error(iex_state.counter, "iex", "incomplete expression", "") + :elixir_errors.parse_error([line: iex_state.counter], "iex", "incomplete expression", "") end defp do_eval(latest_input, iex_state, state) do @@ -275,9 +275,9 @@ defmodule IEx.Evaluator do {%{iex_state | cache: code}, state} end - defp handle_eval({:error, {line, error, token}}, _code, _line, _iex_state, _state) do + defp handle_eval({:error, {location, error, token}}, _code, _line, _iex_state, _state) do # Encountered malformed expression - :elixir_errors.parse_error(line, "iex", error, token) + :elixir_errors.parse_error(location, "iex", error, token) end defp update_history(state, counter, _cache, result) do diff --git a/lib/iex/test/iex/interaction_test.exs b/lib/iex/test/iex/interaction_test.exs index 27803cf0f0..65bed3d5bc 100644 --- a/lib/iex/test/iex/interaction_test.exs +++ b/lib/iex/test/iex/interaction_test.exs @@ -21,7 +21,7 @@ defmodule IEx.InteractionTest do test "invalid input" do assert capture_iex("if true do ) false end") =~ - "** (SyntaxError) iex:1: unexpected token: ). The \"do\" at line 1 is missing terminator \"end\"" + "** (SyntaxError) iex:1:12: unexpected token: ). The \"do\" at line 1 is missing terminator \"end\"" end test "multiple vars" do diff --git a/lib/mix/test/mix/tasks/format_test.exs b/lib/mix/test/mix/tasks/format_test.exs index 46a3e86e7b..9bdaa28ef1 100644 --- a/lib/mix/test/mix/tasks/format_test.exs +++ b/lib/mix/test/mix/tasks/format_test.exs @@ -511,7 +511,7 @@ defmodule Mix.Tasks.FormatTest do defmodule <%= module %>.Bar do end """) - assert_raise SyntaxError, ~r"a.ex:1: syntax error before: '='", fn -> + assert_raise SyntaxError, ~r"a.ex:1:13: syntax error before: '='", fn -> Mix.Tasks.Format.run(["a.ex"]) end @@ -521,7 +521,7 @@ defmodule Mix.Tasks.FormatTest do test "raises SyntaxError when parsing invalid stdin", context do in_tmp(context.test, fn -> - assert_raise SyntaxError, ~r"stdin:1: syntax error before: '='", fn -> + assert_raise SyntaxError, ~r"stdin:1:13: syntax error before: '='", fn -> capture_io("defmodule <%= module %>.Bar do end", fn -> Mix.Tasks.Format.run(["-"]) end)