Packages

Excavate JavaScript variable values from HTML script tags using AST parsing. Supports var/let/const declarations, window.* assignments, JSON.parse() calls, and nested objects/arrays — no regex needed.

Current section

Files

Jump to
excavator lib excavator ast_walker.ex
Raw

lib/excavator/ast_walker.ex

defmodule ExCavator.AstWalker do
@moduledoc false
alias ExCavator.AstValue
@type result :: %{
name: String.t(),
value: term(),
source: :variable_declaration | :assignment
}
@spec extract_assignments(map()) :: [result()]
def extract_assignments(estree) do
estree
|> get_in(["program", "body"])
|> List.wrap()
|> Enum.flat_map(&extract_from_statement/1)
end
# var/let/const x = VALUE
defp extract_from_statement(%{"type" => "VariableDeclaration", "declarations" => decls}) do
Enum.flat_map(decls, fn
%{"type" => "VariableDeclarator", "id" => %{"name" => name}, "init" => init}
when not is_nil(init) ->
case AstValue.to_elixir(init) do
{:ok, value} -> [%{name: name, value: value, source: :variable_declaration}]
:skip -> []
end
_ ->
[]
end)
end
# expression statements: window.x = VALUE, obj.prop = VALUE, x = VALUE
defp extract_from_statement(%{
"type" => "ExpressionStatement",
"expression" => %{
"type" => "AssignmentExpression",
"operator" => "=",
"left" => left,
"right" => right
}
}) do
case {resolve_name(left), AstValue.to_elixir(right)} do
{name, {:ok, value}} when is_binary(name) ->
[%{name: name, value: value, source: :assignment}]
_ ->
[]
end
end
defp extract_from_statement(_), do: []
# Simple identifier: x = VALUE -> "x"
defp resolve_name(%{"type" => "Identifier", "name" => name}), do: name
# Member expression: window.x -> "window.x"
defp resolve_name(%{
"type" => type,
"object" => object,
"property" => %{"type" => "Identifier", "name" => prop}
})
when type in ["MemberExpression", "StaticMemberExpression"] do
case resolve_name(object) do
name when is_binary(name) -> name <> "." <> prop
_ -> nil
end
end
# Computed member with string key: window["key"] -> "window.key"
defp resolve_name(%{
"type" => type,
"object" => object,
"property" => %{"value" => prop},
"computed" => true
})
when type in ["MemberExpression", "ComputedMemberExpression"] and is_binary(prop) do
case resolve_name(object) do
name when is_binary(name) -> name <> "." <> prop
_ -> nil
end
end
defp resolve_name(_), do: nil
end