Packages
metastatic
0.12.0
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/coupling.ex
defmodule Metastatic.Analysis.Coupling do
@moduledoc """
Coupling analysis for containers (modules/classes).
Measures dependencies between containers. Low coupling is desirable for maintainability.
## Coupling Types
- **Afferent Coupling (Ca)**: Number of containers that depend on this container
- **Efferent Coupling (Ce)**: Number of containers this container depends on
- **Instability (I)**: Ce / (Ca + Ce) - ranges from 0 (stable) to 1 (unstable)
## Assessment
- Low coupling (Ce < 5): Excellent - easy to test and maintain
- Moderate coupling (5 <= Ce < 10): Good - acceptable dependencies
- High coupling (10 <= Ce < 20): Fair - consider refactoring
- Very high coupling (Ce >= 20): Poor - difficult to maintain
## Examples
# Low coupling - no external dependencies
ast = {:container, [container_type: :class, name: "Calculator"], [
{:function_def, [name: "add", params: [{:param, [], "x"}, {:param, [], "y"}], visibility: :public], [
{:binary_op, [category: :arithmetic, operator: :+], [{:variable, [], "x"}, {:variable, [], "y"}]}]}
]}
doc = Document.new(ast, :python)
{:ok, result} = Coupling.analyze(doc)
result.efferent_coupling # => 0
result.assessment # => :excellent
"""
alias Metastatic.Document
@type result :: %{
container_name: String.t() | nil,
efferent_coupling: non_neg_integer(),
dependencies: [String.t()],
instability: float(),
assessment: :excellent | :good | :fair | :poor,
warnings: [String.t()],
recommendations: [String.t()]
}
use Metastatic.Document.Analyzer,
doc: """
Analyzes coupling in a document.
Accepts either a `Metastatic.Document` struct or a `{language, native_ast}` tuple.
"""
@impl Metastatic.Document.Analyzer
def handle_analyze(%Document{ast: ast}, _opts \\ []) do
with {:ok, _type, name, members} <- extract_container(ast),
do: {:ok, analyze_coupling(name, members)}
end
# 3-tuple format
defp extract_container({:container, meta, [members]}) when is_list(meta) do
type = Keyword.get(meta, :container_type, :class)
name = Keyword.get(meta, :name, "anonymous")
members_list = if is_list(members), do: members, else: [members]
{:ok, type, name, members_list}
end
defp extract_container(_), do: {:error, "AST does not contain a container"}
defp analyze_coupling(name, members) do
# Extract dependencies (external types/modules referenced)
dependencies = extract_dependencies(members) |> Enum.uniq() |> Enum.sort()
efferent = length(dependencies)
# Calculate instability (assuming Ca=0 since we don't have cross-file analysis yet)
instability = if efferent > 0, do: 1.0, else: 0.0
{assessment, warnings, recommendations} = assess_coupling(efferent)
%{
container_name: name,
efferent_coupling: efferent,
dependencies: dependencies,
instability: instability,
assessment: assessment,
warnings: warnings,
recommendations: recommendations
}
end
defp extract_dependencies(members) do
members
|> Enum.flat_map(&extract_deps_from_member/1)
|> Enum.uniq()
end
# 3-tuple format
defp extract_deps_from_member({:function_def, meta, [body]}) when is_list(meta),
do: extract_deps_from_ast(body)
defp extract_deps_from_member({:property, meta, [getter, setter]}) when is_list(meta) do
extract_deps_from_ast(getter) ++ extract_deps_from_ast(setter)
end
defp extract_deps_from_member(_), do: []
defp extract_deps_from_ast(nil), do: []
defp extract_deps_from_ast(ast) do
case ast do
# Function calls to external modules (Module.function pattern) - 3-tuple
{:attribute_access, _meta, [{:variable, _, module}, _func]}
when module not in ["self", "this", "@"] ->
[module]
# Function call - 3-tuple
{:function_call, meta, args} when is_list(meta) and is_list(args) ->
name = Keyword.get(meta, :name, "")
# Check if it's a qualified call (e.g., "Math.sqrt")
deps =
if is_binary(name) and String.contains?(name, ".") do
[name |> String.split(".") |> hd()]
else
[]
end
deps ++ Enum.flat_map(args, &extract_deps_from_ast/1)
# Binary op - 3-tuple
{:binary_op, _meta, [left, right]} ->
extract_deps_from_ast(left) ++ extract_deps_from_ast(right)
# Conditional - 3-tuple
{:conditional, _meta, [cond_expr, then_br, else_br]} ->
extract_deps_from_ast(cond_expr) ++
extract_deps_from_ast(then_br) ++
extract_deps_from_ast(else_br)
# Block - 3-tuple
{:block, _meta, stmts} when is_list(stmts) ->
Enum.flat_map(stmts, &extract_deps_from_ast/1)
# Assignment - 3-tuple
{:assignment, _meta, [_target, value]} ->
extract_deps_from_ast(value)
# Augmented assignment - 3-tuple
{:augmented_assignment, _meta, [_target, value]} ->
extract_deps_from_ast(value)
# Loop - 3-tuple
{:loop, meta, children} when is_list(meta) and is_list(children) ->
Enum.flat_map(children, &extract_deps_from_ast/1)
_ ->
[]
end
end
defp assess_coupling(efferent) do
{assessment, warnings, recommendations} =
cond do
efferent < 5 ->
{:excellent, [], ["Low coupling - excellent for maintainability and testing"]}
efferent < 10 ->
{:good, [],
[
"Moderate coupling - acceptable but monitor growth",
"Consider dependency injection for better testability"
]}
efferent < 20 ->
{:fair,
["High coupling (#{efferent} dependencies) - may be difficult to test and maintain"],
[
"Refactor to reduce dependencies",
"Use interfaces/protocols to decouple from concrete implementations",
"Consider the Dependency Inversion Principle"
]}
true ->
{:poor,
[
"Very high coupling (#{efferent} dependencies) - serious maintainability issue",
"Changes to dependencies will likely require changes to this container"
],
[
"Major refactoring recommended",
"Split container into smaller, focused units",
"Introduce abstractions to reduce direct dependencies",
"Apply SOLID principles, especially Single Responsibility"
]}
end
{assessment, warnings, recommendations}
end
end