Packages
metastatic
0.10.2
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/complexity/formatter.ex
defmodule Metastatic.Analysis.Complexity.Formatter do
@moduledoc """
Formats complexity analysis results for different output formats.
Supports three output formats:
- `:text` - Human-readable text format with ANSI colors
- `:json` - Machine-readable JSON format
- `:detailed` - Extended text format with recommendations
"""
alias Metastatic.Analysis.Complexity.Result
@doc """
Formats a complexity result in the specified format.
## Formats
- `:text` - Default human-readable format
- `:json` - JSON format for programmatic use
- `:detailed` - Extended format with analysis breakdown
## Examples
iex> result = Metastatic.Analysis.Complexity.Result.new(%{
...> cyclomatic: 5,
...> cognitive: 7,
...> max_nesting: 2,
...> halstead: %{volume: 100.0, difficulty: 5.0, effort: 500.0},
...> loc: %{logical: 20, physical: 30},
...> function_metrics: %{statement_count: 20, return_points: 1, variable_count: 5}
...> })
iex> output = Metastatic.Analysis.Complexity.Formatter.format(result, :text)
iex> String.contains?(output, "Cyclomatic Complexity")
true
"""
@spec format(Result.t(), atom()) :: String.t()
def format(result, :text), do: format_text(result)
def format(result, :json), do: format_json(result)
def format(result, :detailed), do: format_detailed(result)
# Private implementation
defp format_text(result) do
per_function_section = format_per_function(result.per_function)
"""
Complexity Analysis Results:
Cyclomatic Complexity: #{result.cyclomatic}#{warning_indicator(result, :cyclomatic)}
Cognitive Complexity: #{result.cognitive}#{warning_indicator(result, :cognitive)}
Max Nesting Depth: #{result.max_nesting}#{warning_indicator(result, :nesting)}
Halstead Metrics:
Volume: #{format_float(result.halstead[:volume])}
Difficulty: #{format_float(result.halstead[:difficulty])}
Effort: #{format_float(result.halstead[:effort])}
Lines of Code:
Logical: #{result.loc[:logical] || 0}#{warning_indicator(result, :loc)}
Physical: #{result.loc[:physical] || 0}
Function Metrics:
Statements: #{result.function_metrics[:statement_count] || 0}
Return Points: #{result.function_metrics[:return_points] || 0}
Variables: #{result.function_metrics[:variable_count] || 0}
#{per_function_section}
Summary: #{result.summary}
"""
|> String.trim()
end
defp format_per_function([]), do: ""
defp format_per_function(functions) do
formatted =
Enum.map_join(functions, "\n", fn func ->
" #{func.name}: CC=#{func.cyclomatic}, Cog=#{func.cognitive}, Nest=#{func.max_nesting}, Stmts=#{func.statements}, Vars=#{func.variables}"
end)
"\n Per-Function Breakdown:\n" <> formatted <> "\n"
end
defp format_json(result) do
data = %{
cyclomatic: result.cyclomatic,
cognitive: result.cognitive,
max_nesting: result.max_nesting,
halstead: result.halstead,
loc: result.loc,
function_metrics: result.function_metrics,
per_function: result.per_function,
warnings: result.warnings,
summary: result.summary
}
Jason.encode!(data, pretty: true)
end
defp format_detailed(result) do
text = format_text(result)
warnings_section =
if Enum.empty?(result.warnings) do
"\n\nNo warnings detected."
else
warnings = Enum.map_join(result.warnings, "\n - ", & &1)
"\n\nWarnings:\n - #{warnings}"
end
recommendations = generate_recommendations(result)
recommendations_section =
if Enum.empty?(recommendations) do
""
else
recs = Enum.map_join(recommendations, "\n - ", & &1)
"\n\nRecommendations:\n - #{recs}"
end
text <> warnings_section <> recommendations_section
end
defp warning_indicator(result, type) do
relevant_warnings =
result.warnings
|> Enum.filter(fn warning ->
case type do
:cyclomatic -> String.contains?(warning, "Cyclomatic")
:cognitive -> String.contains?(warning, "Cognitive")
:nesting -> String.contains?(warning, "Nesting")
:loc -> String.contains?(warning, "Logical lines")
_ -> false
end
end)
if Enum.empty?(relevant_warnings) do
""
else
" [WARNING]"
end
end
defp format_float(nil), do: "0.0"
defp format_float(value) when is_float(value), do: :erlang.float_to_binary(value, decimals: 1)
defp format_float(value), do: to_string(value)
defp generate_recommendations(result) do
recommendations = []
recommendations =
if result.cyclomatic > 10 do
[
"Consider breaking down complex logic (cyclomatic > 10) into smaller functions"
| recommendations
]
else
recommendations
end
recommendations =
if result.cognitive > 15 do
[
"Reduce nesting depth and conditional complexity (cognitive > 15) for better readability"
| recommendations
]
else
recommendations
end
recommendations =
if result.max_nesting > 3 do
[
"Deeply nested code (depth > 3) is hard to understand. Extract nested logic into functions"
| recommendations
]
else
recommendations
end
logical_loc = get_in(result.loc, [:logical]) || 0
recommendations =
if logical_loc > 50 do
[
"Function length (#{logical_loc} LoC) exceeds 50 lines. Consider splitting it"
| recommendations
]
else
recommendations
end
Enum.reverse(recommendations)
end
end