Current section

Files

Jump to
metastatic lib mix tasks metastatic.dead_code.ex
Raw

lib/mix/tasks/metastatic.dead_code.ex

defmodule Mix.Tasks.Metastatic.DeadCode do
@moduledoc """
Detects dead code in a given file.
## Usage
mix metastatic.dead_code FILE [options]
## Options
* `--format` - Output format: text (default), json, or detailed
* `--language` - Source language (auto-detected if not specified)
* `--min-confidence` - Minimum confidence level: low (default), medium, or high
## Examples
# Basic dead code check
mix metastatic.dead_code my_file.py
# JSON output
mix metastatic.dead_code my_file.py --format json
# Only high confidence results
mix metastatic.dead_code my_file.ex --min-confidence high
## Exit Codes
* 0 - No dead code found
* 1 - Dead code detected
* 2 - Error during analysis
"""
@shortdoc "Detects dead code"
use Mix.Task
@dialyzer {:no_return, run: 1}
alias Metastatic.Analysis.DeadCode
alias Metastatic.Builder
@impl Mix.Task
def run(args) do
{opts, files, _invalid} =
OptionParser.parse(args,
strict: [format: :string, language: :string, min_confidence: :string],
aliases: [f: :format, l: :language]
)
format = parse_format(opts[:format])
language = parse_language(opts[:language])
min_confidence = parse_confidence(opts[:min_confidence])
case files do
[] ->
Mix.shell().error("Error: No file specified")
Mix.shell().info("Usage: mix metastatic.dead_code FILE [options]")
exit({:shutdown, 2})
[file | _] ->
analyze_file(file, language, format, min_confidence)
end
end
defp analyze_file(file, language, format, min_confidence) do
unless File.exists?(file) do
Mix.shell().error("Error: File not found: #{file}")
exit({:shutdown, 2})
end
source = File.read!(file)
lang = language || detect_language(file)
case Builder.from_source(source, lang) do
{:ok, document} ->
case DeadCode.analyze(document, min_confidence: min_confidence) do
{:ok, result} ->
output = format_output(result, format)
Mix.shell().info(output)
if result.has_dead_code? do
exit({:shutdown, 1})
else
exit({:shutdown, 0})
end
end
{:error, reason} ->
Mix.shell().error("Parse error: #{inspect(reason)}")
exit({:shutdown, 2})
end
end
defp format_output(result, :json) do
Jason.encode!(DeadCode.Result.to_map(result), pretty: true)
end
defp format_output(result, :detailed) do
summary = result.summary <> "\n\n"
details =
Enum.map_join(result.dead_locations, "\n", fn loc ->
"""
Type: #{loc.type}
Confidence: #{loc.confidence}
Reason: #{loc.reason}
Suggestion: #{loc.suggestion}
---
"""
end)
summary <> details
end
defp format_output(result, :text) do
result.summary
end
defp parse_format(nil), do: :text
defp parse_format("text"), do: :text
defp parse_format("json"), do: :json
defp parse_format("detailed"), do: :detailed
defp parse_format(other) do
Mix.shell().error("Unknown format: #{other}")
Mix.shell().info("Valid formats: text, json, detailed")
exit({:shutdown, 2})
end
defp parse_language(nil), do: nil
defp parse_language("python"), do: :python
defp parse_language("elixir"), do: :elixir
defp parse_language("erlang"), do: :erlang
defp parse_language("ruby"), do: :ruby
defp parse_language("haskell"), do: :haskell
defp parse_language(other) do
Mix.shell().error("Unknown language: #{other}")
Mix.shell().info("Valid languages: python, elixir, erlang, ruby, haskell")
exit({:shutdown, 2})
end
defp parse_confidence(nil), do: :low
defp parse_confidence("low"), do: :low
defp parse_confidence("medium"), do: :medium
defp parse_confidence("high"), do: :high
defp parse_confidence(other) do
Mix.shell().error("Unknown confidence level: #{other}")
Mix.shell().info("Valid levels: low, medium, high")
exit({:shutdown, 2})
end
defp detect_language(file) do
case Path.extname(file) do
".py" ->
:python
".ex" ->
:elixir
".exs" ->
:elixir
".erl" ->
:erlang
".hrl" ->
:erlang
".rb" ->
:ruby
".hs" ->
:haskell
other ->
Mix.shell().error("Cannot detect language from extension: #{other}")
Mix.shell().info("Please specify --language option")
exit({:shutdown, 2})
end
end
end