diff --git a/lib/spitfire.ex b/lib/spitfire.ex index 582db3c..9db95c1 100644 --- a/lib/spitfire.ex +++ b/lib/spitfire.ex @@ -423,131 +423,151 @@ defmodule Spitfire do trace "parse_grouped_expression", trace_meta(parser) do opening_paren_meta = current_meta(parser) - if peek_token(parser) == :")" do - parser = parser |> next_token() |> eat_eol() - closing_paren_meta = current_meta(parser) - {{:__block__, [parens: opening_paren_meta ++ [closing: closing_paren_meta]], []}, parser} - else - orig_meta = current_meta(parser) - parser = parser |> next_token() |> eat_eol() - old_nesting = parser.nesting + cond do + peek_token(parser) == :eof -> + meta = current_meta(parser) - parser = Map.put(parser, :nesting, 0) + parser = put_error(parser, {meta, "missing closing parentheses"}) - {expression, parser} = parse_expression(parser, @lowest, false, false, true) + {{:__block__, [{:error, true} | meta], []}, next_token(parser)} - expression = push_eoe(expression, peek_eoe(parser)) + peek_token(parser) == :")" -> + parser = parser |> next_token() |> eat_eol() + closing_paren_meta = current_meta(parser) + {{:__block__, [parens: opening_paren_meta ++ [closing: closing_paren_meta]], []}, parser} - cond do - # if the next token is the closing paren or if the next token is a newline and the next next token is the closing paren - peek_token(parser) == :")" || (peek_token(parser) == :eol && peek_token(next_token(parser)) == :")") -> - parser = - parser - |> Map.put(:nesting, old_nesting) - |> next_token() - |> eat_eol() + true -> + orig_meta = current_meta(parser) + parser = parser |> next_token() |> eat_eol() + old_nesting = parser.nesting - closing_paren_meta = current_meta(parser) + parser = Map.put(parser, :nesting, 0) - ast = - case expression do - # unquote splicing is special cased, if it has one expression as an arg, its wrapped in a block - {:unquote_splicing, _, [_]} -> - {:__block__, [{:closing, current_meta(parser)} | orig_meta], [expression]} + {expression, parser} = parse_expression(parser, @lowest, false, false, true) - # not and ! are special cased, if it has one expression as an arg, its wrapped in a block - {op, _, [_]} when op in [:not, :!] -> - {:__block__, [], [expression]} + expression = push_eoe(expression, peek_eoe(parser)) - {:->, _, _} -> - [expression] + cond do + # if the next token is the closing paren or if the next token is a newline and the next next token is the closing paren + peek_token(parser) == :")" || (peek_token(parser) == :eol && peek_token(next_token(parser)) == :")") -> + parser = + parser + |> Map.put(:nesting, old_nesting) + |> next_token() + |> eat_eol() - {f, meta, a} -> - {f, [parens: opening_paren_meta ++ [closing: closing_paren_meta]] ++ meta, a} + closing_paren_meta = current_meta(parser) - expression -> - expression - end + ast = + case expression do + # unquote splicing is special cased, if it has one expression as an arg, its wrapped in a block + {:unquote_splicing, _, [_]} -> + {:__block__, [{:closing, current_meta(parser)} | orig_meta], [expression]} - {ast, parser} + # not and ! are special cased, if it has one expression as an arg, its wrapped in a block + {op, _, [_]} when op in [:not, :!] -> + {:__block__, [], [expression]} - # if the next token is a new line, but the next next token is not the closing paren (implied from previous clause) - peek_token(parser) == :eol or current_token(parser) == :-> -> - # second conditon checks of the next next token is a closing paren or another expression - {exprs, parser} = - while2 current_token(parser) == :-> || - (peek_token(parser) == :eol && parser |> next_token() |> peek_token() != :")") <- parser do - {ast, parser} = - case Map.get(parser, :stab_state) do - %{ast: lhs} -> - {ast, parser} = parse_stab_expression(Map.delete(parser, :stab_state), lhs) - - {ast, parser} = - if current_token(parser) == :-> do - {ast, parser} - else - if peek_token(parser) == :")" do + {:->, _, _} -> + [expression] + + {f, meta, a} -> + {f, [parens: opening_paren_meta ++ [closing: closing_paren_meta]] ++ meta, a} + + expression -> + expression + end + + {ast, parser} + + # if the next token is a new line, but the next next token is not the closing paren (implied from previous clause) + peek_token(parser) == :eol or current_token(parser) == :-> -> + # second conditon checks of the next next token is a closing paren or another expression + {exprs, parser} = + while2 current_token(parser) == :-> || + (peek_token(parser) == :eol && parser |> next_token() |> peek_token() != :")") <- parser do + {ast, parser} = + case Map.get(parser, :stab_state) do + %{ast: lhs} -> + {ast, parser} = parse_stab_expression(Map.delete(parser, :stab_state), lhs) + + {ast, parser} = + if current_token(parser) == :-> do {ast, parser} else - eoe = current_eoe(parser) - ast = push_eoe(ast, eoe) - {ast, next_token(parser)} + if peek_token(parser) == :")" do + {ast, parser} + else + eoe = current_eoe(parser) + ast = push_eoe(ast, eoe) + {ast, next_token(parser)} + end end - end - - {ast, parser} - nil -> - parser = parser |> next_token() |> eat_eol() - {ast, parser} = parse_expression(parser, @lowest, false, false, true) + {ast, parser} - {ast, parser} = - cond do - current_token(parser) == :-> -> - {ast, parser} + nil -> + parser = parser |> next_token() |> eat_eol() + {ast, parser} = parse_expression(parser, @lowest, false, false, true) - peek_token(parser) == :")" -> - {ast, parser} + {ast, parser} = + cond do + current_token(parser) == :-> -> + {ast, parser} - true -> - eoe = peek_eoe(parser) - ast = push_eoe(ast, eoe) - {ast, parser} - end + peek_token(parser) == :")" -> + {ast, parser} - {ast, parser} - end + true -> + eoe = peek_eoe(parser) + ast = push_eoe(ast, eoe) + {ast, parser} + end - {ast, parser} - end + {ast, parser} + end - # handles if the closing paren is on a new line or the same line - parser = - if peek_token(parser) == :eol do - next_token(parser) - else - parser - end + {ast, parser} + end - if peek_token(parser) == :")" do + # handles if the closing paren is on a new line or the same line parser = - parser - |> Map.put(:nesting, old_nesting) - |> next_token() + if peek_token(parser) == :eol do + next_token(parser) + else + parser + end - exprs = [expression | exprs] + if peek_token(parser) == :")" do + parser = + parser + |> Map.put(:nesting, old_nesting) + |> next_token() - ast = - case exprs do - [{:->, _, _} | _] -> - exprs + exprs = [expression | exprs] - _ -> - {:__block__, [{:closing, current_meta(parser)} | orig_meta], exprs} - end + ast = + case exprs do + [{:->, _, _} | _] -> + exprs - {ast, parser} - else + _ -> + {:__block__, [{:closing, current_meta(parser)} | orig_meta], exprs} + end + + {ast, parser} + else + meta = current_meta(parser) + + parser = + parser + |> put_error({meta, "missing closing parentheses"}) + |> Map.put(:nesting, old_nesting) + + {{:__block__, [{:error, true} | meta], []}, next_token(parser)} + end + + true -> meta = current_meta(parser) parser = @@ -556,18 +576,7 @@ defmodule Spitfire do |> Map.put(:nesting, old_nesting) {{:__block__, [{:error, true} | meta], []}, next_token(parser)} - end - - true -> - meta = current_meta(parser) - - parser = - parser - |> put_error({meta, "missing closing parentheses"}) - |> Map.put(:nesting, old_nesting) - - {{:__block__, [{:error, true} | meta], []}, next_token(parser)} - end + end end end end @@ -1625,51 +1634,55 @@ defmodule Spitfire do trace "parse_map_literal", trace_meta(parser) do meta = current_meta(parser) parser = next_token(parser) - # we use a then to create lexical scoping to - # hide manipulating incrementing the parser newlines = peek_newlines(parser) parser = parser |> next_token() |> eat_eol() old_nesting = parser.nesting parser = Map.put(parser, :nesting, 0) - if current_token(parser) == :"}" do - closing = current_meta(parser) - parser = Map.put(parser, :nesting, old_nesting) + cond do + current_token(parser) == :"}" -> + closing = current_meta(parser) + parser = Map.put(parser, :nesting, old_nesting) - extra = - if newlines do - [{:newlines, newlines}, {:closing, closing}] - else - [{:closing, closing}] - end + extra = + if newlines do + [{:newlines, newlines}, {:closing, closing}] + else + [{:closing, closing}] + end - {{:%{}, extra ++ meta, []}, parser} - else - {pairs, parser} = parse_comma_list(parser, @list_comma, false, true) + {{:%{}, extra ++ meta, []}, parser} - parser = eat_eol_at(parser, 1) + peek_token(parser) == :eof -> + parser = put_error(parser, {meta, "missing closing brace for map"}) + {{:%{}, meta, []}, parser} - parser = - case peek_token(parser) do - :"}" -> - next_token(parser) + true -> + {pairs, parser} = parse_comma_list(parser, @list_comma, false, true) - _ -> - put_error(parser, {current_meta(parser), "missing closing brace for map"}) - end + parser = eat_eol_at(parser, 1) - closing = current_meta(parser) - parser = Map.put(parser, :nesting, old_nesting) + parser = + case peek_token(parser) do + :"}" -> + next_token(parser) - extra = - if newlines do - [{:newlines, newlines}, {:closing, closing}] - else - [{:closing, closing}] - end + _ -> + put_error(parser, {current_meta(parser), "missing closing brace for map"}) + end - {{:%{}, extra ++ meta, pairs}, parser} + closing = current_meta(parser) + parser = Map.put(parser, :nesting, old_nesting) + + extra = + if newlines do + [{:newlines, newlines}, {:closing, closing}] + else + [{:closing, closing}] + end + + {{:%{}, extra ++ meta, pairs}, parser} end end end @@ -1807,7 +1820,7 @@ defmodule Spitfire do {{:{}, extra ++ meta, []}, parser} - current_token(parser) in [:end, :"]", :")", :">>"] -> + current_token(parser) in [:end, :"]", :")", :">>", :eof] -> # if the current token is the wrong kind of ending delimiter, we revert to the previous parser # state, put an error, and inject a closing brace to simulate a completed tuple parser = put_error(orig_parser, {meta, "missing closing brace for tuple"}) @@ -1899,7 +1912,7 @@ defmodule Spitfire do parser = Map.put(parser, :nesting, old_nesting) {encode_literal(parser, [], orig_meta), parser} - current_token(parser) in [:end, :"}", :")", :">>"] -> + current_token(parser) in [:end, :"}", :")", :">>", :eof] -> # if the current token is the wrong kind of ending delimiter, we revert to the previous parser # state, put an error, and inject a closing bracket to simulate a completed list parser = put_error(orig_parser, {meta, "missing closing bracket for list"}) @@ -1975,49 +1988,56 @@ defmodule Spitfire do newlines = get_newlines(parser) error_meta = current_meta(parser) - if peek_token(parser) == :")" do - parser = next_token(parser) - closing = current_meta(parser) - ast = {token, newlines ++ [{:closing, closing} | meta], []} + cond do + peek_token(parser) == :eof -> + closing = current_meta(parser) + parser = put_error(parser, {closing, "missing closing parentheses"}) + {{token, newlines ++ [{:closing, closing} | meta], []}, parser} - if peek_token(parser) == :do and parser.nesting == 0 do + peek_token(parser) == :")" -> parser = next_token(parser) - parse_do_block(parser, ast) - else - {ast, parser} - end - else - old_nesting = parser.nesting - parser = Map.put(parser, :nesting, 0) + closing = current_meta(parser) + ast = {token, newlines ++ [{:closing, closing} | meta], []} - {pairs, parser} = - parser - |> next_token() - |> eat_eol() - |> parse_comma_list() + if peek_token(parser) == :do and parser.nesting == 0 do + parser = next_token(parser) + parse_do_block(parser, ast) + else + {ast, parser} + end - parser = Map.put(parser, :nesting, old_nesting) + true -> + old_nesting = parser.nesting + parser = Map.put(parser, :nesting, 0) - parser = eat_eol_at(parser, 1) + {pairs, parser} = + parser + |> next_token() + |> eat_eol() + |> parse_comma_list() - case peek_token(parser) do - :")" -> - parser = next_token(parser) - closing = current_meta(parser) + parser = Map.put(parser, :nesting, old_nesting) - ast = {token, newlines ++ [{:closing, closing} | meta], List.wrap(pairs)} + parser = eat_eol_at(parser, 1) - if peek_token(parser) == :do and parser.nesting == 0 do + case peek_token(parser) do + :")" -> parser = next_token(parser) - parse_do_block(parser, ast) - else - {ast, parser} - end + closing = current_meta(parser) - _ -> - parser = put_error(parser, {error_meta, "missing closing parentheses for function invocation"}) - {{token, newlines ++ meta, List.wrap(pairs)}, parser} - end + ast = {token, newlines ++ [{:closing, closing} | meta], List.wrap(pairs)} + + if peek_token(parser) == :do and parser.nesting == 0 do + parser = next_token(parser) + parse_do_block(parser, ast) + else + {ast, parser} + end + + _ -> + parser = put_error(parser, {error_meta, "missing closing parentheses for function invocation"}) + {{token, newlines ++ meta, List.wrap(pairs)}, parser} + end end end end @@ -2136,31 +2156,38 @@ defmodule Spitfire do newlines = get_newlines(parser) - if peek_token(parser) == :")" do - parser = next_token(parser) - closing = current_meta(parser) - {{lhs, newlines ++ [{:closing, closing} | meta], []}, parser} - else - {pairs, parser} = - parser - |> next_token() - |> eat_eol() - |> parse_comma_list() + cond do + peek_token(parser) == :eof -> + parser = put_error(parser, {meta, "missing closing parentheses for function invocation"}) + closing = current_meta(parser) + {{lhs, newlines ++ [{:closing, closing} | meta], []}, parser} - parser = eat_eol_at(parser, 1) + peek_token(parser) == :")" -> + parser = next_token(parser) + closing = current_meta(parser) + {{lhs, newlines ++ [{:closing, closing} | meta], []}, parser} - parser = - case peek_token(parser) do - :")" -> - next_token(parser) + true -> + {pairs, parser} = + parser + |> next_token() + |> eat_eol() + |> parse_comma_list() - _ -> - put_error(parser, {meta, "missing closing parentheses for function invocation"}) - end + parser = eat_eol_at(parser, 1) - closing = current_meta(parser) + parser = + case peek_token(parser) do + :")" -> + next_token(parser) + + _ -> + put_error(parser, {meta, "missing closing parentheses for function invocation"}) + end + + closing = current_meta(parser) - {{lhs, newlines ++ [{:closing, closing} | meta], List.wrap(pairs)}, parser} + {{lhs, newlines ++ [{:closing, closing} | meta], List.wrap(pairs)}, parser} end end end diff --git a/test/spitfire_test.exs b/test/spitfire_test.exs index afee842..a806b26 100644 --- a/test/spitfire_test.exs +++ b/test/spitfire_test.exs @@ -2058,9 +2058,15 @@ defmodule SpitfireTest do assert Spitfire.parse(code) == {:error, {:<<>>, - [{:end_of_expression, [newlines: 1, line: 2, column: 4]}, {:closing, []}, {:line, 1}, {:column, 1}], - [{:"::", [newlines: 1, line: 1, column: 6], [{:one, [line: 1, column: 3], nil}, :ok]}]}, - [{[line: 1, column: 1], "missing closing brackets for bitstring"}]} + [ + {:end_of_expression, [newlines: 1, line: 2, column: 4]}, + {:closing, []}, + {:line, 1}, + {:column, 1} + ], + [ + {:"::", [newlines: 1, line: 1, column: 6], [{:one, [line: 1, column: 3], nil}, :ok]} + ]}, [{[line: 1, column: 1], "missing closing brackets for bitstring"}]} end test "missing closing parentheses" do @@ -2117,7 +2123,13 @@ defmodule SpitfireTest do {:error, {:__block__, [], [ - {:{}, [end_of_expression: [newlines: 1, line: 1, column: 3], closing: [], line: 1, column: 1], [1]}, + {:{}, + [ + end_of_expression: [newlines: 1, line: 1, column: 3], + closing: [], + line: 1, + column: 1 + ], [1]}, :ok ]}, [{[line: 1, column: 1], "missing closing brace for tuple"}]} end @@ -2135,7 +2147,8 @@ defmodule SpitfireTest do test "missing comma in list" do code = ~S'[:foo :bar, :baz]' - assert Spitfire.parse(code) == {:error, [:foo, :baz], [{[line: 1, column: 7], "syntax error"}]} + assert Spitfire.parse(code) == + {:error, [:foo, :baz], [{[line: 1, column: 7], "syntax error"}]} end test "missing comma in map" do @@ -2152,7 +2165,8 @@ defmodule SpitfireTest do test "missing comma in tuple" do code = ~S'{:foo :bar, :baz}' - assert Spitfire.parse(code) == {:error, {:foo, :baz}, [{[line: 1, column: 7], "syntax error"}]} + assert Spitfire.parse(code) == + {:error, {:foo, :baz}, [{[line: 1, column: 7], "syntax error"}]} end test "missing end in block" do @@ -2183,7 +2197,10 @@ defmodule SpitfireTest do { :., [line: 2, column: 7], - [{:__aliases__, [{:last, [line: 2, column: 3]}, {:line, 2}, {:column, 3}], [:Some]}, :thing] + [ + {:__aliases__, [{:last, [line: 2, column: 3]}, {:line, 2}, {:column, 3}], [:Some]}, + :thing + ] }, [ {:end_of_expression, [newlines: 1, line: 2, column: 15]}, @@ -2240,7 +2257,10 @@ defmodule SpitfireTest do {:__block__, [], [ {{:., [line: 3, column: 9], - [{:__aliases__, [last: [line: 3, column: 5], line: 3, column: 5], [:Some]}, :thing]}, + [ + {:__aliases__, [last: [line: 3, column: 5], line: 3, column: 5], [:Some]}, + :thing + ]}, [ end_of_expression: [newlines: 1, line: 3, column: 17], closing: [line: 3, column: 16], @@ -2334,7 +2354,10 @@ defmodule SpitfireTest do {:new_list, [line: 1, column: 1], nil}, { {:., [line: 2, column: 7], - [{:__aliases__, [last: [line: 2, column: 3], line: 2, column: 3], [:Enum]}, :map]}, + [ + {:__aliases__, [last: [line: 2, column: 3], line: 2, column: 3], [:Enum]}, + :map + ]}, [line: 2, column: 8], [ {:some_list, [line: 2, column: 12], nil}, @@ -2353,7 +2376,11 @@ defmodule SpitfireTest do {:closing, [line: 5, column: 19]}, {:line, 5}, {:column, 1} - ], [{:pid, [line: 5, column: 6], nil}, {:new_list, [line: 5, column: 11], nil}]} + ], + [ + {:pid, [line: 5, column: 6], nil}, + {:new_list, [line: 5, column: 11], nil} + ]} ] } ] @@ -2406,8 +2433,15 @@ defmodule SpitfireTest do :__block__, [], [ - {:import, [end_of_expression: [newlines: 2, line: 2, column: 13], line: 2, column: 3], - [{:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:Baz]}]}, + {:import, + [ + end_of_expression: [newlines: 2, line: 2, column: 13], + line: 2, + column: 3 + ], + [ + {:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:Baz]} + ]}, { :def, [ @@ -2423,8 +2457,12 @@ defmodule SpitfireTest do do: {:__block__, [], [ - {:=, [end_of_expression: [newlines: 1, line: 5, column: 14], line: 5, column: 9], - [{:var, [line: 5, column: 5], nil}, 123]}, + {:=, + [ + end_of_expression: [newlines: 1, line: 5, column: 14], + line: 5, + column: 9 + ], [{:var, [line: 5, column: 5], nil}, 123]}, {:{}, [ {:end_of_expression, [newlines: 1, line: 6, column: 6]}, @@ -2442,7 +2480,11 @@ defmodule SpitfireTest do {:end, [line: 11, column: 3]}, {:line, 9}, {:column, 3} - ], [{:local_function, [line: 9, column: 7], nil}, [do: {:__block__, [], []}]]} + ], + [ + {:local_function, [line: 9, column: 7], nil}, + [do: {:__block__, [], []}] + ]} ] } ] @@ -2489,8 +2531,15 @@ defmodule SpitfireTest do :__block__, [], [ - {:import, [end_of_expression: [newlines: 2, line: 2, column: 13], line: 2, column: 3], - [{:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:Baz]}]}, + {:import, + [ + end_of_expression: [newlines: 2, line: 2, column: 13], + line: 2, + column: 3 + ], + [ + {:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:Baz]} + ]}, { :def, [ @@ -2506,8 +2555,12 @@ defmodule SpitfireTest do do: {:__block__, [], [ - {:=, [end_of_expression: [newlines: 1, line: 5, column: 14], line: 5, column: 9], - [{:var, [line: 5, column: 5], nil}, 123]}, + {:=, + [ + end_of_expression: [newlines: 1, line: 5, column: 14], + line: 5, + column: 9 + ], [{:var, [line: 5, column: 5], nil}, 123]}, {:{}, [closing: [], line: 6, column: 5], [{:var, [line: 6, column: 6], nil}]} ]} ] @@ -2520,7 +2573,11 @@ defmodule SpitfireTest do {:end, [line: 11, column: 3]}, {:line, 9}, {:column, 3} - ], [{:local_function, [line: 9, column: 7], nil}, [do: {:__block__, [], []}]]} + ], + [ + {:local_function, [line: 9, column: 7], nil}, + [do: {:__block__, [], []}] + ]} ] } ] @@ -2649,8 +2706,15 @@ defmodule SpitfireTest do :__block__, [], [ - {:import, [end_of_expression: [newlines: 2, line: 2, column: 13], line: 2, column: 3], - [{:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:Baz]}]}, + {:import, + [ + end_of_expression: [newlines: 2, line: 2, column: 13], + line: 2, + column: 3 + ], + [ + {:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:Baz]} + ]}, { :def, [ @@ -2666,8 +2730,12 @@ defmodule SpitfireTest do do: {:__block__, [], [ - {:=, [end_of_expression: [newlines: 1, line: 5, column: 14], line: 5, column: 9], - [{:var, [line: 5, column: 5], nil}, 123]}, + {:=, + [ + end_of_expression: [newlines: 1, line: 5, column: 14], + line: 5, + column: 9 + ], [{:var, [line: 5, column: 5], nil}, 123]}, [{:var, [line: 6, column: 6], nil}] ]} ] @@ -2680,7 +2748,11 @@ defmodule SpitfireTest do {:end, [line: 11, column: 3]}, {:line, 9}, {:column, 3} - ], [{:local_function, [line: 9, column: 7], nil}, [do: {:__block__, [], []}]]} + ], + [ + {:local_function, [line: 9, column: 7], nil}, + [do: {:__block__, [], []}] + ]} ] } ] @@ -2709,10 +2781,20 @@ defmodule SpitfireTest do do: {:__block__, [], [ - {:import, [end_of_expression: [newlines: 1, line: 2, column: 14], line: 2, column: 3], - [{:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:List]}]}, + {:import, + [ + end_of_expression: [newlines: 1, line: 2, column: 14], + line: 2, + column: 3 + ], + [ + {:__aliases__, [last: [line: 2, column: 10], line: 2, column: 10], [:List]} + ]}, {:=, [line: 3, column: 7], - [{:var, [line: 3, column: 3], nil}, {:__block__, [error: true, line: 3, column: 7], []}]} + [ + {:var, [line: 3, column: 3], nil}, + {:__block__, [error: true, line: 3, column: 7], []} + ]} ]} ] ]}, @@ -2722,6 +2804,46 @@ defmodule SpitfireTest do ] } end + + test "weird characters" do + code = """ + [«] + """ + + assert {:error, _ast, [{[line: 1, column: 1], "missing closing bracket for list"}]} = Spitfire.parse(code) + + code = """ + {«} + """ + + assert {:error, _ast, [{[line: 1, column: 1], "missing closing brace for tuple"}]} = Spitfire.parse(code) + + code = """ + %{«} + """ + + assert {:error, _ast, [{[line: 1, column: 1], "missing closing brace for map"}]} = Spitfire.parse(code) + + code = """ + («) + """ + + assert {:error, _ast, [{[line: 1, column: 1], "missing closing parentheses"}]} = Spitfire.parse(code) + + code = """ + defp foo(«) do + :ok + end + """ + + assert {:error, _ast, [{[line: 1, column: 9], "missing closing parentheses"}]} = Spitfire.parse(code) + + code = """ + foo(«) + """ + + assert {:error, _ast, [{[line: 1, column: 4], "missing closing parentheses"}]} = Spitfire.parse(code) + end end describe "&parse_with_comments/2" do @@ -2861,7 +2983,10 @@ defmodule SpitfireTest do [ do: { {:., [line: 3, column: 9], - [{:__aliases__, [last: [line: 3, column: 5], line: 3, column: 5], [:Enum]}, :map]}, + [ + {:__aliases__, [last: [line: 3, column: 5], line: 3, column: 5], [:Enum]}, + :map + ]}, [closing: [line: 9, column: 19], line: 3, column: 10], [ {:items, [line: 3, column: 14], nil}, @@ -2890,7 +3015,12 @@ defmodule SpitfireTest do {:->, [newlines: 1, line: 8, column: 16], [ [{:error, [line: 8, column: 10], nil}], - {:__cursor__, [closing: [line: 9, column: 12], line: 9, column: 1], []} + {:__cursor__, + [ + closing: [line: 9, column: 12], + line: 9, + column: 1 + ], []} ]} ] ] @@ -2912,7 +3042,10 @@ defmodule SpitfireTest do end defp s2q(code, opts \\ []) do - Code.string_to_quoted(code, Keyword.merge([columns: true, token_metadata: true, emit_warnings: false], opts)) + Code.string_to_quoted( + code, + Keyword.merge([columns: true, token_metadata: true, emit_warnings: false], opts) + ) end defp s2qwc(code, opts \\ []) do