pronto-rubycritic 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubycritic-pronto.yml +65 -0
- data/CHANGELOG.md +102 -0
- data/CONTRIBUTING.md +22 -0
- data/LICENSE +21 -0
- data/README.md +342 -0
- data/lib/pronto/rubycritic/analyser.rb +32 -0
- data/lib/pronto/rubycritic/config_loader.rb +84 -0
- data/lib/pronto/rubycritic/formatter.rb +168 -0
- data/lib/pronto/rubycritic/message_builder.rb +82 -0
- data/lib/pronto/rubycritic/smell_filter.rb +152 -0
- data/lib/pronto/rubycritic/version.rb +11 -0
- data/lib/pronto/rubycritic.rb +113 -0
- data/pronto-rubycritic.gemspec +54 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d06797042b9ba142db8bbd28c624b767db0d87d84e1013bc81c9d0379e64b61c
|
|
4
|
+
data.tar.gz: f134fbfaeb1fdfaec256850dcd534e3f03b9f5f09e5cb2b62adc5e046d4d12a3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 54d29e6a0262b192e5f54deb0893b7bdade9e0e2fb3eab50f4546d2d06c5e4db4126907eb8ea7b88328e2620eb0b7a98decd1c622db49d04f56646f473577007
|
|
7
|
+
data.tar.gz: 422f6e93e604dfe071d1c4f853bd836f86a920d14979527a28f7274a663a832767b0f6899245498b304ba7645accd0d4470974fafd136f47957c07ab202d8fe5
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Example configuration for pronto-rubycritic.
|
|
2
|
+
# Copy to your repo root as .rubycritic-pronto.yml and adjust thresholds.
|
|
3
|
+
#
|
|
4
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
# reek — detects code smells (long methods, feature envy, bad naming, etc.).
|
|
6
|
+
# Each smell is a potential design issue attached to a specific location.
|
|
7
|
+
# smell_types: allow-list of reek smell types (see list below).
|
|
8
|
+
# Only reek smells are filtered — flay/flog smells pass through.
|
|
9
|
+
# max_smells: cap total reported smells per module (across all analysers).
|
|
10
|
+
#
|
|
11
|
+
# flay — structural code duplication. Score is duplication mass; higher = more.
|
|
12
|
+
# max_score: drop flay smells above this score.
|
|
13
|
+
# exclude: repo-relative globs to skip for flay.
|
|
14
|
+
#
|
|
15
|
+
# flog — method complexity score. Higher = more complex.
|
|
16
|
+
# max_score: drop flog smells above this score.
|
|
17
|
+
# exclude: repo-relative globs to skip for flog.
|
|
18
|
+
#
|
|
19
|
+
# complexity — cyclomatic complexity (per module).
|
|
20
|
+
# max: drop modules above this complexity.
|
|
21
|
+
#
|
|
22
|
+
# churn — how often the file changes in git history.
|
|
23
|
+
# max: drop modules with churn above this.
|
|
24
|
+
#
|
|
25
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
# Reek smell types you can list under reek.smell_types:
|
|
27
|
+
# Attribute, BooleanParameter, ClassVariable, ControlParameter, DataClump,
|
|
28
|
+
# DuplicateMethodCall, FeatureEnvy, InstanceVariableAssumption,
|
|
29
|
+
# IrresponsibleModule, LongParameterList, LongYieldList, ManualDispatch,
|
|
30
|
+
# MissingSafeMethod, ModuleInitialize, NestedIterators, NilCheck,
|
|
31
|
+
# RepeatedConditional, SelfAssignment, SingletonMethodCall, TooManyConstants,
|
|
32
|
+
# TooManyInstanceVariables, TooManyMethods, UncommunicativeMethodName,
|
|
33
|
+
# UncommunicativeModuleName, UncommunicativeParameterName,
|
|
34
|
+
# UncommunicativeVariableName, UnusedParameters, UnusedPrivateMethod,
|
|
35
|
+
# UtilityFunction
|
|
36
|
+
#
|
|
37
|
+
# NOTE (0.12.0): reek.min_severity was REMOVED — RubyCritic smells do not
|
|
38
|
+
# expose a severity field, so the key was always a no-op that silently
|
|
39
|
+
# dropped every smell. Use reek.smell_types or flay/flog.max_score instead.
|
|
40
|
+
|
|
41
|
+
reek:
|
|
42
|
+
smell_types:
|
|
43
|
+
- UncommunicativeMethodName
|
|
44
|
+
- UncommunicativeVariableName
|
|
45
|
+
- DuplicateMethodCall
|
|
46
|
+
- FeatureEnvy
|
|
47
|
+
max_smells: 5
|
|
48
|
+
|
|
49
|
+
flay:
|
|
50
|
+
max_score: 100
|
|
51
|
+
exclude:
|
|
52
|
+
- 'spec/**/*'
|
|
53
|
+
- 'test/**/*'
|
|
54
|
+
|
|
55
|
+
flog:
|
|
56
|
+
max_score: 20
|
|
57
|
+
exclude:
|
|
58
|
+
- 'spec/**/*'
|
|
59
|
+
- 'test/**/*'
|
|
60
|
+
|
|
61
|
+
complexity:
|
|
62
|
+
max: 10
|
|
63
|
+
|
|
64
|
+
churn:
|
|
65
|
+
max: 5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.12.0] — 2026-06-16
|
|
8
|
+
|
|
9
|
+
Major forensic refactor. **Breaking changes** — see migration notes below.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `PRONTO_RUBYCRITIC_SEVERITY_LEVEL` environment variable (preferred).
|
|
13
|
+
- `PRONTO_RUBYCRITIC_DEBUG` for verbose error output (prints backtrace head).
|
|
14
|
+
- `PRONTO_RUBYCRITIC_RAISE_ERRORS` to fail fast in CI instead of swallowing errors.
|
|
15
|
+
- GitHub Actions / GitLab CI auto-detection via `Pronto::RubyCritic::Formatter`.
|
|
16
|
+
GitHub gets HTML `<details>` blocks; GitLab gets plain Markdown.
|
|
17
|
+
- `flay.max_score`, `flog.max_score`, `flay.exclude`, `flog.exclude` now map to
|
|
18
|
+
real `Smell#analyser` / `Smell#score` fields and actually filter smells.
|
|
19
|
+
- Internal modules: `ConfigLoader`, `Analyser`, `SmellFilter`, `MessageBuilder`,
|
|
20
|
+
`Formatter`. Each has its own test file.
|
|
21
|
+
- `.gitignore`, `Rakefile`, `.reek.yml`, `.rubycritic.yml`, `.gitlab-ci.yml`.
|
|
22
|
+
- Ruby 4.0 to the CI test matrix (GitHub Actions + GitLab); verified green on
|
|
23
|
+
4.0.5. Single-version CI jobs (lint / self-analysis / build) moved off the
|
|
24
|
+
now-security-only Ruby 3.3 onto the fully-supported 3.4.
|
|
25
|
+
- Convenience `Pronto::RubyCritic::VERSION` alongside
|
|
26
|
+
`Pronto::RubyCriticVersion::VERSION`.
|
|
27
|
+
|
|
28
|
+
### Changed (breaking)
|
|
29
|
+
- Default severity level: `:info` → `:warning`.
|
|
30
|
+
Rationale: `:info` messages are often ignored in Pronto reports.
|
|
31
|
+
- `rubycritic` dependency: `>= 4.9, < 6.0` (was unpinned).
|
|
32
|
+
- `pronto` dependency: `>= 0.11, < 2.0` (was `~> 0.11`).
|
|
33
|
+
- `File.fnmatch` exclude patterns now use `FNM_PATHNAME | FNM_DOTMATCH` and
|
|
34
|
+
are matched against repo-relative paths (absolute paths never matched before).
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
- `reek.min_severity` config key. RubyCritic's `Smell` has no `severity` field
|
|
38
|
+
(only `status`, `score`, `cost`), so the previous implementation silently
|
|
39
|
+
filtered out *every* smell whenever this key was set.
|
|
40
|
+
**Migration:** use `reek.smell_types` to allow-list specific smell types, or
|
|
41
|
+
`flay.max_score` / `flog.max_score` for score-based filtering.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- `reek.min_severity` silently dropping every smell (dead code).
|
|
45
|
+
- `flay.max_score` / `flog.max_score` never matching any module (dead code —
|
|
46
|
+
`AnalysedModule` has no `flay_score` / `flog_score` attributes).
|
|
47
|
+
- `File.fnmatch` exclude patterns silently never matching due to absolute path.
|
|
48
|
+
- Unbounded `YAML.load_file` on user config (now `YAML.safe_load_file` with an
|
|
49
|
+
explicit permitted_classes list — defence in depth against CVE-style YAML
|
|
50
|
+
RCE vectors on older Psych versions).
|
|
51
|
+
- Env var typo: `PRONTO_REEK_SEVERITY_LEVEL` is now read with a deprecation
|
|
52
|
+
warning; `PRONTO_RUBYCRITIC_SEVERITY_LEVEL` is the canonical name.
|
|
53
|
+
- Broad `rescue StandardError` now emits class name + message, and prints
|
|
54
|
+
backtrace head when `PRONTO_RUBYCRITIC_DEBUG` is set.
|
|
55
|
+
- `patch_for_smell` O(N·M) scan replaced with pre-built hash index.
|
|
56
|
+
- `smell.message.capitalize` crash on nil message.
|
|
57
|
+
- GitLab comments no longer show literal ` ` entities and unrendered
|
|
58
|
+
`<details>` blocks.
|
|
59
|
+
- **Malformed config no longer silently disables the runner.** A valid-YAML-but-
|
|
60
|
+
wrong-shape config (e.g. `reek: false`, `complexity: 10`, a scalar `exclude`,
|
|
61
|
+
a non-string `exclude` entry, or a non-numeric / negative `max_smells` /
|
|
62
|
+
`max_score` / `max`) used to raise inside `SmellFilter`, get swallowed by the
|
|
63
|
+
top-level rescue, and report **zero** smells — a false "all clear" for the CI
|
|
64
|
+
gate. Each of these is now coerced or ignored defensively, and `ConfigLoader`
|
|
65
|
+
warns and drops a section whose value is not a mapping.
|
|
66
|
+
- `.pronto.yml` with a non-Hash `rubycritic:` section no longer raises in
|
|
67
|
+
severity resolution.
|
|
68
|
+
- Boolean env vars (`PRONTO_RUBYCRITIC_RAISE_ERRORS`, `PRONTO_RUBYCRITIC_DEBUG`)
|
|
69
|
+
now require a truthy value (`1`/`true`/`yes`/`on`); previously `=0`/`=false`
|
|
70
|
+
also activated them because only presence was checked.
|
|
71
|
+
- GitHub output escapes HTML-significant characters (`&`, `<`, `>`) in smell
|
|
72
|
+
type/context/message, so an operator method name (e.g. `Foo#<=>`) or a message
|
|
73
|
+
containing markup can no longer break the comment's `<details>`/table layout.
|
|
74
|
+
|
|
75
|
+
### Security
|
|
76
|
+
- YAML aliases are no longer permitted when loading `.rubycritic-pronto.yml`
|
|
77
|
+
(`YAML.safe_load_file` is called without `aliases: true`). The gem's config
|
|
78
|
+
schema never used anchors/aliases, and enabling them exposed an alias-expansion
|
|
79
|
+
("billion laughs") DoS on a config file that, in CI, can be supplied by an
|
|
80
|
+
untrusted pull request. An aliased config now degrades to `{}` with a warning.
|
|
81
|
+
|
|
82
|
+
### Known limitations
|
|
83
|
+
- RubyCritic self-analysis sits at ≈ 78 (floor 75). The remaining gap is the
|
|
84
|
+
`SmellFilter` per-analyser branch fan-out and `MessageBuilder`'s `filter_map`
|
|
85
|
+
chains; lifting the floor toward 85 is tracked as tech debt for a future
|
|
86
|
+
release rather than chased with premature extraction.
|
|
87
|
+
- `reek.smell_types` filters reek smells only; flay/flog smells always pass that
|
|
88
|
+
filter (use `flay.max_score` / `flog.max_score` to bound them).
|
|
89
|
+
|
|
90
|
+
### Migration from 0.11.x
|
|
91
|
+
- If you use `reek.min_severity` in `.rubycritic-pronto.yml`, remove it. It
|
|
92
|
+
never worked; removing it will restore visibility into your smells.
|
|
93
|
+
- If you use `flay.max_score` or `flog.max_score`, no action needed — they now
|
|
94
|
+
actually filter (previously were a no-op).
|
|
95
|
+
- If you rely on the default severity being `:info`, set
|
|
96
|
+
`severity_level: info` under the `rubycritic:` key of `.pronto.yml` or export
|
|
97
|
+
`PRONTO_RUBYCRITIC_SEVERITY_LEVEL=info`.
|
|
98
|
+
- If you import `Pronto::RubyCriticVersion::VERSION`, no change required — it
|
|
99
|
+
is preserved. `Pronto::RubyCritic::VERSION` is a new equivalent.
|
|
100
|
+
|
|
101
|
+
## [0.11.1] and earlier
|
|
102
|
+
See git history (`git log --oneline lib/pronto/rubycritic.rb`).
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thank you for considering contributing to `pronto-rubycritic`!
|
|
4
|
+
|
|
5
|
+
## How to contribute
|
|
6
|
+
|
|
7
|
+
- Fork the repository
|
|
8
|
+
- Create a new branch for your feature or bugfix
|
|
9
|
+
- Write tests for your changes
|
|
10
|
+
- Run the full quality gate locally before opening a PR:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle exec rake ci # RuboCop + Reek + RSpec + RubyCritic
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Contributions must pass RuboCop, Reek, RSpec with ≥ 90 % line coverage
|
|
17
|
+
(run with `COVERAGE=1`), and RubyCritic with a score ≥ 75.
|
|
18
|
+
- Submit a pull request with a clear description
|
|
19
|
+
|
|
20
|
+
## Code of Conduct
|
|
21
|
+
|
|
22
|
+
Please be respectful and considerate in all interactions.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Rishabh Singh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# pronto-rubycritic
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Rishabhs343/custom-pronto-gem/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/pronto-rubycritic)
|
|
5
|
+
[](https://www.ruby-lang.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
A production-grade [Pronto](https://github.com/prontolabs/pronto) runner for
|
|
9
|
+
[RubyCritic](https://github.com/whitesmith/rubycritic). It reports
|
|
10
|
+
**reek / flay / flog / complexity / churn** issues as Pronto messages —
|
|
11
|
+
**only on lines added or changed in the pull request**, never on untouched code.
|
|
12
|
+
|
|
13
|
+
> **New in 0.12.0** — forensic refactor: real working filters for flay/flog scores,
|
|
14
|
+
> safe YAML loading, GitHub/GitLab auto-detection, six independently-tested
|
|
15
|
+
> modules, and ≥90 % line-coverage CI gates.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of contents
|
|
20
|
+
|
|
21
|
+
- [Why this gem](#why-this-gem)
|
|
22
|
+
- [At a glance](#at-a-glance)
|
|
23
|
+
- [Installation](#installation)
|
|
24
|
+
- [Quickstart](#quickstart)
|
|
25
|
+
- [Configuration](#configuration)
|
|
26
|
+
- [Environment variables](#environment-variables)
|
|
27
|
+
- [CI integration](#ci-integration)
|
|
28
|
+
- [Architecture](#architecture)
|
|
29
|
+
- [Development](#development)
|
|
30
|
+
- [Troubleshooting](#troubleshooting)
|
|
31
|
+
- [Migrating from 0.11.x](#migrating-from-011x)
|
|
32
|
+
- [Contributing](#contributing)
|
|
33
|
+
- [License](#license)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why this gem
|
|
38
|
+
|
|
39
|
+
Running the full [RubyCritic](https://github.com/whitesmith/rubycritic) report
|
|
40
|
+
on every PR is **noisy** — contributors get flagged for code they never touched.
|
|
41
|
+
This gem scopes RubyCritic output to diff-changed lines, making it actionable:
|
|
42
|
+
|
|
43
|
+
- **Only added / modified lines are flagged.** A smell on line 42 that existed
|
|
44
|
+
before the PR is silently ignored; a smell introduced by the PR on line 7 is
|
|
45
|
+
reported as a Pronto message.
|
|
46
|
+
- **Every RubyCritic analyser is supported** — reek, flay, flog, complexity,
|
|
47
|
+
churn — behind one config file.
|
|
48
|
+
- **Per-CI formatting.** GitHub renders HTML `<details>` blocks in PR comments;
|
|
49
|
+
GitLab gets plain Markdown (detected automatically).
|
|
50
|
+
- **Fails loud in CI when asked, fails soft in local dev.** Set
|
|
51
|
+
`PRONTO_RUBYCRITIC_RAISE_ERRORS=1` in CI to surface runner bugs; locally the
|
|
52
|
+
runner degrades to an empty result set with a stderr warning.
|
|
53
|
+
|
|
54
|
+
## At a glance
|
|
55
|
+
|
|
56
|
+
| | |
|
|
57
|
+
|---|---|
|
|
58
|
+
| **Ruby** | ≥ 3.2.2 |
|
|
59
|
+
| **Pronto** | ≥ 0.11, < 2.0 |
|
|
60
|
+
| **RubyCritic** | ≥ 4.9, < 6.0 |
|
|
61
|
+
| **Analysers** | reek · flay · flog · complexity · churn |
|
|
62
|
+
| **Output modes** | GitHub HTML · GitLab Markdown · Plain |
|
|
63
|
+
| **Tests** | RSpec · 123 examples · ≥ 90 % line coverage (enforced) |
|
|
64
|
+
| **Lint** | RuboCop · Reek · RubyCritic (self-analysis) |
|
|
65
|
+
| **CI** | GitHub Actions (3.2 / 3.3 / 3.4 / 4.0) + GitLab CI |
|
|
66
|
+
| **License** | MIT |
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
Add to your `Gemfile`:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
gem 'pronto-rubycritic', '~> 0.12', require: false
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bundle install
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or install standalone:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gem install pronto-rubycritic
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quickstart
|
|
89
|
+
|
|
90
|
+
Run on the current diff:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bundle exec pronto run -r rubycritic
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Run on the last N commits:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bundle exec pronto run -r rubycritic -c HEAD~3
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Run against a specific commit range (CI-friendly):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
bundle exec pronto run -r rubycritic --commit="origin/main" -f github_pr -c origin/main
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Example output
|
|
109
|
+
|
|
110
|
+
Given a PR that introduces a smell on a new line, the **local / plain** output
|
|
111
|
+
looks like this (Pronto prefixes each message with `path:line` and a level letter):
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
app/services/order.rb:42 W: [warning] FeatureEnvy — Order#calculate_total (reek)
|
|
115
|
+
Message: Order#calculate_total refers to 'customer' more than self
|
|
116
|
+
Locations: app/services/order.rb:42
|
|
117
|
+
Complexity: 14.50 Duplication: 0 Methods: 7 Cost: 2.10 Churn: 3
|
|
118
|
+
Docs: https://github.com/troessner/reek/blob/master/docs/Feature-Envy.md
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Under **GitHub Actions** the same finding is posted as an inline PR review comment
|
|
122
|
+
with a severity emoji, the smell type, a collapsible `<details>` metrics table,
|
|
123
|
+
and a docs link; under **GitLab CI** it is the same content rendered as flat
|
|
124
|
+
Markdown (a single-row metrics table, no `<details>`). See the
|
|
125
|
+
[output differences table](#output-differences-table) below.
|
|
126
|
+
|
|
127
|
+
## Configuration
|
|
128
|
+
|
|
129
|
+
Create `.rubycritic-pronto.yml` at your repo root. **All keys are optional** —
|
|
130
|
+
an empty file (or no file at all) reports every smell on changed lines at the
|
|
131
|
+
default severity.
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
# Filter by reek smell type and cap total smells per module.
|
|
135
|
+
reek:
|
|
136
|
+
smell_types:
|
|
137
|
+
- FeatureEnvy
|
|
138
|
+
- UncommunicativeMethodName
|
|
139
|
+
- UncommunicativeVariableName
|
|
140
|
+
- DuplicateMethodCall
|
|
141
|
+
- LongParameterList
|
|
142
|
+
max_smells: 5 # cap total smells per module (all analysers)
|
|
143
|
+
|
|
144
|
+
# Drop flay smells above this duplication-mass score.
|
|
145
|
+
flay:
|
|
146
|
+
max_score: 100
|
|
147
|
+
exclude:
|
|
148
|
+
- 'spec/**/*'
|
|
149
|
+
- 'db/migrate/**/*'
|
|
150
|
+
|
|
151
|
+
# Drop flog smells above this complexity score.
|
|
152
|
+
flog:
|
|
153
|
+
max_score: 20
|
|
154
|
+
exclude:
|
|
155
|
+
- 'spec/**/*'
|
|
156
|
+
|
|
157
|
+
# Drop entire modules above these thresholds.
|
|
158
|
+
complexity:
|
|
159
|
+
max: 10
|
|
160
|
+
|
|
161
|
+
churn:
|
|
162
|
+
max: 5
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Configuration keys reference
|
|
166
|
+
|
|
167
|
+
| Key | Scope | Behaviour |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `reek.smell_types` | reek smells only | Allow-list of reek smell type names. Non-listed reek smells are dropped. flay/flog smells are unaffected. |
|
|
170
|
+
| `reek.max_smells` | all smells per module | Cap total reported smells per module. |
|
|
171
|
+
| `flay.max_score` | flay smells only | Drop flay smells with `score > max_score`. |
|
|
172
|
+
| `flay.exclude` | flay smells only | Repo-relative globs (`FNM_PATHNAME`) to skip. |
|
|
173
|
+
| `flog.max_score` | flog smells only | Drop flog smells with `score > max_score`. |
|
|
174
|
+
| `flog.exclude` | flog smells only | Same as `flay.exclude`. |
|
|
175
|
+
| `complexity.max` | module | Drop modules with complexity > max. |
|
|
176
|
+
| `churn.max` | module | Drop modules with churn > max. |
|
|
177
|
+
|
|
178
|
+
### Recognised reek smell types
|
|
179
|
+
|
|
180
|
+
Use any of these under `reek.smell_types`:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Attribute · BooleanParameter · ClassVariable · ControlParameter · DataClump
|
|
184
|
+
DuplicateMethodCall · FeatureEnvy · InstanceVariableAssumption
|
|
185
|
+
IrresponsibleModule · LongParameterList · LongYieldList · ManualDispatch
|
|
186
|
+
MissingSafeMethod · ModuleInitialize · NestedIterators · NilCheck
|
|
187
|
+
RepeatedConditional · SelfAssignment · SingletonMethodCall · TooManyConstants
|
|
188
|
+
TooManyInstanceVariables · TooManyMethods · UncommunicativeMethodName
|
|
189
|
+
UncommunicativeModuleName · UncommunicativeParameterName
|
|
190
|
+
UncommunicativeVariableName · UnusedParameters · UnusedPrivateMethod
|
|
191
|
+
UtilityFunction
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Environment variables
|
|
195
|
+
|
|
196
|
+
| Variable | Default | Purpose |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `PRONTO_RUBYCRITIC_SEVERITY_LEVEL` | `warning` | Severity of emitted messages: `info`, `warning`, `error`, or `fatal`. |
|
|
199
|
+
| `PRONTO_RUBYCRITIC_DEBUG` | unset | Set to `1`/`true`/`yes`/`on` to print the first 10 backtrace lines on any internal error. |
|
|
200
|
+
| `PRONTO_RUBYCRITIC_RAISE_ERRORS` | unset | Set to `1`/`true`/`yes`/`on` to re-raise internal errors instead of returning `[]`. Recommended in CI. |
|
|
201
|
+
| `PRONTO_REEK_SEVERITY_LEVEL` | unset | **Deprecated.** Read for back-compat with a deprecation warning. Use `PRONTO_RUBYCRITIC_SEVERITY_LEVEL`. |
|
|
202
|
+
|
|
203
|
+
Severity can also be set in `.pronto.yml`:
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
# .pronto.yml
|
|
207
|
+
rubycritic:
|
|
208
|
+
severity_level: warning
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Precedence: `PRONTO_RUBYCRITIC_SEVERITY_LEVEL` → `PRONTO_REEK_SEVERITY_LEVEL`
|
|
212
|
+
(deprecated) → `.pronto.yml` → `:warning` default.
|
|
213
|
+
|
|
214
|
+
## CI integration
|
|
215
|
+
|
|
216
|
+
### GitHub Actions
|
|
217
|
+
|
|
218
|
+
The runner auto-detects `$GITHUB_ACTIONS` and emits rich HTML with `<details>`
|
|
219
|
+
blocks. Example step:
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
- name: Pronto
|
|
223
|
+
env:
|
|
224
|
+
PRONTO_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
|
|
225
|
+
PRONTO_GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
226
|
+
PRONTO_RUBYCRITIC_RAISE_ERRORS: '1'
|
|
227
|
+
run: |
|
|
228
|
+
bundle exec pronto run -f github_pr -c origin/${{ github.base_ref }}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### GitLab CI
|
|
232
|
+
|
|
233
|
+
The runner auto-detects `$GITLAB_CI` and emits plain Markdown (GitLab discussion
|
|
234
|
+
comments do not render `<details>` reliably). Example:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
pronto:
|
|
238
|
+
stage: lint
|
|
239
|
+
script:
|
|
240
|
+
- bundle exec pronto run -f gitlab_mr -c origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
|
|
241
|
+
variables:
|
|
242
|
+
PRONTO_GITLAB_API_PRIVATE_TOKEN: $PRONTO_GITLAB_TOKEN
|
|
243
|
+
PRONTO_RUBYCRITIC_RAISE_ERRORS: '1'
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Output differences table
|
|
247
|
+
|
|
248
|
+
| Feature | GitHub Actions | GitLab CI | Local CLI |
|
|
249
|
+
|---|---|---|---|
|
|
250
|
+
| Markup | HTML `<details>` + table | Flat Markdown table | Indented plain text |
|
|
251
|
+
| Source location | Anchored to the diff line | Anchored to the diff line | Printed on a `Locations:` line |
|
|
252
|
+
| Detected via | `$GITHUB_ACTIONS` | `$GITLAB_CI` | neither |
|
|
253
|
+
|
|
254
|
+
## Architecture
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
Pronto::RubyCritic (Runner)
|
|
258
|
+
├── ConfigLoader — safe YAML + severity resolution
|
|
259
|
+
├── Analyser — owns ::RubyCritic::Config global, runs AnalysersRunner
|
|
260
|
+
├── SmellFilter — pure filter against user config
|
|
261
|
+
├── MessageBuilder — maps smells → Pronto::Message on added lines
|
|
262
|
+
└── Formatter — GitHub HTML / GitLab Markdown / Plain
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Each module has its own spec file (`spec/pronto/rubycritic/*_spec.rb`) and
|
|
266
|
+
mocks only the boundary between layers. Integration tests in
|
|
267
|
+
`spec/integration/` exercise the full runner end-to-end against real fixtures.
|
|
268
|
+
|
|
269
|
+
See [CHANGELOG.md](CHANGELOG.md) for the rationale behind every change in 0.12.0.
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
git clone https://github.com/Rishabhs343/custom-pronto-gem.git
|
|
275
|
+
cd custom-pronto-gem
|
|
276
|
+
bundle install
|
|
277
|
+
|
|
278
|
+
bundle exec rake # RuboCop + RSpec
|
|
279
|
+
bundle exec rake ci # RuboCop + Reek + RSpec + RubyCritic (full CI)
|
|
280
|
+
bundle exec rake reek # Reek only
|
|
281
|
+
bundle exec rake critic # RubyCritic self-analysis
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Run a single spec file:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
bundle exec rspec spec/pronto/rubycritic_spec.rb
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Generate a coverage report (opens `coverage/index.html`):
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
COVERAGE=1 bundle exec rspec
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Debug the runner against a real project:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
cd /path/to/your/project
|
|
300
|
+
PRONTO_RUBYCRITIC_DEBUG=1 bundle exec pronto run -r rubycritic --unstaged
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
| Symptom | Cause | Fix |
|
|
306
|
+
|---|---|---|
|
|
307
|
+
| `no messages reported, but smells exist` | You set the now-removed `reek.min_severity` key | Remove it from `.rubycritic-pronto.yml`; see [migration](#migrating-from-011x) |
|
|
308
|
+
| `undefined method new_file? for Pronto::Git::Patch` | Running a test that was written against ≤ 0.11.x mocks | Upgrade test mocks to use `instance_double(Pronto::Git::Patch)` |
|
|
309
|
+
| `invalid YAML in .rubycritic-pronto.yml` on stderr, no messages | Config file has a syntax error | Run `yamllint .rubycritic-pronto.yml` or check your editor's linter |
|
|
310
|
+
| `pronto-rubycritic: PRONTO_REEK_SEVERITY_LEVEL is deprecated…` | Legacy env var still set | Switch to `PRONTO_RUBYCRITIC_SEVERITY_LEVEL` |
|
|
311
|
+
| All smells reported as `:warning` regardless of config | Default severity is `:warning` in 0.12.0 | Set `severity_level: info` in `.pronto.yml` under `rubycritic:` |
|
|
312
|
+
| `RubyCritic::SourceControlSystem::NotFoundError` in CI | Working dir is not a git repo | Ensure `actions/checkout@v4` uses `fetch-depth: 0` |
|
|
313
|
+
|
|
314
|
+
Set `PRONTO_RUBYCRITIC_DEBUG=1` for the first 10 lines of any backtrace.
|
|
315
|
+
|
|
316
|
+
## Migrating from 0.11.x
|
|
317
|
+
|
|
318
|
+
| Change | Action needed |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `reek.min_severity` removed | Delete the key from `.rubycritic-pronto.yml`. It was dead code that silently filtered out every smell. |
|
|
321
|
+
| `flay.max_score` / `flog.max_score` now actually filter | Expect more accurate filtering — previously these keys were no-ops. |
|
|
322
|
+
| Default severity `:info` → `:warning` | To keep `:info`, set `PRONTO_RUBYCRITIC_SEVERITY_LEVEL=info` or add `rubycritic.severity_level: info` to `.pronto.yml`. |
|
|
323
|
+
| `PRONTO_REEK_SEVERITY_LEVEL` deprecated | Rename to `PRONTO_RUBYCRITIC_SEVERITY_LEVEL`. The legacy var still works with a deprecation warning. |
|
|
324
|
+
| `Pronto::RubyCritic::VERSION` added | Existing `Pronto::RubyCriticVersion::VERSION` still works. |
|
|
325
|
+
| `rubycritic` dependency now pinned `< 6.0` | If you pin RubyCritic 6.x in your own `Gemfile`, wait for pronto-rubycritic 0.13. |
|
|
326
|
+
|
|
327
|
+
Full breaking-change rationale in [CHANGELOG.md](CHANGELOG.md).
|
|
328
|
+
|
|
329
|
+
## Contributing
|
|
330
|
+
|
|
331
|
+
1. Fork the repo
|
|
332
|
+
2. Create a feature branch (`git checkout -b feature/amazing-thing`)
|
|
333
|
+
3. Make your changes with tests
|
|
334
|
+
4. Run the full CI suite locally: `bundle exec rake ci`
|
|
335
|
+
5. Open a PR
|
|
336
|
+
|
|
337
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more detail. All contributions must
|
|
338
|
+
pass RuboCop, Reek, RSpec with ≥ 90 % line coverage, and RubyCritic ≥ 75 score.
|
|
339
|
+
|
|
340
|
+
## License
|
|
341
|
+
|
|
342
|
+
[MIT](LICENSE) © Rishabh Singh. See [LICENSE](LICENSE) for the full text.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rubycritic'
|
|
4
|
+
require 'rubycritic/analysers_runner'
|
|
5
|
+
|
|
6
|
+
module Pronto
|
|
7
|
+
class RubyCritic < Runner
|
|
8
|
+
# Thin wrapper around RubyCritic's AnalysersRunner. Owns the side-effect
|
|
9
|
+
# of configuring RubyCritic's global Config; no other class in this gem
|
|
10
|
+
# touches ::RubyCritic::Config.
|
|
11
|
+
class Analyser
|
|
12
|
+
def initialize(paths)
|
|
13
|
+
@paths = Array(paths).map(&:to_s).uniq.reject(&:empty?)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
return [] if @paths.empty?
|
|
18
|
+
|
|
19
|
+
configure_rubycritic
|
|
20
|
+
::RubyCritic::AnalysersRunner.new(@paths).run
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def configure_rubycritic
|
|
26
|
+
::RubyCritic::Config.source_control_system =
|
|
27
|
+
::RubyCritic::SourceControlSystem::Base.create
|
|
28
|
+
::RubyCritic::Config.set(paths: @paths)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'pronto/config_file'
|
|
5
|
+
|
|
6
|
+
module Pronto
|
|
7
|
+
class RubyCritic < Runner
|
|
8
|
+
module ConfigLoader
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Top-level keys that SmellFilter expects to be sub-mappings. A non-Hash
|
|
12
|
+
# value here (e.g. `reek: false`, `complexity: 10`) is a user typo; we
|
|
13
|
+
# drop it with a warning rather than letting it crash SmellFilter#dig.
|
|
14
|
+
RECOGNISED_SECTIONS = %w[reek flay flog complexity churn].freeze
|
|
15
|
+
|
|
16
|
+
def load_runner_config(filename, cwd: Dir.pwd)
|
|
17
|
+
path = File.join(cwd, filename)
|
|
18
|
+
return {} unless File.exist?(path)
|
|
19
|
+
|
|
20
|
+
# aliases are deliberately NOT permitted: YAML anchors/aliases are not
|
|
21
|
+
# used by this gem's config schema, and enabling them exposes a
|
|
22
|
+
# billion-laughs expansion DoS on a config file that, in CI, can be
|
|
23
|
+
# supplied by an untrusted pull request.
|
|
24
|
+
parsed = YAML.safe_load_file(path, permitted_classes: [Symbol])
|
|
25
|
+
return {} if parsed.nil?
|
|
26
|
+
return validate_sections(parsed, filename) if parsed.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
# YAML like ":\n:\n- [" parses to a Symbol / Array / scalar — not a
|
|
29
|
+
# Hash. SmellFilter expects a Hash (calls #dig). Reject anything else.
|
|
30
|
+
warn("pronto-rubycritic: #{filename} must be a YAML mapping (Hash); " \
|
|
31
|
+
"got #{parsed.class}. Ignoring.")
|
|
32
|
+
{}
|
|
33
|
+
rescue Psych::Exception => e
|
|
34
|
+
# Covers SyntaxError, DisallowedClass, and AliasesNotEnabled (the last
|
|
35
|
+
# one fires when a config uses a YAML alias, which we intentionally
|
|
36
|
+
# disable above).
|
|
37
|
+
warn("pronto-rubycritic: invalid YAML in #{filename}: #{e.class}: #{e.message}")
|
|
38
|
+
{}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def validate_sections(config, filename)
|
|
42
|
+
config.each_with_object({}) do |(key, value), acc|
|
|
43
|
+
if RECOGNISED_SECTIONS.include?(key) && !value.is_a?(Hash)
|
|
44
|
+
warn("pronto-rubycritic: #{filename}: section '#{key}' must be a mapping " \
|
|
45
|
+
"(got #{value.class}); ignoring it.")
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
acc[key] = value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def load_pronto_config
|
|
54
|
+
Pronto::ConfigFile.new.to_h
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
warn("pronto-rubycritic: could not load .pronto.yml: #{e.class}: #{e.message}")
|
|
57
|
+
{}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resolve_severity(env_value:, pronto_config:, valid_levels:, default:)
|
|
61
|
+
raw = first_non_blank(env_value, pronto_severity(pronto_config))
|
|
62
|
+
return default if raw.nil?
|
|
63
|
+
|
|
64
|
+
sym = raw.to_s.strip.downcase.to_sym
|
|
65
|
+
return sym if valid_levels.include?(sym)
|
|
66
|
+
|
|
67
|
+
warn("pronto-rubycritic: invalid severity #{raw.inspect}, falling back to #{default}.")
|
|
68
|
+
default
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Reads severity_level from .pronto.yml's `rubycritic:` section without
|
|
72
|
+
# assuming the section is a Hash — a non-Hash section (e.g. `rubycritic: x`)
|
|
73
|
+
# would otherwise raise TypeError from Hash#dig and be swallowed upstream.
|
|
74
|
+
def pronto_severity(pronto_config)
|
|
75
|
+
section = pronto_config['rubycritic'] if pronto_config.is_a?(Hash)
|
|
76
|
+
section.is_a?(Hash) ? section['severity_level'] : nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def first_non_blank(*values)
|
|
80
|
+
values.find { |v| !v.nil? && !v.to_s.strip.empty? }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|