Behaviour for library-specific analysis plugins.
Plugins extend Reach in three ways:
Graph edges —
analyze/2andanalyze_project/3add domain-specific edges to the dependence graph (framework dispatch, message routing, etc.)Effect classification —
classify_effect/1teaches the effect classifier about framework-specific calls (Ecto queries are pure, Repo writes are:write, etc.)Embedded IR —
analyze_embedded/2extracts code from string literals (e.g. JS inside QuickBEAM.eval) and returns additional IR nodes plus cross-language edges.Framework presentation/patterns — optional callbacks provide framework-specific trace presets, behaviour labels, and visualization edge filtering.
Implementing a plugin
defmodule MyPlugin do
@behaviour Reach.Plugin
@impl true
def analyze(all_nodes, _opts), do: []
@impl true
def classify_effect(%Reach.IR.Node{type: :call, meta: %{function: :my_pure_fn}}), do: :pure
def classify_effect(_), do: nil
endBuilt-in plugins
Plugins for Phoenix, Ecto, Oban, GenStage, Jido, and OpenTelemetry
are included and auto-detected at runtime. Override with the
:plugins option:
Reach.Project.from_mix_project(plugins: [Reach.Plugins.Ecto])Disable auto-detection:
Reach.string_to_graph!(source, plugins: [])
Summary
Callbacks
Analyzes IR nodes from a single module and returns edges to add.
Extracts embedded code from IR nodes (e.g. JS strings passed to QuickBEAM.eval) and returns additional IR nodes plus edges connecting them to the host graph.
Analyzes IR nodes across all modules in a project.
Classifies the effect of a call node.
Functions
Infers a framework-specific behaviour label from callback names.
Asks each plugin to classify a call node's effect.
Returns the list of auto-detected plugins based on loaded dependencies.
Returns true when a plugin marks a call-graph edge as visualization noise.
Resolves plugins from options, falling back to auto-detection.
Runs module-local analysis hooks for the configured plugins.
Runs embedded-node analysis hooks for plugins that provide them.
Runs project-level analysis hooks for plugins that provide them.
Compiles a framework-specific trace pattern, if a plugin recognizes it.
Types
@type edge_spec() :: {Reach.IR.Node.id(), Reach.IR.Node.id(), term()}
@type embedded_result() :: {[Reach.IR.Node.t()], [edge_spec()]}
Callbacks
@callback analyze(all_nodes :: [Reach.IR.Node.t()], opts :: keyword()) :: [edge_spec()]
Analyzes IR nodes from a single module and returns edges to add.
@callback analyze_embedded(all_nodes :: [Reach.IR.Node.t()], opts :: keyword()) :: embedded_result()
Extracts embedded code from IR nodes (e.g. JS strings passed to QuickBEAM.eval) and returns additional IR nodes plus edges connecting them to the host graph.
@callback analyze_project( modules :: %{required(module()) => map()}, all_nodes :: [Reach.IR.Node.t()], opts :: keyword() ) :: [edge_spec()]
Analyzes IR nodes across all modules in a project.
Only needed for cross-module patterns like router→controller dispatch or job enqueue→perform flow.
@callback classify_effect(node :: Reach.IR.Node.t()) :: atom() | nil
Classifies the effect of a call node.
Return an effect atom (:pure, :read, :write, :io, :send,
:exception) or nil to defer to the next classifier.
@callback ignore_call_edge?(Graph.Edge.t()) :: boolean()
@callback trace_pattern(pattern :: String.t()) :: (Reach.IR.Node.t() -> boolean()) | nil
Functions
Infers a framework-specific behaviour label from callback names.
Asks each plugin to classify a call node's effect.
Returns the first non-nil result, or nil if no plugin matches.
Returns the list of auto-detected plugins based on loaded dependencies.
Returns true when a plugin marks a call-graph edge as visualization noise.
Resolves plugins from options, falling back to auto-detection.
Runs module-local analysis hooks for the configured plugins.
Runs embedded-node analysis hooks for plugins that provide them.
Runs project-level analysis hooks for plugins that provide them.
Compiles a framework-specific trace pattern, if a plugin recognizes it.