solid-score
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 installOr install it yourself as:
gem install solid_scoreQuick 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.ymlUsage
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 classesSOLID 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/elsechains with type checking (is_a?,kind_of?,instance_of?) -
case/whenstatements 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
supercalls 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.newcalls 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: 60The 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 5RuboCop 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_scoreAvailable 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: 65Exit 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/fixturesLimitations
- 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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
The gem is available as open source under the terms of the MIT License.
References
- SOLID Principles - Wikipedia
- LCOM4 Metric - Lack of Cohesion of Methods
- Robert C. Martin - Clean Architecture