Metastatic.Semantic.Enricher (Metastatic v0.22.1)

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:

  1. op_kind on :function_call nodes -- classifies calls by semantic domain (database, HTTP, queue, ...) using registered patterns.
  2. callback_for on :function_def nodes -- tags functions that implement a known behaviour/base-class callback (e.g., Oban.Worker perform/1, ActiveJob::Base perform).

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
end

Enrichment 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

Types

Traversal accumulator carrying container context

Language identifier for pattern matching

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

enricher_acc()

@type enricher_acc() :: %{language: language(), behaviours: [String.t()]}

Traversal accumulator carrying container context

language()

Language identifier for pattern matching

Functions

enrich(node, language)

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 enrich
  • language - 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]

enrich_callback(node, language, behaviours)

@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_def MetaAST node
  • language - Source language atom
  • behaviours - 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"

enrich_tree(ast, language)

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 :container node, collects behaviours from its :import children and :parent metadata.
  • On leaving a :function_def inside such a container, checks Callbacks.lookup/4 and adds callback_for: if matched.
  • On leaving a :function_call, adds op_kind: if matched.

Parameters

  • ast - The root MetaAST node
  • language - 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

enriched?(arg1)

@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

get_callback_for(arg1)

@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

get_op_kind(arg1)

@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