Metastatic.Semantic.Enricher
(Metastatic v0.22.2)
View Source
AST enricher for semantic metadata injection.
This module provides functions to enrich MetaAST nodes with semantic metadata. Two kinds of enrichment are performed:
op_kindon:function_callnodes -- classifies calls by semantic domain (database, HTTP, queue, ...) using registered patterns.callback_foron:function_defnodes -- tags functions that implement a known behaviour/base-class callback (e.g.,Oban.Workerperform/1,ActiveJob::Baseperform).
Usage
Single Node Enrichment
alias Metastatic.Semantic.Enricher
# Enrich a single function_call node
enriched = Enricher.enrich(node, :elixir)Full Tree Enrichment
# Enrich all nodes in an AST tree (including callback_for)
enriched_ast = Enricher.enrich_tree(ast, :python)Integration with Adapters
The enricher should be called at the end of the to_meta transformation:
def transform(native_ast) do
with {:ok, meta_ast, metadata} <- do_transform(native_ast) do
enriched = Enricher.enrich_tree(meta_ast, :python)
{:ok, enriched, metadata}
end
endEnrichment Strategy
Enrichment is eager -- all applicable nodes are enriched in a single pass. This ensures semantic information is available immediately for analyzers.
The tree traversal is context-aware: when entering a :container node,
the enricher collects behaviours declared via :import children (e.g.,
use Oban.Worker) and :parent metadata (e.g., Ruby class inheritance).
These behaviours are then matched against the Semantic.Callbacks
registry to annotate enclosed :function_def nodes.
Summary
Functions
Enriches a single MetaAST node with semantic metadata.
Enriches a :function_def node with callback_for metadata.
Enriches an entire AST tree with semantic metadata.
Checks if a node has been enriched with semantic metadata.
Gets the callback_for value from a node, if present.
Gets the op_kind from a node, if present.
Types
Traversal accumulator carrying container context
@type language() :: Metastatic.Semantic.Patterns.language()
Language identifier for pattern matching
Functions
@spec enrich(Metastatic.AST.meta_ast(), language()) :: Metastatic.AST.meta_ast()
Enriches a single MetaAST node with semantic metadata.
If the node is a :function_call and matches a known pattern for the
given language, adds op_kind metadata. Otherwise returns the node unchanged.
Parameters
node- The MetaAST node to enrichlanguage- The source language for pattern matching
Examples
iex> node = {:function_call, [name: "Repo.get"], [{:variable, [], "User"}, {:literal, [subtype: :integer], 1}]}
iex> enriched = Enricher.enrich(node, :elixir)
iex> Keyword.get(elem(enriched, 1), :op_kind)
[domain: :db, operation: :retrieve, target: "User", async: false, framework: :ecto]
@spec enrich_callback(Metastatic.AST.meta_ast(), language(), [String.t()]) :: Metastatic.AST.meta_ast()
Enriches a :function_def node with callback_for metadata.
When the function matches a known callback for one of the given
behaviours, the node's metadata is annotated with
callback_for: "BehaviourModule".
This is called internally by enrich_tree/2 with the behaviours
collected from the enclosing :container. Can also be called
directly when the set of behaviours is known.
Parameters
node- A:function_defMetaAST nodelanguage- Source language atombehaviours- List of behaviour/base-class module name strings
Examples
iex> node = {:function_def, [name: "perform", params: [{:param, [], "job"}], visibility: :public, arity: 1], []}
iex> enriched = Enricher.enrich_callback(node, :elixir, ["Oban.Worker"])
iex> Keyword.get(elem(enriched, 1), :callback_for)
"Oban.Worker"
@spec enrich_tree(Metastatic.AST.meta_ast(), language()) :: Metastatic.AST.meta_ast()
Enriches an entire AST tree with semantic metadata.
Traverses the AST and enriches all applicable nodes. This is the recommended way to enrich AST during adapter transformations.
The traversal is context-aware:
- On entering a
:containernode, collects behaviours from its:importchildren and:parentmetadata. - On leaving a
:function_definside such a container, checksCallbacks.lookup/4and addscallback_for:if matched. - On leaving a
:function_call, addsop_kind:if matched.
Parameters
ast- The root MetaAST nodelanguage- The source language for pattern matching
Examples
iex> ast = {:block, [], [
...> {:function_call, [name: "Repo.get"], [{:variable, [], "User"}, {:literal, [subtype: :integer], 1}]},
...> {:function_call, [name: "Repo.all"], [{:variable, [], "Post"}]}
...> ]}
iex> enriched = Enricher.enrich_tree(ast, :elixir)
iex> {:block, [], [call1, call2]} = enriched
iex> Keyword.get(elem(call1, 1), :op_kind) |> Keyword.get(:operation)
:retrieve
iex> Keyword.get(elem(call2, 1), :op_kind) |> Keyword.get(:operation)
:retrieve_all
@spec enriched?(Metastatic.AST.meta_ast()) :: boolean()
Checks if a node has been enriched with semantic metadata.
Returns true if the node carries op_kind or callback_for.
Examples
iex> node = {:function_call, [name: "Repo.get", op_kind: [domain: :db, operation: :retrieve]], []}
iex> Enricher.enriched?(node)
true
iex> node = {:function_def, [name: "perform", callback_for: "Oban.Worker"], []}
iex> Enricher.enriched?(node)
true
iex> node = {:function_call, [name: "unknown"], []}
iex> Enricher.enriched?(node)
false
@spec get_callback_for(Metastatic.AST.meta_ast()) :: String.t() | nil
Gets the callback_for value from a node, if present.
Examples
iex> node = {:function_def, [name: "perform", callback_for: "Oban.Worker"], []}
iex> Enricher.get_callback_for(node)
"Oban.Worker"
iex> node = {:function_def, [name: "run"], []}
iex> Enricher.get_callback_for(node)
nil
@spec get_op_kind(Metastatic.AST.meta_ast()) :: Metastatic.Semantic.OpKind.t() | nil
Gets the op_kind from a node, if present.
Examples
iex> node = {:function_call, [name: "Repo.get", op_kind: [domain: :db, operation: :retrieve]], []}
iex> Enricher.get_op_kind(node)
[domain: :db, operation: :retrieve]
iex> node = {:function_call, [name: "unknown"], []}
iex> Enricher.get_op_kind(node)
nil