# `Reach.Plugin`
[🔗](https://github.com/elixir-vibe/reach/blob/v2.8.4/lib/reach/plugin.ex#L1)

Behaviour for library-specific analysis plugins.

Plugins extend Reach in three ways:

1. **Graph edges** — `analyze/2` and `analyze_project/3` add domain-specific
   edges to the dependence graph (framework dispatch, message routing, etc.)

2. **Effect classification** — `classify_effect/1` teaches the effect
   classifier about framework-specific calls (Ecto queries are pure,
   Repo writes are `:write`, etc.)

3. **Source frontends** — optional `source_extensions/0` and `parse_file/2`
   callbacks let plugins own language frontends for optional ecosystems.

4. **Elixir AST lowering** — `lower_elixir_ast/2` translates framework
   DSL forms, sigils, or template syntax into analysis-friendly Elixir AST.

5. **Embedded IR** — `analyze_embedded/2` extracts code from string
   literals (e.g. JS inside QuickBEAM.eval) and returns additional IR
   nodes plus cross-language edges.

6. **Framework presentation/patterns** — optional callbacks provide
   framework-specific trace presets, behaviour labels, and visualization
   edge filtering.

7. **Reinterpreted DSL semantics** — `reinterpreted_ast?/1` identifies macro
   ranges where generic Elixir smell/evidence rules do not apply.

8. **External-data provenance** — `external_data_source/1` identifies
   dependency-owned decoder call shapes without hardcoding libraries in generic evidence.

## 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
    end

## Built-in plugins

Plugins for Phoenix, Ecto, Ash, Nx, Oban, GenStage, Jido, OpenTelemetry,
and Gettext 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: [])

# `edge_spec`

```elixir
@type edge_spec() :: {Reach.IR.Node.id(), Reach.IR.Node.id(), term()}
```

# `embedded_result`

```elixir
@type embedded_result() :: {[Reach.IR.Node.t()], [edge_spec()]}
```

# `analyze`

```elixir
@callback analyze(all_nodes :: [Reach.IR.Node.t()], opts :: keyword()) :: [edge_spec()]
```

Analyzes IR nodes from a single module and returns edges to add.

# `analyze_embedded`
*optional* 

```elixir
@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.

# `analyze_project`
*optional* 

```elixir
@callback analyze_project(
  modules :: %{required(module()) =&gt; 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.

# `behaviour_label`
*optional* 

```elixir
@callback behaviour_label(callbacks :: [atom()]) :: String.t() | nil
```

# `classify_effect`
*optional* 

```elixir
@callback classify_effect(node :: Reach.IR.Node.t()) :: Reach.Effects.effect() | 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.

# `evidence_providers`
*optional* 

```elixir
@callback evidence_providers() :: [module()]
```

# `expected_effect_boundary?`
*optional* 

```elixir
@callback expected_effect_boundary?(module(), atom(), non_neg_integer()) ::
  boolean() | nil
```

# `external_data_source`
*optional* 

```elixir
@callback external_data_source(ast :: Macro.t()) :: String.t() | nil
```

# `ignore_call_edge?`
*optional* 

```elixir
@callback ignore_call_edge?(Graph.Edge.t()) :: boolean()
```

# `inference_hints`
*optional* 

```elixir
@callback inference_hints() :: %{
  optional(:deps) =&gt; [atom()],
  optional(:source) =&gt; [String.t()]
}
```

# `lower_elixir_ast`
*optional* 

```elixir
@callback lower_elixir_ast(ast :: Macro.t(), opts :: keyword()) ::
  {:ok, Macro.t()} | :ignore | {:error, term()}
```

Lowers framework-specific Elixir AST into ordinary Elixir AST before Reach IR translation.

Return `:ignore` when the plugin does not own the AST node. Returned AST may
carry `%Reach.Source.Origin{}` metadata under the `:reach` metadata key.

# `macro_facts`
*optional* 

```elixir
@callback macro_facts(ast :: Macro.t(), context :: map()) :: [Reach.MacroFact.t()]
```

# `parse_file`
*optional* 

```elixir
@callback parse_file(path :: Path.t(), opts :: keyword()) ::
  {:ok, [Reach.IR.Node.t()]} | {:error, term()}
```

# `refine_evidence`
*optional* 

```elixir
@callback refine_evidence(evidence :: struct() | map(), context :: map()) ::
  struct() | map() | :unchanged
```

# `refine_macro_fact`
*optional* 

```elixir
@callback refine_macro_fact(fact :: Reach.MacroFact.t(), context :: map()) ::
  Reach.MacroFact.t() | map() | :unchanged
```

# `reinterpreted_ast?`
*optional* 

```elixir
@callback reinterpreted_ast?(ast :: Macro.t()) :: boolean()
```

Returns whether an AST node introduces a DSL that reinterprets ordinary Elixir semantics.

# `smell_checks`
*optional* 

```elixir
@callback smell_checks() :: [module()]
```

# `source_extensions`
*optional* 

```elixir
@callback source_extensions() :: [String.t()]
```

# `source_language`
*optional* 

```elixir
@callback source_language(extension :: String.t()) :: atom() | nil
```

# `trace_pattern`
*optional* 

```elixir
@callback trace_pattern(pattern :: String.t()) :: (Reach.IR.Node.t() -&gt; boolean()) | nil
```

# `trace_preset`
*optional* 

```elixir
@callback trace_preset(pattern :: String.t()) :: Reach.Trace.Pattern.Preset.t() | nil
```

# `behaviour_label`

Infers a framework-specific behaviour label from callback names.

# `built_in_plugins`

Returns built-in plugin modules known to Reach.

# `classify_effect`

Asks each plugin to classify a call node's effect.

Returns the first non-nil result, or `nil` if no plugin matches.

# `classify_effect_with_plugin`

Classifies a call node and returns the plugin that supplied the result.

Returns `{effect, plugin}` for the first matching plugin, or `nil`.

# `detect`

Returns the list of auto-detected plugins based on loaded dependencies.

# `evidence_providers`

Returns evidence providers contributed by plugins.

# `expected_effect_boundary?`

Returns true when plugins recognize an expected effect boundary callback.

# `external_data_source`

Returns a plugin-owned label when an AST node decodes external data.

# `ignore_call_edge?`

Returns true when a plugin marks a call-graph edge as visualization noise.

# `lower_elixir_ast`

Asks plugins to lower a framework-specific Elixir AST node.

# `macro_facts`

Collects source-first macro/DSL facts contributed by plugins.

# `parse_file`

Parses a source file with the first plugin frontend that accepts its extension.

# `refine_evidence`

Lets plugins annotate or adjust evidence facts without owning user-facing policy.

# `refine_macro_fact`

Lets plugins annotate source-first macro/DSL facts without owning generic policy.

# `reinterpreted_ast?`

Returns whether any configured plugin owns reinterpreted semantics for an AST node.

# `resolve`

Resolves plugins from options, falling back to auto-detection.

# `run_analyze`

Runs module-local analysis hooks for the configured plugins.

# `run_analyze_embedded`

Runs embedded-node analysis hooks for plugins that provide them.

# `run_analyze_project`

Runs project-level analysis hooks for plugins that provide them.

# `smell_checks`

Returns smell checks contributed by plugins.

# `source_extensions`

Returns source extensions handled by plugins.

# `source_language`

Returns the source language for an extension handled by a plugin.

# `trace_pattern`

Compiles a framework-specific trace pattern, if a plugin recognizes it.

# `trace_preset`

Returns the first plugin-provided named trace preset.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
