Ragex.Analysis.Smells (Ragex v0.10.1)

View Source

Code smell detection using Metastatic.Analysis.Smells.

Provides file and directory-level code smell detection with configurable thresholds, parallel processing, and detailed reporting.

Usage

alias Ragex.Analysis.Smells

# Analyze single file
{:ok, result} = Smells.analyze_file("lib/my_module.ex")

# Analyze with custom thresholds
{:ok, result} = Smells.analyze_file("lib/my_module.ex",
  thresholds: %{max_statements: 30, max_nesting: 3})

# Analyze directory
{:ok, results} = Smells.analyze_directory("lib/",
  recursive: true,
  parallel: true)

# Filter by severity
critical = Smells.filter_by_severity(results, :critical)

Detected Smells

  • Long function - Too many statements (default threshold: 50)
  • Deep nesting - Excessive nesting depth (default threshold: 4)
  • Magic numbers - Unexplained numeric literals
  • Complex conditionals - Deeply nested boolean operations
  • Long parameter list - Too many parameters (default threshold: 5)

Summary

Functions

Analyzes all files in a directory for code smells.

Analyzes a single file for code smells.

Gets default thresholds for smell detection.

Detects code smells in a directory.

Filters smell results by minimum severity level.

Filters results by smell type.

Types

directory_result()

@type directory_result() :: %{
  total_files: non_neg_integer(),
  files_with_smells: non_neg_integer(),
  total_smells: non_neg_integer(),
  by_severity: %{required(atom()) => non_neg_integer()},
  by_type: %{required(atom()) => non_neg_integer()},
  results: [smell_result()],
  summary: String.t()
}

location()

@type location() :: %{
  optional(:module) => atom(),
  optional(:function) => atom(),
  optional(:arity) => non_neg_integer(),
  optional(:line) => non_neg_integer(),
  optional(:formatted) => String.t()
}

smell_result()

@type smell_result() :: %{
  path: String.t(),
  language: atom(),
  has_smells?: boolean(),
  total_smells: non_neg_integer(),
  smells: [smell_with_location()],
  by_severity: %{required(atom()) => non_neg_integer()},
  by_type: %{required(atom()) => non_neg_integer()},
  summary: String.t(),
  timestamp: DateTime.t()
}

smell_with_location()

@type smell_with_location() :: %{
  type: atom(),
  severity: atom(),
  description: String.t(),
  suggestion: String.t(),
  context: map(),
  location: location() | nil
}

Functions

analyze_directory(path, opts \\ [])

@spec analyze_directory(path :: String.t(), opts :: keyword()) ::
  {:ok, directory_result()} | {:error, term()}

Analyzes all files in a directory for code smells.

Options

  • :recursive - Recursively analyze subdirectories (default: true)
  • :thresholds - Map of threshold overrides
  • :parallel - Use parallel processing (default: true)
  • :max_concurrency - Maximum concurrent analyses (default: System.schedulers_online())
  • :min_severity - Minimum severity to include (:low, :medium, :high, :critical)

Examples

{:ok, results} = Smells.analyze_directory("lib/",
  recursive: true,
  parallel: true,
  min_severity: :medium)

analyze_file(path, opts \\ [])

@spec analyze_file(path :: String.t(), opts :: keyword()) ::
  {:ok, smell_result()} | {:error, term()}

Analyzes a single file for code smells.

Options

  • :thresholds - Map of threshold overrides
  • :language - Explicit language (default: auto-detect)

Examples

{:ok, result} = Smells.analyze_file("lib/my_module.ex")
result.has_smells?  # => true/false
result.total_smells # => 3

default_thresholds()

@spec default_thresholds() :: map()

Gets default thresholds for smell detection.

Examples

iex> Ragex.Analysis.Smells.default_thresholds()
%{max_statements: 50, max_nesting: 4, max_parameters: 5, max_cognitive: 15}

detect_smells(path, opts \\ [])

@spec detect_smells(
  String.t(),
  keyword()
) :: {:ok, [map()]} | {:error, term()}

Detects code smells in a directory.

Alias for analyze_directory/2. Provided for API consistency with mix tasks.

Examples

{:ok, smells} = Smells.detect_smells("lib/")

filter_by_severity(results, min_severity)

@spec filter_by_severity([smell_result()], atom()) :: [smell_result()]

Filters smell results by minimum severity level.

Severity Levels

  • :low - Include all smells
  • :medium - Include medium, high, and critical
  • :high - Include high and critical only
  • :critical - Include critical only

Examples

critical_smells = Smells.filter_by_severity(results, :critical)

filter_by_type(results, smell_type)

@spec filter_by_type([smell_result()], atom()) :: [smell_result()]

Filters results by smell type.

Examples

magic_numbers = Smells.filter_by_type(results, :magic_number)