Parses Elixir source into a plain term without ever evaluating it.
target.exs is data, not code (goals.md G6): reading it must never
evaluate it via Code's eval_string/1 or eval_file/1, because doing
so would execute whatever its author — or a compromised generator —
chose to put there. from_source/1 calls Code.string_to_quoted/1
(parse, not evaluate) and walks the resulting AST, reconstructing only a fixed
whitelist of literal shapes: maps with atom keys, lists (plain or
keyword), module aliases, atoms, binaries, and integers. Anything else —
a function call, a variable, an operator, string interpolation, a sigil
— is rejected before it is ever reduced to a value.
Accepted risk, not a gap: Code.string_to_quoted/1 itself interns
an atom for every atom literal and module alias in source — this
module never disables that (no static_atoms_encoder:), and
Module.concat/1 mints one more per alias it reconstructs. G6's
property is "never evaluates target.exs"; atom-table exhaustion is a
different property this module does not claim. For a CLI reading one
local, developer-authored file per invocation that is not a live
threat — the concern is a server parsing unbounded attacker input on
every request, not this. If that trust model ever changes,
static_atoms_encoder: is the mechanism to reach for.
Summary
Types
Every reason from_source/1 can fail: unparseable source, a non-literal AST node, or a duplicate map key.
A location inside the parsed term, as a list of map keys and list indices from the root.
Functions
Parses source and reconstructs it as a plain Elixir term.
Types
@type error() :: {:syntax_error, line :: pos_integer(), description :: String.t()} | {:not_literal, path(), quoted :: Macro.t()} | {:duplicate_key, path(), key :: atom()}
Every reason from_source/1 can fail: unparseable source, a non-literal AST node, or a duplicate map key.
@type path() :: [atom() | non_neg_integer()]
A location inside the parsed term, as a list of map keys and list indices from the root.