0.0
The project is in a healthy, maintained state
Static analysis tool that scores Ruby classes/modules against SOLID principles using AST analysis
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 1.0
 Project Readme

solid-score

Gem Version Ruby License: MIT

A static analysis tool that scores Ruby classes and modules against SOLID principles using AST analysis.

Features

  • SOLID Principles Analysis: Scores each class/module on all five SOLID principles (SRP, OCP, LSP, ISP, DIP)
  • Multiple Output Formats: Text (table), JSON, and HTML report output
  • Git Diff Mode: Analyze only changed files to prevent score regression
  • Configurable Thresholds: Set minimum scores for CI quality gates
  • Customizable Weights: Adjust the importance of each principle in the total score
  • RuboCop Integration: Use as RuboCop cops for seamless linting workflow

Installation

Add this line to your application's Gemfile:

gem 'solid_score'

And then execute:

bundle install

Or install it yourself as:

gem install solid_score

Quick Start

# Analyze current directory
solid-score .

# Analyze specific directory
solid-score app/models

# Output as JSON
solid-score app/ --format json

# Generate HTML report
solid-score app/ --format html > report.html

# Analyze only changed files (git diff)
solid-score . --diff origin/main

# Use configuration file
solid-score . --config .solid-score.yml

Usage

Command Line Options

Usage: solid-score [path] [options]

Options:
    --format FORMAT              Output format: text (default), json, html
    --config FILE                Path to configuration file
    --min-score SCORE            Minimum total score (exit 1 if below)
    --min-srp SCORE              Minimum SRP score
    --min-ocp SCORE              Minimum OCP score
    --min-lsp SCORE              Minimum LSP score
    --min-isp SCORE              Minimum ISP score
    --min-dip SCORE              Minimum DIP score
    --diff REF                   Analyze only files changed since REF (git ref)
    --max-decrease SCORE         Maximum allowed score decrease per class
    --exclude PATTERN            Exclude patterns (comma-separated)
    --version                    Show version
    -h, --help                   Show help

Output Examples

Text Format (default)

solid-score v0.1.0

Analyzed 5 class(es)

Class                                      SRP   OCP   LSP   ISP   DIP   Total
-------------------------------------------------------------------------------
OrderService                             100.0 100.0 100.0 100.0 100.0   100.0
UserRepository                            80.0  90.0 100.0  95.0  85.0    88.0
PaymentProcessor                          60.0  70.0 100.0  80.0  50.0    68.5
ShapeCalculator                           60.0  35.0 100.0 100.0 100.0    78.2
GodClass                                  30.0 100.0 100.0  65.0 100.0    72.0
-------------------------------------------------------------------------------
Average                                   66.0  79.0 100.0  88.0  87.0    81.3

JSON Format

{
  "version": "0.1.0",
  "classes": [
    {
      "class_name": "OrderService",
      "file_path": "app/services/order_service.rb",
      "srp": 100,
      "ocp": 100.0,
      "lsp": 100,
      "isp": 100,
      "dip": 100.0,
      "total": 100.0,
      "confidence": {
        "srp": "high",
        "ocp": "low",
        "lsp": "low_medium",
        "isp": "medium_high",
        "dip": "high"
      }
    }
  ],
  "summary": {
    "total_classes": 1,
    "average_score": 100.0
  }
}

Configuration

Create a .solid-score.yml file in your project root:

# Target paths to analyze
paths:
  - app/models
  - app/services
  - lib

