Packages
metastatic
0.13.1
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/mix/tasks/metastatic.code_smells.ex
defmodule Mix.Tasks.Metastatic.CodeSmells do
@shortdoc "Detects code smells"
@moduledoc """
Detects code smells in a given source file.
## Usage
mix metastatic.code_smells FILE [options]
## Options
* `--format` - Output format: text (default) or json
* `--language` - Source language: python, elixir, erlang, ruby, or haskell (auto-detected if not specified)
## Examples
# Basic code smell detection
mix metastatic.code_smells my_file.py
# JSON output
mix metastatic.code_smells my_file.ex --format json
# Explicit language
mix metastatic.code_smells my_file.rb --language ruby
## Detected Smells
* Long functions (>20 statements)
* Deep nesting (>4 levels)
* High cyclomatic complexity (>10)
* High cognitive complexity (>15)
* Magic numbers (unexplained literals)
* Complex conditionals (>3 boolean operators)
## Exit Codes
* 0 - No smells detected
* 1 - Code smells found
* 2 - Error during analysis
"""
use Mix.Task
@dialyzer {:no_return, run: 1}
alias Metastatic.Analysis.Smells
alias Metastatic.Builder
@impl Mix.Task
def run(args) do
{opts, files, _} = OptionParser.parse(args, strict: [format: :string, language: :string])
case files do
[] ->
Mix.shell().error("Usage: mix metastatic.code_smells FILE")
exit({:shutdown, 2})
[file | _] ->
analyze(file, opts[:language], opts[:format] || "text")
end
end
defp analyze(file, language, format) do
unless File.exists?(file),
do:
(
Mix.shell().error("File not found")
exit({:shutdown, 2})
)
source = File.read!(file)
lang = language || detect_lang(file)
case Builder.from_source(source, String.to_atom(lang)) do
{:ok, doc} ->
{:ok, result} = Smells.analyze(doc)
output =
if format == "json",
do: Jason.encode!(Smells.Result.to_map(result), pretty: true),
else: result.summary
Mix.shell().info(output)
exit({:shutdown, if(result.has_smells?, do: 1, else: 0)})
{:error, reason} ->
Mix.shell().error("Parse error: #{inspect(reason)}")
exit({:shutdown, 2})
end
end
defp detect_lang(file) do
case Path.extname(file) do
".py" ->
"python"
".ex" ->
"elixir"
".exs" ->
"elixir"
".erl" ->
"erlang"
".rb" ->
"ruby"
".hs" ->
"haskell"
_ ->
Mix.shell().error("Cannot detect language")
exit({:shutdown, 2})
end
end
end