Search documentation of Ragex

# Ragex.Analysis.Smells (Ragex v0.4.0)

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

### Types

directory_result()
location()
smell_result()
smell_with_location()

### Functions

analyze_directory(path, opts \\ [])

Analyzes all files in a directory for code smells.

analyze_file(path, opts \\ [])

Analyzes a single file for code smells.

default_thresholds()

Gets default thresholds for smell detection.

detect_smells(path, opts \\ [])

Detects code smells in a directory.

filter_by_severity(results, min_severity)

Filters smell results by minimum severity level.

filter_by_type(results, smell_type)

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)