# Patterns to exclude (glob format)
exclude:
  - spec/**
  - test/**
  - tmp/**
  - vendor/**

# Preset (loads predefined configuration)
# preset: rails

# Output format
format: text  # or json or html

# Score thresholds (CI will fail if below)
thresholds:
  total: 70
  srp: 60
  ocp: 50
  lsp: 50
  isp: 50
  dip: 60

# Weight of each principle in total score (must sum to 1.0)
weights:
  srp: 0.30   # 30%
  ocp: 0.15   # 15%
  lsp: 0.10   # 10%
  isp: 0.20   # 20%
  dip: 0.25   # 25%

# Git diff analysis settings
diff:
  max_decrease: 5     # Max allowed score decrease per class
  new_class_min: 70   # Minimum score for new classes

SOLID Principles Scoring

SRP (Single Responsibility Principle)

Metric: LCOM4 (Lack of Cohesion of Methods 4)

Measures class cohesion using graph connectivity analysis. A class with high cohesion (methods working with shared instance variables) scores higher.

  • Score 100: All methods are connected (single responsibility)
  • Score 0: Methods are completely disconnected (multiple responsibilities)
  • Confidence: High

OCP (Open/Closed Principle)

Metric: Conditional complexity + type checking patterns

Detects patterns that suggest a class needs modification for extension:

  • if/else chains with type checking (is_a?, kind_of?, instance_of?)

  • case/when statements with multiple branches

  • Conditional density in methods

  • Confidence: Low-Medium (heuristic-based)

LSP (Liskov Substitution Principle)

Metric: Inheritance contract compliance

Detects potential violations:

  • Method signature changes (different parameters than parent)

  • Missing super calls in overridden methods

  • Return type inconsistencies

  • Confidence: Low-Medium (requires runtime analysis for full accuracy)

ISP (Interface Segregation Principle)

Metric: Interface bloat detection

Measures:

  • Number of public methods

  • Method parameter count

  • Unused method dependencies

  • Confidence: Medium-High

DIP (Dependency Inversion Principle)

Metric: Concrete dependency ratio

Calculates the ratio of concrete class instantiations to total dependencies:

  • Direct ClassName.new calls penalize the score

  • Standard library classes (Array, Hash, String, etc.) are whitelisted

  • Dependency injection patterns score higher

  • Confidence: High

Rails Preset

For Rails projects, use the built-in preset to get sensible defaults:

# .solid-score.yml
preset: rails
thresholds:
  total: 60

The Rails preset configures:

  • Paths: app/models, app/controllers, app/services, app/jobs, app/mailers, lib, etc.
  • Excludes: spec/**, test/**, vendor/**, db/**, tmp/**
  • Weights: DIP weighted higher (0.30), SRP slightly lower (0.25)
  • DIP Whitelist: Rails, Logger, Net::HTTP, ActionMailer, ActiveJob, etc.

Explicit YAML values override the preset.

CI/CD Integration

GitHub Actions (Official Action)

name: SOLID Score Check

on:
  pull_request:
    branches: [main]

jobs:
  solid-score:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: harakeishi/solid-score@main
        with:
          min-score: "70"
          pr-comment: "true"

The action supports these inputs:

Input Default Description
ruby-version 3.2 Ruby version
min-score 0 Minimum total score threshold
format text Output format (text/json/html)
paths . Paths to analyze
config .solid-score.yml Config file path
pr-comment false Post score report as PR comment
diff-ref origin/main Base ref for diff comparison

Outputs: score-json, passing, average-score.

Manual Setup

jobs:
  solid-score:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
          bundler-cache: true

      - name: Install solid-score
        run: gem install solid_score

      - name: Check SOLID scores
        run: solid-score app/ lib/ --min-score 70

      - name: Check score regression (diff mode)
        run: solid-score . --diff origin/main --max-decrease 5

RuboCop Integration

Add rubocop-solid_score to your .rubocop.yml to run SOLID score checks as part of your existing RuboCop workflow:

# .rubocop.yml
require:
  - rubocop-solid_score

Available Cops

Cop Default Description
SolidScore/TotalScore Enabled (70) Checks weighted total score
SolidScore/SingleResponsibility Disabled (60) Checks SRP score
SolidScore/OpenClosed Disabled (50) Checks OCP score
SolidScore/LiskovSubstitution Disabled (60) Checks LSP score
SolidScore/InterfaceSegregation Disabled (70) Checks ISP score
SolidScore/DependencyInversion Disabled (65) Checks DIP score

Configuration

# .rubocop.yml
SolidScore/TotalScore:
  Enabled: true
  MinScore: 70
  Weights:
    srp: 0.30
    ocp: 0.15
    lsp: 0.10
    isp: 0.20
    dip: 0.25
  DipWhitelist:
    - Rails
    - Logger

# Enable individual principle checks
SolidScore/SingleResponsibility:
  Enabled: true
  MinScore: 60

SolidScore/DependencyInversion:
  Enabled: true
  MinScore: 65

Exit Codes

Code Description
0 All checks passed
1 Score below threshold or regression detected
2 Invalid arguments or configuration error

Development

After checking out the repo, run:

# Install dependencies
bundle install

# Run tests
bundle exec rspec

# Run tests with coverage
COVERAGE=true bundle exec rspec

# Run linter
bundle exec rubocop

# Run the tool locally
bundle exec solid-score spec/fixtures

Limitations

  • Static Analysis Only: Cannot detect runtime behavior or duck typing patterns
  • OCP/LSP Accuracy: These principles are difficult to measure statically; scores are heuristic-based
  • Module Support: Module analysis is less accurate than class analysis
  • Metaprogramming: Heavy use of define_method, method_missing, etc. may affect accuracy

Requirements

  • Ruby >= 3.2.0

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/harakeishi/solid-score.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

The gem is available as open source under the terms of the MIT License.

References