Packages
Library for accessibility snapshot testing in Phoenix applications using axe-core and Playwright.
Current section
Files
Jump to
Current section
Files
lib/telemetry_capture/analyzers/assign_lifecycle.ex
defmodule Excessibility.TelemetryCapture.Analyzers.AssignLifecycle do
@moduledoc """
Analyzes assign lifecycle patterns.
Detects assigns that are set once and never change ("dead state").
These may indicate unused data being carried in socket assigns.
Uses `key_state` from timeline events to track which assigns change.
## Output
%{
findings: [
%{
severity: :info,
message: "2 assign(s) never changed: [:static_config, :user_id]",
events: [],
metadata: %{stale_assigns: [:static_config, :user_id]}
}
],
stats: %{
total_assigns: 5,
active_assigns: 3,
stale_assigns: 2
}
}
"""
@behaviour Excessibility.TelemetryCapture.Analyzer
def name, do: :assign_lifecycle
def default_enabled?, do: true
def analyze(%{timeline: []}, _opts) do
%{findings: [], stats: %{total_assigns: 0, active_assigns: 0, stale_assigns: 0}}
end
def analyze(%{timeline: timeline}, _opts) when length(timeline) < 2 do
%{findings: [], stats: %{total_assigns: 0, active_assigns: 0, stale_assigns: 0}}
end
def analyze(%{timeline: timeline}, _opts) do
all_keys = collect_all_keys(timeline)
changing_keys = find_changing_keys(timeline)
stale_keys = all_keys |> MapSet.difference(changing_keys) |> MapSet.to_list()
stats = %{
total_assigns: MapSet.size(all_keys),
active_assigns: MapSet.size(changing_keys),
stale_assigns: length(stale_keys)
}
findings =
if length(stale_keys) > 0 and length(timeline) >= 3 do
[
%{
severity: :info,
message: "#{length(stale_keys)} assign(s) never changed: #{inspect(Enum.sort(stale_keys))}",
events: [],
metadata: %{stale_assigns: stale_keys}
}
]
else
[]
end
%{findings: findings, stats: stats}
end
defp collect_all_keys(timeline) do
timeline
|> Enum.flat_map(fn entry ->
entry
|> Map.get(:key_state, %{})
|> Map.keys()
end)
|> MapSet.new()
end
defp find_changing_keys(timeline) do
timeline
|> Enum.chunk_every(2, 1, :discard)
|> Enum.flat_map(fn [prev, curr] ->
prev_state = Map.get(prev, :key_state, %{})
curr_state = Map.get(curr, :key_state, %{})
curr_state
|> Map.keys()
|> Enum.filter(fn key ->
Map.get(prev_state, key) != Map.get(curr_state, key)
end)
end)
|> MapSet.new()
end
end