Current section
Files
Jump to
Current section
Files
lib/excavator/ast_value.ex
defmodule ExCavator.AstValue do
@moduledoc false
@spec to_elixir(map() | nil) :: {:ok, term()} | :skip
def to_elixir(nil), do: :skip
# JSON.parse('...') with StaticMemberExpression callee
def to_elixir(%{
"type" => "CallExpression",
"callee" => %{
"type" => "StaticMemberExpression",
"object" => %{"type" => "Identifier", "name" => "JSON"},
"property" => %{"type" => "Identifier", "name" => "parse"}
},
"arguments" => [%{"type" => "StringLiteral", "value" => json_string} | _]
})
when is_binary(json_string) do
case Jason.decode(json_string) do
{:ok, value} -> {:ok, value}
{:error, _} -> :skip
end
end
# JSON.parse('...') with MemberExpression callee
def to_elixir(%{
"type" => "CallExpression",
"callee" => %{
"type" => "MemberExpression",
"object" => %{"type" => "Identifier", "name" => "JSON"},
"property" => %{"type" => "Identifier", "name" => "parse"}
},
"arguments" => [%{"type" => "StringLiteral", "value" => json_string} | _]
})
when is_binary(json_string) do
case Jason.decode(json_string) do
{:ok, value} -> {:ok, value}
{:error, _} -> :skip
end
end
# JSON.parse with Literal type arg (fallback)
def to_elixir(%{
"type" => "CallExpression",
"callee" => callee,
"arguments" => [%{"type" => "Literal", "value" => json_string} | _]
})
when is_binary(json_string) do
if json_parse_callee?(callee) do
case Jason.decode(json_string) do
{:ok, value} -> {:ok, value}
{:error, _} -> :skip
end
else
:skip
end
end
# String literal
def to_elixir(%{"type" => "StringLiteral", "value" => value}) when is_binary(value) do
{:ok, value}
end
# Numeric literal
def to_elixir(%{"type" => "NumericLiteral", "value" => value}) when is_number(value) do
{:ok, value}
end
# Boolean literal
def to_elixir(%{"type" => "BooleanLiteral", "value" => value}) when is_boolean(value) do
{:ok, value}
end
# Null literal
def to_elixir(%{"type" => "NullLiteral"}) do
{:ok, nil}
end
# Standard ESTree Literal (fallback)
def to_elixir(%{"type" => "Literal", "value" => value}) do
{:ok, value}
end
# Object expression -> Elixir map
def to_elixir(%{"type" => "ObjectExpression", "properties" => props}) do
Enum.reduce_while(props, {:ok, %{}}, fn prop, {:ok, acc} ->
with key when is_binary(key) <- extract_property_key(prop),
{:ok, val} <- to_elixir(prop["value"]) do
{:cont, {:ok, Map.put(acc, key, val)}}
else
_ -> {:halt, :skip}
end
end)
end
# Array expression -> Elixir list
def to_elixir(%{"type" => "ArrayExpression", "elements" => elements}) do
Enum.reduce_while(elements, {:ok, []}, fn elem, {:ok, acc} ->
case to_elixir(elem) do
{:ok, val} -> {:cont, {:ok, [val | acc]}}
:skip -> {:halt, :skip}
end
end)
|> case do
{:ok, list} -> {:ok, Enum.reverse(list)}
:skip -> :skip
end
end
# Unary minus: -42
def to_elixir(%{"type" => "UnaryExpression", "operator" => "-", "argument" => arg}) do
case to_elixir(arg) do
{:ok, n} when is_number(n) -> {:ok, -n}
_ -> :skip
end
end
# Template literal with no expressions (plain backtick string)
def to_elixir(%{
"type" => "TemplateLiteral",
"expressions" => [],
"quasis" => [%{"value" => %{"cooked" => str}}]
})
when is_binary(str) do
{:ok, str}
end
# Anything else -> skip
def to_elixir(_), do: :skip
defp extract_property_key(%{"key" => %{"type" => "Identifier", "name" => name}}), do: name
defp extract_property_key(%{"key" => %{"type" => "StringLiteral", "value" => name}})
when is_binary(name),
do: name
defp extract_property_key(%{"key" => %{"type" => "Literal", "value" => name}})
when is_binary(name),
do: name
defp extract_property_key(%{"key" => %{"type" => "NumericLiteral", "value" => name}})
when is_number(name),
do: to_string(name)
defp extract_property_key(_), do: nil
defp json_parse_callee?(%{
"type" => type,
"object" => %{"type" => "Identifier", "name" => "JSON"},
"property" => %{"type" => "Identifier", "name" => "parse"}
})
when type in ["MemberExpression", "StaticMemberExpression"],
do: true
defp json_parse_callee?(_), do: false
end