From 41c96eb4fe3a5a2bbdde6f734d443c2ceb113562 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Fri, 7 Feb 2025 16:31:55 +0100 Subject: [PATCH] fix: Fix crash in keyword parsing Add test cases from https://github.com/elixir-tools/spitfire/pull/32 --- lib/spitfire.ex | 30 +++++++++++++++++++++-------- test/spitfire_test.exs | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/lib/spitfire.ex b/lib/spitfire.ex index 43dc624..25a2c09 100644 --- a/lib/spitfire.ex +++ b/lib/spitfire.ex @@ -574,10 +574,15 @@ defmodule Spitfire do {kvs, parser} = while2 peek_token(parser) == :"," <- parser do - parser = parser |> next_token() |> next_token() - {pair, parser} = parse_kw_identifier(parser) - - {pair, parser} + if kw_identifier?(current_token(parser)) do + parser = parser |> next_token() |> next_token() + {pair, parser} = parse_kw_identifier(parser) + {pair, parser} + else + parser = parser |> next_token() |> next_token() + {item, parser} = parse_expression(parser, @kw_identifier, true, false, false) + {item, parser} + end end {[{token, value} | kvs], parser} @@ -601,16 +606,25 @@ defmodule Spitfire do {kvs, parser} = while2 peek_token(parser) == :"," <- parser do - parser = parser |> next_token() |> next_token() - {pair, parser} = parse_kw_identifier(parser) - - {pair, parser} + if kw_identifier?(current_token(parser)) do + parser = parser |> next_token() |> next_token() + {pair, parser} = parse_kw_identifier(parser) + {pair, parser} + else + parser = parser |> next_token() |> next_token() + {item, parser} = parse_expression(parser, @kw_identifier, true, false, false) + {item, parser} + end end {[{atom, value} | kvs], parser} end end + defp kw_identifier?({:kw_identifier, _, _}), do: true + defp kw_identifier?({:kw_identifier_unsafe, _, _}), do: true + defp kw_identifier?(_), do: false + defp parse_assoc_op(%{current_token: {:assoc_op, _, _token}} = parser, key) do trace "parse_assoc_op", trace_meta(parser) do assoc_meta = current_meta(parser) diff --git a/test/spitfire_test.exs b/test/spitfire_test.exs index afee842..c70e241 100644 --- a/test/spitfire_test.exs +++ b/test/spitfire_test.exs @@ -534,6 +534,24 @@ defmodule SpitfireTest do end end + test "unfinished keyword list" do + code = ~S''' + defmodule MyModule do + IO.inspect( + :stderr, + label: "label", + (__cursor__()) + ) + end + ''' + + assert Spitfire.parse(code) == s2q(code) + + code = ~S'foo(a, "#{field}": value, (__cursor__()))' + + assert Spitfire.parse(code) == s2q(code) + end + test "another thing" do code = ~S''' case foo do @@ -2909,6 +2927,31 @@ defmodule SpitfireTest do ] }} = Spitfire.container_cursor_to_quoted(code) end + + test "incomplete keyword list" do + code = "[foo: ]" + + assert Spitfire.parse(code) == + {:error, [{:foo, {:__block__, [error: true, line: 1, column: 7], []}}], + [{[line: 1, column: 7], "unknown token: ]"}, {[line: 1, column: 1], "missing closing bracket for list"}]} + end + + test "incomplete keyword list in module attr" do + code = """ + @tag foo: bar, + foo + """ + + assert Spitfire.parse(code) == + { + :ok, + {:@, [end_of_expression: [newlines: 1, line: 2, column: 6], line: 1, column: 1], + [ + {:tag, [line: 1, column: 2], + [[{:foo, {:bar, [line: 1, column: 11], nil}}, {:foo, [line: 2, column: 3], nil}]]} + ]} + } + end end defp s2q(code, opts \\ []) do