Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions lib/spitfire.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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)
Expand Down
43 changes: 43 additions & 0 deletions test/spitfire_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
'''
Comment on lines +538 to +546
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an interesting result from the builtin parser

iex(5)> Code.string_to_quoted(~S'''
...(5)>       defmodule MyModule do
...(5)>         IO.inspect(
...(5)>           :stderr,
...(5)>           label: "label",
...(5)>           (__cursor__())
...(5)>         )
...(5)>       end
...(5)>       ''')
{:ok,
 {:defmodule, [line: 1],
  [
    {:__aliases__, [line: 1], [:MyModule]},
    [
      do: {{:., [line: 2], [{:__aliases__, [line: 2], [:IO]}, :inspect]},
       [line: 2], [:stderr, [{:label, "label"}, {:__cursor__, [line: 5], []}]]}
    ]
  ]}}
iex(6)> Code.string_to_quoted(~S'''
...(6)> defmodule MyModule do
...(6)>   IO.inspect(
...(6)>     :stderr,
...(6)>     label: "label",
...(6)>     :foo
...(6)>   )
...(6)> end
...(6)> '''
...(6)> )
{:error,
 {[line: 4, column: 19],
  "unexpected expression after keyword list. Keyword lists must always come as the last argument. Therefore, this is not allowed:\n\n    function_call(1, some: :option, 2)\n\nInstead, wrap the keyword in brackets:\n\n    function_call(1, [some: :option], 2)\n\nSyntax error after: ",
  "','"}}
iex(7)>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either the builtin parser supports a macro call returning tuple AST node or this is an error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possibility it is more forgiving for nodes with __cursor__() special form

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhanberg We have two options here.

  1. Have a special handling for __cursor__() nodes in keyword lists
  2. Make the parser error tolerant and either silently drop invalid list entries or produce invalid AST


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
Expand Down Expand Up @@ -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
Expand Down
Loading