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 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 `&nbsp;` 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
+ [![CI](https://github.com/Rishabhs343/custom-pronto-gem/actions/workflows/ci.yml/badge.svg)](https://github.com/Rishabhs343/custom-pronto-gem/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/pronto-rubycritic.svg)](https://rubygems.org/gems/pronto-rubycritic)
5
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2.2-ruby.svg)](https://www.ruby-lang.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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