Ragex.Analysis.Quality (Ragex v0.10.0)

View Source

High-level API for code quality analysis.

Provides a unified interface for analyzing code quality metrics and storing/querying results. Combines MetastaticBridge for analysis and QualityStore for persistence.

Features

  • File and directory analysis with complexity metrics
  • Automatic storage in knowledge graph
  • Quality reporting and statistics
  • Finding complex code patterns
  • Purity analysis

Usage

alias Ragex.Analysis.Quality

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

# Analyze with options
{:ok, result} = Quality.analyze_file("lib/my_module.ex",
  metrics: [:cyclomatic, :cognitive],
  store: true
)

# Analyze directory
{:ok, results} = Quality.analyze_directory("lib/")

# Get statistics
stats = Quality.statistics()

# Find complex files
complex = Quality.find_complex(metric: :cyclomatic, threshold: 10)

Summary

Functions

Analyzes all files in a directory.

Analyzes a single file for code quality metrics.

Analyzes a specific function's quality metrics.

Analyzes all functions in a module.

Analyzes directory quality metrics.

Clears all stored quality metrics.

Generates a comprehensive report including both quality and security metrics.

Returns the number of files with stored quality metrics.

Finds files exceeding complexity thresholds.

Finds complex code in a directory.

Finds impure files (files with side effects).

Finds files with analysis warnings.

Generates a comprehensive quality report.

Retrieves stored quality metrics for a file.

Returns the most complex files.

Returns project-wide quality statistics.

Returns quality statistics grouped by language.

Types

analysis_result()

@type analysis_result() :: %{
  path: String.t(),
  language: atom(),
  complexity: map(),
  purity: map(),
  warnings: [String.t()],
  timestamp: DateTime.t()
}

quality_report()

@type quality_report() :: %{
  total_files: non_neg_integer(),
  avg_cyclomatic: float(),
  avg_cognitive: float(),
  avg_nesting: float(),
  max_cyclomatic: non_neg_integer(),
  max_cognitive: non_neg_integer(),
  max_nesting: non_neg_integer(),
  files_with_warnings: non_neg_integer(),
  impure_files: non_neg_integer(),
  languages: %{required(atom()) => non_neg_integer()}
}

Functions

analyze_directory(path, opts \\ [])

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

Analyzes all files in a directory.

Recursively analyzes all supported source files in the directory and optionally stores results in the knowledge graph.

Parameters

  • path: Directory path to analyze
  • opts: Keyword list of options
    • :recursive - Recursively analyze subdirectories (default: true)
    • :store - Store results in knowledge graph (default: true)
    • :metrics - List of metrics to calculate (default: all)
    • :parallel - Use parallel processing (default: true)
    • :max_concurrency - Maximum concurrent analyses (default: System.schedulers_online())

Returns

  • {:ok, results} - List of analysis results (mix of {:ok, result} and {:error, reason})
  • {:error, reason} - Error if directory access fails

Examples

# Analyze entire lib directory
{:ok, results} = Quality.analyze_directory("lib/")

# Analyze without storing
{:ok, results} = Quality.analyze_directory("lib/", store: false)

# Sequential analysis (useful for debugging)
{:ok, results} = Quality.analyze_directory("lib/", parallel: false)

analyze_file(path, opts \\ [])

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

Analyzes a single file for code quality metrics.

Performs comprehensive quality analysis including complexity metrics and purity analysis. Optionally stores results in the knowledge graph for later querying.

Parameters

  • path: Path to the file to analyze
  • opts: Keyword list of options
    • :metrics - List of specific metrics to calculate (default: all)
    • :store - Store results in knowledge graph (default: true)
    • :thresholds - Custom threshold map for warnings
    • :language - Explicit language (default: auto-detect)

Returns

  • {:ok, analysis_result} - Analysis results with metrics
  • {:error, reason} - Error if analysis fails

Examples

# Analyze with default options (all metrics, auto-store)
{:ok, result} = Quality.analyze_file("lib/my_module.ex")
result.complexity.cyclomatic  # => 5

# Analyze specific metrics without storing
{:ok, result} = Quality.analyze_file("lib/my_module.ex",
  metrics: [:cyclomatic, :cognitive],
  store: false
)

# Analyze with custom thresholds
{:ok, result} = Quality.analyze_file("lib/my_module.ex",
  thresholds: %{cyclomatic: 15, cognitive: 10}
)

analyze_function(module, function, arity, opts \\ [])

@spec analyze_function(module(), atom(), non_neg_integer(), keyword()) ::
  {:ok, map()} | {:error, term()}

