Packages
metastatic
0.7.4
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/metastatic/analysis/state_management.ex
defmodule Metastatic.Analysis.StateManagement do
@moduledoc """
State management analysis for containers (modules/classes).
Analyzes how containers manage state, identifying patterns and potential issues.
## Patterns Detected
- **Stateless**: No instance state (best for immutability)
- **Immutable State**: State set once, never modified
- **Controlled Mutation**: State modified through encapsulated methods
- **Uncontrolled Mutation**: Direct state modification
## Examples
# Stateless container
ast = {:container, :class, "Math", %{}, [
{:function_def, :public, "add", ["x", "y"], %{},
{:binary_op, :arithmetic, :+, {:variable, "x"}, {:variable, "y"}}}
]}
doc = Document.new(ast, :python)
{:ok, result} = StateManagement.analyze(doc)
result.pattern # => :stateless
# Controlled mutation
ast = {:container, :class, "Counter", %{}, [
{:function_def, :public, "increment", [], %{},
{:augmented_assignment, :+,
{:attribute_access, {:variable, "self"}, "count"}, {:literal, :integer, 1}}}
]}
{:ok, result} = StateManagement.analyze(doc)
result.pattern # => :controlled_mutation
"""
alias Metastatic.{Analysis.StateManagement.Result, Document}
use Metastatic.Document.Analyzer,
doc: """
Analyze state management of a container.
## Examples
iex> ast = {:container, :class, "Empty", %{}, []}
iex> doc = Metastatic.Document.new(ast, :python)
iex> {:ok, result} = Metastatic.Analysis.StateManagement.analyze(doc)
iex> result.pattern
:stateless
"""
@impl Metastatic.Document.Analyzer
def handle_analyze(%Document{ast: ast}, _opts \\ []) do
with {:ok, container_type, container_name, members} <- extract_container(ast),
do: {:ok, analyze_container(container_type, container_name, members)}
end
# Private implementation
defp extract_container({:container, type, name, _metadata, members}) do
{:ok, type, name, members}
end
defp extract_container(_), do: {:error, "AST does not contain a container"}
defp analyze_container(container_type, container_name, members) do
methods = Enum.filter(members, &match?({:function_def, _, _, _, _, _}, &1))
# Collect all state variables
all_state = collect_all_state(members)
state_count = length(all_state)
# Identify initialized vs uninitialized
{initialized, uninitialized} = categorize_initialization(members, all_state)
# Track mutations
mutations = collect_mutations(methods)
mutation_count = length(mutations)
mutable_state = mutations |> Enum.map(& &1.state_var) |> Enum.uniq()
# Identify read-only state
read_only_state = all_state -- mutable_state
# Determine pattern
pattern = Result.identify_pattern(state_count, mutation_count, read_only_state, mutable_state)
# Generate assessment
warnings = Result.generate_warnings(pattern, mutation_count, uninitialized)
assessment = Result.assess(pattern, mutation_count, warnings)
recommendations = Result.generate_recommendations(pattern, state_count)
%Result{
container_name: container_name,
container_type: container_type,
pattern: pattern,
state_count: state_count,
mutation_count: mutation_count,
mutations: mutations,
initialized_state: Enum.sort(initialized),
uninitialized_state: Enum.sort(uninitialized),
read_only_state: Enum.sort(read_only_state),
mutable_state: Enum.sort(mutable_state),
assessment: assessment,
warnings: warnings,
recommendations: recommendations
}
end
# Collect all state variables accessed in the container
defp collect_all_state(members) do
members
|> Enum.flat_map(&extract_state_vars/1)
|> Enum.uniq()
end
defp extract_state_vars({:function_def, _, _, _, _, body}), do: extract_state_from_ast(body)
defp extract_state_vars({:property, _, getter, setter, _}),
do: extract_state_from_ast(getter) ++ extract_state_from_ast(setter)
defp extract_state_vars(_), do: []
defp extract_state_from_ast(nil), do: []
defp extract_state_from_ast(ast) do
case ast do
{:attribute_access, {:variable, var}, attr} when var in ["self", "this", "@"] ->
[attr]
{:assignment, target, value} ->
extract_state_from_ast(target) ++ extract_state_from_ast(value)
{:augmented_assignment, _op, target, value} ->
extract_state_from_ast(target) ++ extract_state_from_ast(value)
{:binary_op, _, _, left, right} ->
extract_state_from_ast(left) ++ extract_state_from_ast(right)
{:conditional, cond, then_br, else_br} ->
extract_state_from_ast(cond) ++
extract_state_from_ast(then_br) ++
extract_state_from_ast(else_br)
{:block, stmts} when is_list(stmts) ->
Enum.flat_map(stmts, &extract_state_from_ast/1)
{:function_call, _, args} ->
Enum.flat_map(args, &extract_state_from_ast/1)
_ ->
[]
end
end
# Categorize state as initialized or uninitialized
defp categorize_initialization(members, all_state) do
# Look for initialization methods (constructor, __init__, initialize, etc.)
init_methods =
members
|> Enum.filter(fn
{:function_def, _, name, _, _, _} ->
name in ["__init__", "initialize", "constructor", "init"]
_ ->
false
end)
# Extract state initialized in these methods
initialized =
init_methods
|> Enum.flat_map(fn {:function_def, _, _, _, _, body} ->
find_initialized_state(body)
end)
|> Enum.uniq()
uninitialized = all_state -- initialized
{initialized, uninitialized}
end
defp find_initialized_state(ast) do
case ast do
{:assignment, {:attribute_access, {:variable, var}, attr}, _}
when var in ["self", "this", "@"] ->
[attr]
{:block, stmts} when is_list(stmts) ->
Enum.flat_map(stmts, &find_initialized_state/1)
_ ->
[]
end
end
# Collect all mutations in methods
defp collect_mutations(methods) do
methods
|> Enum.flat_map(fn {:function_def, _vis, name, _, _, body} ->
find_mutations(body, name)
end)
end
defp find_mutations(ast, location) do
case ast do
{:assignment, {:attribute_access, {:variable, var}, attr}, _}
when var in ["self", "this", "@"] ->
[Result.mutation(location, attr, :assignment)]
{:augmented_assignment, _op, {:attribute_access, {:variable, var}, attr}, _}
when var in ["self", "this", "@"] ->
[Result.mutation(location, attr, :augmented_assignment)]
{:block, stmts} when is_list(stmts) ->
Enum.flat_map(stmts, &find_mutations(&1, location))
{:conditional, _cond, then_br, else_br} ->
find_mutations(then_br, location) ++ find_mutations(else_br, location)
{:loop, :while, _condition, body} ->
find_mutations(body, location)
{:loop, _, _iter, _coll, body} ->
find_mutations(body, location)
_ ->
[]
end
end
end