Analyzes a specific function's quality metrics.

Extracts function-level metrics from the file's stored analysis results. The file must have been analyzed and stored first using analyze_file/2.

Parameters

  • module: Module name atom
  • function: Function name atom
  • arity: Function arity (non-negative integer)
  • opts: Keyword list of options
    • :path - Explicit file path (default: lookup from module in graph)
    • :analyze_if_missing - Analyze file if not found in store (default: false)

Returns

  • {:ok, function_metrics} - Map with function-specific metrics
  • {:error, :not_found} - File not analyzed or function not found
  • {:error, reason} - Other errors

Function Metrics Structure

%{
  module: MyModule,
  function: :my_function,
  arity: 2,
  cyclomatic: 3,
  cognitive: 2,
  nesting: 1,
  halstead: %{...},
  loc: %{total: 10, code: 8, comments: 2}
}

Examples

# Analyze a specific function (file must be analyzed first)
{:ok, result} = Quality.analyze_file("lib/my_module.ex")
{:ok, func_metrics} = Quality.analyze_function(MyModule, :my_function, 2)

# With auto-analysis if not found
{:ok, func_metrics} = Quality.analyze_function(MyModule, :my_function, 2,
  analyze_if_missing: true
)

# With explicit path
{:ok, func_metrics} = Quality.analyze_function(MyModule, :my_function, 2,
  path: "lib/my_module.ex"
)

analyze_module(module, opts \\ [])

@spec analyze_module(
  module(),
  keyword()
) :: {:ok, map()} | {:error, term()}

Analyzes all functions in a module.

Retrieves quality metrics for all functions in the specified module. The module's file must have been analyzed and stored first.

Parameters

  • module: Module name atom
  • opts: Keyword list of options
    • :path - Explicit file path (default: lookup from module in graph)
    • :analyze_if_missing - Analyze file if not found in store (default: false)
    • :sort_by - Sort functions by metric: :cyclomatic, :cognitive, :nesting, :name (default: :name)
    • :threshold - Only return functions exceeding this complexity (optional)

Returns

  • {:ok, module_analysis} - Map with module-level and per-function metrics
  • {:error, reason} - Error if module not found or analysis fails

Module Analysis Structure

%{
  module: MyModule,
  file: "lib/my_module.ex",
  language: :elixir,
  total_cyclomatic: 25,
  total_cognitive: 18,
  function_count: 5,
  functions: [
    %{function: :func1, arity: 2, cyclomatic: 3, ...},
    %{function: :func2, arity: 1, cyclomatic: 5, ...}
  ]
}

Examples

# Analyze all functions in a module
{:ok, analysis} = Quality.analyze_module(MyModule)
analysis.function_count  # => 5

# Sort by complexity
{:ok, analysis} = Quality.analyze_module(MyModule, sort_by: :cyclomatic)

# Only complex functions
{:ok, analysis} = Quality.analyze_module(MyModule, threshold: 10)

# With auto-analysis
{:ok, analysis} = Quality.analyze_module(MyModule, analyze_if_missing: true)

analyze_quality(path, opts \\ [])

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

Analyzes directory quality metrics.

Convenience function that returns a quality score and statistics.

Options

  • :min_complexity - Complexity threshold (default: 10)

Examples

{:ok, metrics} = Quality.analyze_quality("lib/")
metrics.overall_score  # => 75

clear_all()

@spec clear_all() :: :ok

Clears all stored quality metrics.

Removes all quality_metrics nodes from the knowledge graph. Does not affect other graph data (modules, functions, etc.).

Examples

:ok = Quality.clear_all()

comprehensive_report(path, opts \\ [])

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

Generates a comprehensive report including both quality and security metrics.

This convenience function combines quality analysis with security scanning to provide a holistic view of code health.

Parameters

  • path: Directory path to analyze
  • opts: Keyword list of options
    • :min_severity - Minimum security severity to report (default: :medium)
    • :include_security - Include security analysis (default: true)
    • All options from analyze_directory/2

Returns

  • {:ok, report} - Comprehensive report map
  • {:error, reason} - Analysis failed

Report Structure

%{
  quality: %{
    statistics: quality_report(),
    most_complex: [{path, complexity}],
    with_warnings: [{path, warnings}],
    impure_files: [path]
  },
  security: %{
    total_vulnerabilities: integer(),
    by_severity: %{critical: integer(), high: integer(), ...},
    files_with_vulnerabilities: [path],
    summary: string()
  }
}

Examples

# Full analysis with security
{:ok, report} = Quality.comprehensive_report("lib/")
report.quality.statistics.avg_cyclomatic  # => 4.5
report.security.total_vulnerabilities     # => 3

# Quality only
{:ok, report} = Quality.comprehensive_report("lib/", include_security: false)

# Only critical security issues
{:ok, report} = Quality.comprehensive_report("lib/", min_severity: :critical)

count()

@spec count() :: non_neg_integer()

Returns the number of files with stored quality metrics.

Examples

count = Quality.count()  # => 42

find_complex(opts \\ [])

@spec find_complex(keyword()) :: [String.t()]

Finds files exceeding complexity thresholds.

Parameters

  • opts: Keyword list of options
    • :metric - Metric to evaluate: :cyclomatic, :cognitive, :nesting (default: :cyclomatic)
    • :threshold - Threshold value (default: 10)
    • :operator - Comparison: :gt, :gte, :lt, :lte, :eq (default: :gt)
    • :limit - Maximum results (default: 20)

Returns

  • List of file paths exceeding threshold

Examples

# Find files with cyclomatic complexity > 10
complex = Quality.find_complex(threshold: 10)

# Find files with cognitive complexity >= 15
complex = Quality.find_complex(
  metric: :cognitive,
  threshold: 15,
  operator: :gte
)

# Find top 5 most complex files
complex = Quality.find_complex(threshold: 5, limit: 5)

find_complex_code(path, opts \\ [])

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

Finds complex code in a directory.

Convenience function that analyzes a directory and returns functions exceeding the complexity threshold.

Options

  • :min_complexity - Minimum cyclomatic complexity (default: 10)

Examples

{:ok, functions} = Quality.find_complex_code("lib/", min_complexity: 15)

find_impure()

@spec find_impure() :: [String.t()]

Finds impure files (files with side effects).

Returns

  • List of file paths with side effects detected

Examples

impure = Quality.find_impure()
# => ["lib/database.ex", "lib/logger.ex"]

find_with_warnings()

@spec find_with_warnings() :: [{String.t(), [String.t()]}]

Finds files with analysis warnings.

Returns

  • List of {path, warnings} tuples

Examples

files_with_warnings = Quality.find_with_warnings()
# => [{"lib/complex.ex", ["High cyclomatic complexity: 15"]}]

generate_report(opts \\ [])

@spec generate_report(keyword()) :: map() | String.t()

Generates a comprehensive quality report.

Parameters

  • opts: Keyword list of options
    • :type - Report type: :summary, :detailed, :by_language (default: :summary)
    • :format - Output format: :text, :map (default: :map)

Returns

  • Report content (map or formatted string based on format option)

Examples

# Summary report as map
report = Quality.generate_report()

# Detailed report as text
report = Quality.generate_report(type: :detailed, format: :text)

# Language breakdown
report = Quality.generate_report(type: :by_language)

get_metrics(path)

@spec get_metrics(String.t()) :: {:ok, map()} | {:error, :not_found}

Retrieves stored quality metrics for a file.

Parameters

  • path: File path

Returns

  • {:ok, metrics} - Stored metrics map
  • {:error, :not_found} - File not analyzed or metrics not stored

Examples

{:ok, metrics} = Quality.get_metrics("lib/my_module.ex")
metrics.cyclomatic  # => 5

most_complex(opts \\ [])

@spec most_complex(keyword()) :: [{String.t(), number()}]

Returns the most complex files.

Parameters

  • opts: Keyword list of options
    • :metric - Metric to rank by: :cyclomatic, :cognitive, :nesting (default: :cyclomatic)
    • :limit - Number of results (default: 10)

Returns

  • List of {path, metric_value} tuples, sorted by complexity

Examples

# Top 10 most complex files by cyclomatic complexity
top = Quality.most_complex()

# Top 5 by cognitive complexity
top = Quality.most_complex(metric: :cognitive, limit: 5)

statistics()

@spec statistics() :: quality_report()

Returns project-wide quality statistics.

Aggregates metrics from all analyzed files stored in the knowledge graph.

Returns

  • Quality statistics map with averages, maximums, and counts

Examples

stats = Quality.statistics()
stats.total_files        # => 42
stats.avg_cyclomatic     # => 3.5
stats.max_cognitive      # => 25

statistics_by_language()

@spec statistics_by_language() :: %{required(atom()) => quality_report()}

Returns quality statistics grouped by language.

Returns

  • Map of language => statistics

Examples

by_lang = Quality.statistics_by_language()
by_lang[:elixir].avg_cyclomatic  # => 4.2
by_lang[:python].avg_cognitive   # => 5.1