rubocop-gusto 10.8.0 → 10.9.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e949d8014c77fd20ec618ea6aac33438bc6d211a47748ae12d80a9fee1eb7416
4
- data.tar.gz: f6d9c0e2fd5cadffe3a9b7fbeb9b37231d364885109ce7be731c6852dada912d
3
+ metadata.gz: bfe32904ee2cd054ac91081d5cab3f9cd4b15deb007e816ca1afefdf00aa41b0
4
+ data.tar.gz: 244866f38efc30bc95881ff3129664f136448284209a5b04f76bbe9241338d89
5
5
  SHA512:
6
- metadata.gz: f3cd3c47b88b1d80a4d1eebd62cc5a9bce4cd4f589b03b854654aadfa00aaba5f2e4a574883d99b43b14a5666abc998f388f8e13b59cf61e607d7635b33289d3
7
- data.tar.gz: cb4487b2568f8ea75b3dc1d21903c9d60f4702c1ecf6e3e5f85b9c3b559716ec21a86570f7b574e9bb8ea37ba6786c2a7f1f94db5afaa27faba6bc67d8843efe
6
+ metadata.gz: ec813a15822dbd2030267110dd697142e51c705bf48c9c2b197d0b6567d4373a2d389e1465a12f67df5edc640d932e2a3587fc4fb2a921577594ff93a78ecc07
7
+ data.tar.gz: abf67fb4291bc44ab9b86c3a0bdf91c9871af49bee41ac8de6f9f986888b6231cecb3cb9d754289b97ae389a1aa56e7727cc5f64097d5140fadf59f286c55fd8
data/CHANGELOG.md CHANGED
@@ -1,10 +1,33 @@
1
- ## Pending
2
-
3
1
  ## 10.8.0
4
2
 
5
3
  - Remove redundant `Rails: Enabled: true` from `config/rails.yml` (already set by rubocop-rails' own defaults)
6
4
  - Enable `Rails/DefaultScope` cop (disabled by default in rubocop-rails)
7
5
 
6
+ ## [10.9.1](https://github.com/Gusto/rubocop-gusto/compare/v10.9.0...v10.9.1) (2026-05-22)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * remove redundant config entries ([#112](https://github.com/Gusto/rubocop-gusto/issues/112)) ([e3dab8d](https://github.com/Gusto/rubocop-gusto/commit/e3dab8d3f96907a4b4be955fd3407926aa47b5a7))
12
+
13
+ ## [10.9.0](https://github.com/Gusto/rubocop-gusto/compare/v10.8.1...v10.9.0) (2026-05-22)
14
+
15
+
16
+ ### Features
17
+
18
+ * make rubocop-rspec move/scatter cops Sorbet-sig-aware ([#107](https://github.com/Gusto/rubocop-gusto/issues/107)) ([b3f1449](https://github.com/Gusto/rubocop-gusto/commit/b3f14491b74548adbd05738b90484ba6ccb5ea67))
19
+
20
+ ## [10.8.1](https://github.com/Gusto/rubocop-gusto/compare/v10.8.0...v10.8.1) (2026-03-30)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * Change runner configuration to use custom group ([#95](https://github.com/Gusto/rubocop-gusto/issues/95)) ([87af85d](https://github.com/Gusto/rubocop-gusto/commit/87af85d17c150d689ca73448e0d2b5372968f21b))
26
+ * correct release-please-action pinned SHA ([#89](https://github.com/Gusto/rubocop-gusto/issues/89)) ([87db13a](https://github.com/Gusto/rubocop-gusto/commit/87db13ad17f60d0398a84dc7957515cac623efee))
27
+ * pin GitHub Actions to commit SHAs to prevent supply-chain attacks ([#85](https://github.com/Gusto/rubocop-gusto/issues/85)) ([bc85834](https://github.com/Gusto/rubocop-gusto/commit/bc85834d5fbed70278f9cd67eff6e564fb4e9925))
28
+ * remove extra empty line at block body end in execute_migration_spec ([87af85d](https://github.com/Gusto/rubocop-gusto/commit/87af85d17c150d689ca73448e0d2b5372968f21b))
29
+ * use GitHub App token in release-please to trigger CI on PRs ([#98](https://github.com/Gusto/rubocop-gusto/issues/98)) ([a2d3171](https://github.com/Gusto/rubocop-gusto/commit/a2d3171909ee8fe04be233b13adb8aaac48e0bef))
30
+
8
31
  ## 10.7.0
9
32
 
10
33
  - Improve `Rack/LowercaseHeaderKeys` for Rack 3 migration
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
- # Rubocop::Gusto
1
+ # RuboCop::Gusto
2
2
 
3
- Gusto's Ruby style guide implemented as RuboCop rules.
3
+ [![Gem Version](https://img.shields.io/gem/v/rubocop-gusto)](https://rubygems.org/gems/rubocop-gusto)
4
+ [![Build](https://img.shields.io/github/actions/workflow/status/Gusto/rubocop-gusto/build.yml?branch=main)](https://github.com/Gusto/rubocop-gusto/actions/workflows/build.yml)
5
+ [![GitHub Release](https://img.shields.io/github/v/release/Gusto/rubocop-gusto)](https://github.com/Gusto/rubocop-gusto/releases)
6
+
7
+ Gusto's custom [RuboCop](https://rubocop.org/) cops and shared configuration, distributed as a gem and integrated via the [`lint_roller`](https://github.com/standardrb/lint_roller) plugin interface.
4
8
 
5
9
  ## Installation
6
10
 
@@ -18,36 +22,70 @@ $ bundle
18
22
 
19
23
  ## Usage
20
24
 
21
- `rubocop-gusto` ships with an executable that updates and maintains .rubocop.yml.
25
+ `rubocop-gusto` ships with a CLI that sets up your project's `.rubocop.yml`:
22
26
 
23
27
  ```sh
24
28
  bundle exec rubocop-gusto init
25
29
  ```
26
30
 
31
+ This adds `rubocop-gusto` to your `.rubocop.yml` `plugins:` list and includes any relevant configuration (e.g. Rails-specific rules when Rails is detected).
32
+
27
33
  If this is an existing project, it is recommended to run the autocorrector (`bundle exec rubocop -a`) and then to regenerate the `.rubocop_todo.yml` (`bundle exec rubocop --auto-gen-config`), so issues can be dealt with piecemeal.
28
34
 
35
+ ### Available cops
36
+
37
+ Custom cops live under the following namespaces:
38
+
39
+ - `Gusto/` — general Gusto-specific cops (see [`lib/rubocop/cop/gusto/`](lib/rubocop/cop/gusto/))
40
+ - `Rack/` — cops scoped to Rack middleware patterns (see [`lib/rubocop/cop/rack/`](lib/rubocop/cop/rack/))
41
+
29
42
  ## Publishing New Versions
30
43
 
31
- To publish a new release:
44
+ Releases are fully automated via [release-please](https://github.com/googleapis/release-please).
45
+
46
+ **How it works:**
47
+
48
+ 1. Merge PRs to `main` using [Conventional Commits](https://www.conventionalcommits.org/) in the PR title (enforced by CI).
49
+ 2. release-please automatically creates and maintains a "Release PR" that bumps the version and updates the changelog.
50
+ 3. When you merge the Release PR, a GitHub Release and git tag are created, and the gem is published to RubyGems.
32
51
 
33
- 1. Update the `RuboCop::Gusto::VERSION` constant to a higher version number conforming to this project's [versioning policy](#versioning-policy).
34
- 2. Document your changes in the [changelog](CHANGELOG.md).
35
- 3. Open a pull request and follow the typical review/merge process.
36
- 4. TODO: finish release process
52
+ **Conventional commit types and version bumps:**
37
53
 
38
- After publishing, wait for dependabot, or make a new PR downstream to update to the latest version.
54
+ | PR title prefix | Version bump | Example |
55
+ |---|---|---|
56
+ | `feat:` | Minor (10.8.0 -> 10.9.0) | `feat: add Gusto/NewCop cop` |
57
+ | `fix:` | Patch (10.8.0 -> 10.8.1) | `fix: correct false positive in DefaultScope` |
58
+ | `feat!:` or `BREAKING CHANGE` footer | Major (10.8.0 -> 11.0.0) | `feat!: drop Ruby 3.1 support` |
59
+ | `chore:`, `docs:`, `ci:`, etc. | No release | `chore: update dev dependencies` |
39
60
 
40
61
  ## Contributing
41
62
 
42
63
  Submit new rules, updated configuration, and other checks to be used organization wide by submitting a Pull Request!
43
64
 
65
+ PR titles must use [Conventional Commits](https://www.conventionalcommits.org/) format (enforced by CI) — see the version bump table above for which prefixes trigger releases.
66
+
67
+ ### Adding a new cop
68
+
69
+ 1. Create `lib/rubocop/cop/gusto/<cop_name>.rb`
70
+ 2. Add an entry to `config/default.yml`, then sort it:
71
+ ```sh
72
+ bundle exec rubocop-gusto sort config/default.yml
73
+ ```
74
+ 3. Add a spec in `spec/rubocop/cop/gusto/<cop_name>_spec.rb`
75
+ 4. Run tests and lint:
76
+ ```sh
77
+ bundle exec rspec
78
+ bundle exec rubocop
79
+ ```
80
+
44
81
  ### Versioning policy
45
82
 
46
- Rubocop-gusto generally follows semver, with the exception that the only thing that is considered a breaking change is a change in the public API to use rubocop-gusto.
83
+ rubocop-gusto generally follows semver, with the exception that the only thing that is considered a breaking change is a change in the public API to use rubocop-gusto.
47
84
 
48
85
  Users can generally expect to need to regenerate their rubocop todo when they make a minor version bump to rubocop-gusto.
49
86
 
50
- ### Git Pre-Commit Hooks
51
- ```
87
+ ### Git pre-commit hooks
88
+
89
+ ```sh
52
90
  git config core.hooksPath script/githooks
53
91
  ```
data/config/default.yml CHANGED
@@ -155,37 +155,13 @@ Layout/BlockAlignment:
155
155
 
156
156
  Layout/CaseIndentation:
157
157
  EnforcedStyle: end
158
- IndentOneStep: false
159
-
160
- Layout/DotPosition:
161
- Enabled: true
162
- # We use the (default) leading dot position for Sorbet and IDE compatibility.
163
- EnforcedStyle: leading
164
-
165
- Layout/EmptyLineAfterGuardClause:
166
- Enabled: true
167
-
168
- Layout/EmptyLineAfterMagicComment:
169
- Enabled: true
170
-
171
- Layout/EmptyLineBetweenDefs:
172
- AllowAdjacentOneLineDefs: true
173
158
 
174
159
  Layout/EmptyLinesAroundAttributeAccessor:
175
160
  Enabled: false
176
161
 
177
- Layout/EmptyLinesAroundClassBody:
178
- EnforcedStyle: no_empty_lines
179
-
180
- Layout/EmptyLinesAroundModuleBody:
181
- EnforcedStyle: no_empty_lines
182
-
183
162
  Layout/EndAlignment:
184
163
  EnforcedStyleAlignWith: start_of_line
185
164
 
186
- Layout/ExtraSpacing:
187
- AllowForAlignment: true
188
-
189
165
  Layout/FirstArgumentIndentation:
190
166
  EnforcedStyle: consistent
191
167
 
@@ -211,53 +187,22 @@ Layout/LineLength:
211
187
  # TODO: Pick some maximum like 200 to start with
212
188
  Enabled: false
213
189
 
214
- Layout/MultilineArrayBraceLayout:
215
- EnforcedStyle: symmetrical
216
-
217
- Layout/MultilineAssignmentLayout:
218
- Enabled: false
219
-
220
- Layout/MultilineHashBraceLayout:
221
- EnforcedStyle: symmetrical
222
-
223
- Layout/MultilineMethodCallBraceLayout:
224
- EnforcedStyle: symmetrical
225
-
226
190
  Layout/MultilineMethodCallIndentation:
227
191
  EnforcedStyle: indented
228
192
 
229
- Layout/MultilineMethodDefinitionBraceLayout:
230
- EnforcedStyle: symmetrical
231
-
232
193
  Layout/MultilineOperationIndentation:
233
194
  EnforcedStyle: indented
234
195
 
235
196
  Layout/ParameterAlignment:
236
197
  EnforcedStyle: with_fixed_indentation
237
198
 
238
- Layout/SpaceAroundEqualsInParameterDefault:
239
- Enabled: true
240
-
241
- Layout/SpaceAroundMethodCallOperator:
242
- Enabled: true
243
-
244
199
  Layout/SpaceInLambdaLiteral:
245
200
  EnforcedStyle: require_space
246
201
 
247
202
  Lint/AmbiguousBlockAssociation:
248
- Enabled: true
249
203
  Exclude:
250
204
  - '**/spec/**/*'
251
205
 
252
- Lint/EmptyFile:
253
- Enabled: true
254
-
255
- Lint/EnsureReturn:
256
- Enabled: true
257
-
258
- Lint/RaiseException:
259
- Enabled: true
260
-
261
206
  Lint/SelfAssignment:
262
207
  Severity: error
263
208
 
@@ -287,7 +232,6 @@ Metrics/BlockLength:
287
232
  Enabled: false
288
233
 
289
234
  Metrics/ClassLength:
290
- Enabled: true
291
235
  Max: 2500
292
236
 
293
237
  Metrics/CyclomaticComplexity:
@@ -333,16 +277,9 @@ Performance/RedundantBlockCall:
333
277
  Performance/ZipWithoutBlock:
334
278
  Enabled: true
335
279
 
336
- RSpec:
337
- Include:
338
- - '**/spec/**/*'
339
-
340
280
  RSpec/ContainExactly:
341
281
  Enabled: false
342
282
 
343
- RSpec/ContextWording:
344
- Enabled: true
345
-
346
283
  RSpec/DescribeClass:
347
284
  Enabled: false
348
285
 
@@ -370,12 +307,6 @@ RSpec/ExpectInHook:
370
307
  RSpec/IndexedLet:
371
308
  Enabled: false
372
309
 
373
- RSpec/IteratedExpectation:
374
- Enabled: true
375
-
376
- RSpec/LeadingSubject:
377
- Enabled: true
378
-
379
310
  RSpec/LetSetup:
380
311
  Enabled: false
381
312
 
@@ -417,12 +348,6 @@ RSpec/ReceiveMessages:
417
348
  RSpec/ScatteredSetup:
418
349
  AutoCorrect: false
419
350
 
420
- RSpec/SpecFilePathFormat:
421
- Enabled: true
422
-
423
- RSpec/SpecFilePathSuffix:
424
- Enabled: true
425
-
426
351
  RSpec/StubbedMock:
427
352
  Enabled: false
428
353
 
@@ -452,12 +377,6 @@ Rack/LowercaseHeaderKeys:
452
377
  Rake/ClassDefinitionInTask:
453
378
  Enabled: false
454
379
 
455
- Rake/Desc:
456
- Enabled: true
457
-
458
- Security/YAMLLoad:
459
- Enabled: true
460
-
461
380
  Sorbet:
462
381
  # TODO: validate these choices for Harmonization
463
382
  # BindingConstantWithoutTypeAlias
@@ -466,11 +385,6 @@ Sorbet:
466
385
  # SignatureBuildOrder
467
386
  Enabled: true
468
387
 
469
- Sorbet/FalseSigil:
470
- # We want to avoid `typed: ignore` as much as possible, as it breaks LSP tooling.
471
- Include:
472
- - "**/*.{rb,rbi,rake,ru}"
473
-
474
388
  Sorbet/Refinement:
475
389
  # Still marked pending upstream, we contributed this and enable it here.
476
390
  Enabled: true
@@ -479,8 +393,6 @@ Sorbet/StrictSigil:
479
393
  # Forgot the difference between typed levels? (ignore, false, true, strict, and strong)
480
394
  # Check this out: https://sorbet.org/docs/static#file-level-granularity-strictness-levels
481
395
  Enabled: true
482
- Include:
483
- - "**/*.{rb,rbi,rake,ru}"
484
396
  Exclude:
485
397
  - bin/**/*
486
398
  - db/**/*.rb
@@ -488,7 +400,6 @@ Sorbet/StrictSigil:
488
400
  - spec/**/*spec.rb
489
401
 
490
402
  Sorbet/ValidSigil:
491
- Enabled: true
492
403
  RequireSigilOnAllFiles: true
493
404
  # We don't want to require any specific typed level at this point – only that there IS a typed sigil.
494
405
  MinimumStrictness: ignore
@@ -503,7 +414,6 @@ Style/AccessorGrouping:
503
414
  Enabled: false
504
415
 
505
416
  Style/Alias:
506
- Enabled: true
507
417
  EnforcedStyle: prefer_alias_method
508
418
 
509
419
  Style/ArgumentsForwarding:
@@ -517,7 +427,6 @@ Style/AutoResourceCleanup:
517
427
  Enabled: true
518
428
 
519
429
  Style/BlockDelimiters:
520
- EnforcedStyle: line_count_based
521
430
  AllowedMethods:
522
431
  - it
523
432
  - expect
@@ -529,15 +438,11 @@ Style/ClassAndModuleChildren:
529
438
  # EnforcedStyle: compact
530
439
  Enabled: false
531
440
 
532
- Style/CollectionMethods:
533
- Enabled: false
534
-
535
441
  Style/CommandLiteral:
536
442
  # This cop Style/CommandLiteral protects us from accidentally using backticks for strings quotes.
537
443
  # Forcing the use of %x() should make it more obvious visually where the command literals are.
538
444
  # This is easy to miss, but is a bug-without-test-failures at best and an opportunity for an
539
445
  # attack vector at worst.
540
- Enabled: true
541
446
  EnforcedStyle: percent_x
542
447
 
543
448
  Style/ConditionalAssignment:
@@ -560,9 +465,6 @@ Style/ExpandPathArguments:
560
465
  Exclude:
561
466
  - '**/bin/*'
562
467
 
563
- Style/ExponentialNotation:
564
- Enabled: true
565
-
566
468
  Style/FormatString:
567
469
  Enabled: false
568
470
 
@@ -571,15 +473,11 @@ Style/FormatStringToken:
571
473
 
572
474
  Style/FrozenStringLiteralComment:
573
475
  EnforcedStyle: always_true
574
- Enabled: true
575
476
 
576
477
  # This can make lines longer and impair readability
577
478
  Style/GuardClause:
578
479
  Enabled: false
579
480
 
580
- Style/HashEachMethods:
581
- Enabled: true
582
-
583
481
  Style/HashSyntax:
584
482
  EnforcedStyle: ruby19_no_mixed_keys
585
483
 
@@ -595,9 +493,6 @@ Style/IfInsideElse:
595
493
  Style/IfUnlessModifier:
596
494
  Enabled: false
597
495
 
598
- Style/ImplicitRuntimeError:
599
- Enabled: false
600
-
601
496
  Style/ItBlockParameter:
602
497
  Enabled: true
603
498
  EnforcedStyle: only_numbered_parameters
@@ -609,7 +504,6 @@ Style/LambdaCall:
609
504
  Enabled: false
610
505
 
611
506
  Style/MethodCallWithArgsParentheses:
612
- IgnoreMacros: true
613
507
  AllowedMethods:
614
508
  # Ruby
615
509
  - puts
@@ -698,18 +592,11 @@ Style/MethodCallWithArgsParentheses:
698
592
  - input
699
593
  - action
700
594
 
701
- Style/MethodCalledOnDoEndBlock:
702
- Enabled: false
703
-
704
- Style/MissingElse:
705
- Enabled: false
706
-
707
595
  Style/ModuleFunction:
708
596
  # Sorbet does not enforce the singleton version of module function methods: https://github.com/sorbet/sorbet/issues/8531
709
597
  # Even if did, requiring the code to typecheck both paths would be a pain for maintainability.
710
598
  # Also, it's better for the code to be explicit about which version of the method is being called,
711
599
  # as it is one fewer decision the downstream developer has to make.
712
- Enabled: true
713
600
  EnforcedStyle: forbidden
714
601
 
715
602
  Style/MultilineBlockChain:
@@ -728,9 +615,6 @@ Style/Next:
728
615
  Style/NumericPredicate:
729
616
  Enabled: false
730
617
 
731
- Style/OptionHash:
732
- Enabled: false
733
-
734
618
  Style/OptionalBooleanParameter:
735
619
  AllowedMethods:
736
620
  - respond_to_missing?
@@ -745,9 +629,6 @@ Style/PercentLiteralDelimiters:
745
629
  '%w': ()
746
630
  '%W': ()
747
631
 
748
- Style/RaiseArgs:
749
- Enabled: true
750
-
751
632
  Style/RedundantSelf:
752
633
  Enabled: false
753
634
 
@@ -755,22 +636,15 @@ Style/RegexpLiteral:
755
636
  Enabled: false
756
637
 
757
638
  Style/RescueStandardError:
758
- Enabled: true
759
639
  AutoCorrect: true
760
640
  EnforcedStyle: 'implicit'
761
641
 
762
642
  Style/Send:
763
643
  Enabled: true
764
644
 
765
- Style/SingleLineBlockParams:
766
- Enabled: false
767
-
768
645
  Style/SlicingWithRange:
769
646
  Enabled: false
770
647
 
771
- Style/SpecialGlobalVars:
772
- Enabled: true
773
-
774
648
  Style/StringLiterals:
775
649
  Enabled: false
776
650
 
@@ -780,22 +654,13 @@ Style/StringMethods:
780
654
  Style/SymbolArray:
781
655
  Enabled: false
782
656
 
783
- Style/SymbolProc:
784
- Enabled: true
785
-
786
657
  Style/TernaryParentheses:
787
658
  Enabled: false
788
659
 
789
- Style/TrailingCommaInArguments:
790
- Enabled: true
791
- EnforcedStyleForMultiline: no_comma # matches Standard https://github.com/standardrb/standard/blob/250b306cd44bea509d20023d9ab63170da67c815/config/base.yml#L1857
792
-
793
660
  Style/TrailingCommaInArrayLiteral:
794
- Enabled: true
795
661
  EnforcedStyleForMultiline: consistent_comma
796
662
 
797
663
  Style/TrailingCommaInHashLiteral:
798
- Enabled: true
799
664
  EnforcedStyleForMultiline: consistent_comma
800
665
 
801
666
  Style/TrivialAccessors:
data/config/rails.yml CHANGED
@@ -31,9 +31,6 @@ Performance/DoubleStartEndWith:
31
31
  Rails/ActiveRecordAliases:
32
32
  Enabled: false
33
33
 
34
- Rails/ActiveRecordOverride:
35
- Enabled: true
36
-
37
34
  Rails/ApplicationRecord:
38
35
  AutoCorrect: true
39
36
 
@@ -42,7 +39,6 @@ Rails/BulkChangeTable:
42
39
 
43
40
  Rails/Date:
44
41
  AutoCorrect: false
45
- Enabled: true
46
42
 
47
43
  Rails/DefaultScope:
48
44
  Enabled: true
@@ -112,18 +108,6 @@ Rails/NotNullColumn:
112
108
  Rails/ReadWriteAttribute:
113
109
  Enabled: false
114
110
 
115
- Rails/SaveBang:
116
- SafeAutoCorrect: false
117
-
118
- Rails/SkipsModelValidations:
119
- Enabled: true
120
-
121
- Rails/TimeZone:
122
- Enabled: true
123
-
124
- Rails/UniqueValidationWithoutIndex:
125
- Enabled: true
126
-
127
111
  Rails/UnknownEnv:
128
112
  Environments:
129
113
  - demo
@@ -41,7 +41,7 @@ module RuboCop
41
41
  METHODS_TO_CHECK = %i(match? include? ==).to_set.freeze
42
42
 
43
43
  def on_rescue(node)
44
- node.resbody_branches.last.each_descendant(:if, :unless).each do |condition_node|
44
+ node.resbody_branches.last.each_descendant(:if, :unless) do |condition_node|
45
45
  add_offense(condition_node) if message_check?(condition_node)
46
46
  end
47
47
  end
@@ -30,6 +30,11 @@ module RuboCop
30
30
  WORKER_FALLBACK = %w(Sidekiq::Worker).freeze
31
31
  WORKER_MODULES = "WorkerModules"
32
32
 
33
+ # @!method worker_module_include?(node)
34
+ def_node_matcher :worker_module_include?, <<~PATTERN
35
+ (send nil? :include (const _ _))
36
+ PATTERN
37
+
33
38
  def on_def(node)
34
39
  return unless node.method?(:perform)
35
40
  return unless (method_type = perform_class_method_type(node))
@@ -51,19 +56,12 @@ module RuboCop
51
56
 
52
57
  def is_sidekiq_worker?(search_node, method_type)
53
58
  search_node = search_node.parent if method_type == :sclass
54
- search_node.parent.children.any? do |sibling|
55
- next if sibling.nil?
56
- next unless is_include?(sibling)
57
- next unless sibling.first_argument.const_type?
58
-
59
- worker_modules.include?(sibling.first_argument.const_name)
59
+ search_node.parent.each_child_node.any? do |sibling|
60
+ worker_module_include?(sibling) &&
61
+ worker_modules.include?(sibling.first_argument.const_name)
60
62
  end
61
63
  end
62
64
 
63
- def is_include?(node)
64
- node.send_type? && node.method?(:include)
65
- end
66
-
67
65
  def worker_modules
68
66
  @worker_modules ||= cop_config.fetch(WORKER_MODULES, WORKER_FALLBACK)
69
67
  end
@@ -69,7 +69,7 @@ module RuboCop
69
69
  if type_validation?(validation_node) && validation_node.first_argument.value == type_field
70
70
  has_validation = true
71
71
  # Check for allow_blank in the validation options
72
- validation_node.arguments[1].each_node(:pair) do |pair_node|
72
+ validation_node.last_argument.each_node(:pair) do |pair_node|
73
73
  has_allow_blank = true if allow_blank?(pair_node)
74
74
  end
75
75
  elsif polymorphic_methods_for?(validation_node) && validation_node.first_argument.value == relation_name
@@ -52,19 +52,24 @@ module RuboCop
52
52
  )
53
53
  ).freeze
54
54
  MSG = "Use Feature Flags or config instead of `Rails.env`."
55
- PROHIBITED_CLASS = "Rails"
56
55
  RESTRICT_ON_SEND = %i(env).freeze
57
56
 
58
- def on_send(node)
59
- return unless node.receiver&.const_name == PROHIBITED_CLASS
57
+ # @!method prohibited_rails_env?(node)
58
+ def_node_matcher :prohibited_rails_env?, <<~PATTERN
59
+ (send
60
+ (send (const _ :Rails) :env)
61
+ #prohibited_predicate?
62
+ )
63
+ PATTERN
60
64
 
61
- return unless (parent = node.parent)
62
- return unless parent.send_type?
63
- return unless parent.predicate_method?
65
+ def on_send(node)
66
+ add_offense(node.parent) if prohibited_rails_env?(node.parent)
67
+ end
64
68
 
65
- return if ALLOWED_LIST.include?(parent.method_name)
69
+ private
66
70
 
67
- add_offense(parent)
71
+ def prohibited_predicate?(name)
72
+ name.to_s.end_with?("?") && !ALLOWED_LIST.include?(name)
68
73
  end
69
74
  end
70
75
  end
@@ -33,6 +33,11 @@ module RuboCop
33
33
  MSG = "Don't mock #{CLASSES.join('/')} directly. Use Rails Testing Time Helpers (eg `freeze_time` and `travel_to`) instead.".freeze
34
34
  RESTRICT_ON_SEND = %i(to).freeze
35
35
 
36
+ # @!method and_call_original?(node)
37
+ def_node_search :and_call_original?, <<~PATTERN
38
+ (send _ :and_call_original)
39
+ PATTERN
40
+
36
41
  # Matches allow/expect with a time class (or chain) receiver and a `receive` or `receive_message_chain`
37
42
  # Examples matched:
38
43
  # allow(Time).to receive(:now)
@@ -70,9 +75,7 @@ module RuboCop
70
75
  return false if node.nil?
71
76
 
72
77
  current = node
73
- while current.respond_to?(:send_type?) && current.send_type?
74
- current = current.receiver
75
- end
78
+ current = current.receiver while current&.send_type?
76
79
 
77
80
  if current.nil?
78
81
  return false
@@ -83,16 +86,14 @@ module RuboCop
83
86
  # Accept both `Time` and `::Time` as root-level constants
84
87
  namespace = current.namespace
85
88
  is_root_level = namespace.nil? || namespace.cbase_type?
86
- is_root_level && CLASSES.include?(current.children[1])
89
+ is_root_level && CLASSES.include?(current.short_name)
87
90
  end
88
91
 
89
92
  def and_call_original_in_chain?(node)
90
93
  return false if node.nil?
91
94
  return false unless node.send_type?
92
95
 
93
- return true if node.method?(:and_call_original)
94
-
95
- node.each_descendant(:send).any? { |send_node| send_node.method?(:and_call_original) }
96
+ and_call_original?(node)
96
97
  end
97
98
  end
98
99
  end
@@ -5,15 +5,14 @@ module RuboCop
5
5
  module Gusto
6
6
  class SidekiqParams < Base
7
7
  MSG = "Sidekiq perform methods cannot take keyword arguments"
8
- PROHIBITED_ARG_TYPES = Set.new(%i(kwoptarg kwarg)).freeze
9
8
 
10
- def on_def(node)
11
- return unless node.method?(:perform)
12
- return if node.arguments.empty?
9
+ # @!method perform_with_kwargs?(node)
10
+ def_node_matcher :perform_with_kwargs?, <<~PATTERN
11
+ (def :perform (args <{kwarg kwoptarg} ...>) ...)
12
+ PATTERN
13
13
 
14
- node.arguments.each_child_node do |arg|
15
- add_offense(node) if PROHIBITED_ARG_TYPES.include?(arg.type)
16
- end
14
+ def on_def(node)
15
+ add_offense(node) if perform_with_kwargs?(node)
17
16
  end
18
17
  end
19
18
  end
@@ -129,7 +129,7 @@ module RuboCop
129
129
 
130
130
  def colorized_string?(node)
131
131
  node.send_type? &&
132
- node.receiver.is_a?(RuboCop::AST::Node) &&
132
+ node.receiver &&
133
133
  string_or_colorized_receiver?(node.receiver)
134
134
  end
135
135
 
@@ -152,19 +152,16 @@ module RuboCop
152
152
  foreground = ":#{args.first.value}"
153
153
  elsif args.length == 1 && args.first.hash_type?
154
154
  # Hash argument, like colorize(color: :red, background: :blue)
155
- args.first.pairs.each do |pair|
156
- break unless pair.value.sym_type? # can't handle non-symbol arguments
155
+ args.first.each_pair do |key_node, value_node|
156
+ break unless value_node.sym_type? # can't handle non-symbol arguments
157
157
 
158
- key = pair.key.value
159
- value = ":#{pair.value.value}"
160
-
161
- case key
158
+ case key_node.value
162
159
  when :color
163
- foreground = value
160
+ foreground = ":#{value_node.value}"
164
161
  when :background
165
- background = value
162
+ background = ":#{value_node.value}"
166
163
  when :mode
167
- styles << value
164
+ styles << ":#{value_node.value}"
168
165
  else
169
166
  break # unknown key, skip the rest of the hash
170
167
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop/cop/rspec/scattered_let"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module RSpec
8
+ # Patches the upstream `RSpec/ScatteredLet` cop so that Sorbet `sig`
9
+ # declarations attached to `let`/`let!` blocks (Sorbet's RSpec mode)
10
+ # do not interrupt the consecutive-let chain.
11
+ #
12
+ # Without this patch the upstream cop flags valid `sig`+`let`
13
+ # arrangements because the intervening `sig` block breaks the
14
+ # consecutive-sibling check. (The sig-aware `MoveNode` patch in
15
+ # `lib/rubocop/gusto/move_node_patch.rb` handles dragging the `sig`
16
+ # along during autocorrect.)
17
+ #
18
+ # @example
19
+ # # good (no longer flagged)
20
+ # context "..." do
21
+ # sig { returns(Something) }
22
+ # let(:thing) { create(:something) }
23
+ #
24
+ # sig { returns(Other) }
25
+ # let(:other) { create(:other) }
26
+ # end
27
+ class ScatteredLet
28
+ LetUnit = Struct.new(:let, :start_index, :length)
29
+ private_constant :LetUnit
30
+
31
+ # @!method sig_block?(node)
32
+ def_node_matcher :sig_block?, <<~PATTERN
33
+ (block (send nil? :sig) _ _)
34
+ PATTERN
35
+
36
+ private
37
+
38
+ def check_let_declarations(body)
39
+ children = body.each_child_node.to_a
40
+ units = build_let_units(children)
41
+ return if units.empty?
42
+
43
+ reference_unit = units.first
44
+ expected_start = reference_unit.start_index
45
+
46
+ units.each do |unit|
47
+ if unit.start_index == expected_start
48
+ reference_unit = unit
49
+ else
50
+ add_offense(unit.let) do |corrector|
51
+ ::RuboCop::RSpec::Corrector::MoveNode.new(
52
+ unit.let, corrector, processed_source
53
+ ).move_after(reference_unit.let)
54
+ end
55
+ end
56
+ expected_start += unit.length
57
+ end
58
+ end
59
+
60
+ def build_let_units(children)
61
+ children.each_with_index.with_object([]) do |(node, idx), units|
62
+ next unless let?(node)
63
+
64
+ prev = idx.positive? ? children[idx - 1] : nil
65
+ if prev && sig_block?(prev)
66
+ units << LetUnit.new(node, prev.sibling_index, 2)
67
+ else
68
+ units << LetUnit.new(node, node.sibling_index, 1)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop/rspec/corrector/move_node"
4
+
5
+ module RuboCop
6
+ module RSpec
7
+ module Corrector
8
+ # Patches `MoveNode` to treat Sorbet `sig { ... }` blocks as part of the
9
+ # `let`/`subject`/hook they precede.
10
+ #
11
+ # Sorbet's RSpec mode attaches type signatures to memoized helpers via a
12
+ # `sig` block immediately above the declaration:
13
+ #
14
+ # sig { returns(Something) }
15
+ # let(:thing) { create(:something) }
16
+ #
17
+ # Several rubocop-rspec cops use `MoveNode` to relocate `let`/`subject`/
18
+ # hook nodes (`ScatteredLet`, `LeadingSubject`, `LetBeforeExamples`,
19
+ # `HooksBeforeExamples`). Without this patch, the move strands the `sig`
20
+ # at the original location. This patch:
21
+ #
22
+ # 1. Extends the source range of the moved node to include a preceding
23
+ # `sig` block, so `sig` is carried with the move.
24
+ # 2. In `move_before`, redirects the insertion point above a preceding
25
+ # `sig` on the destination, so the destination's `sig` pairing stays
26
+ # intact.
27
+ module SigAwareMoveNode
28
+ extend ::RuboCop::AST::NodePattern::Macros
29
+
30
+ # @!method sig_block?(node)
31
+ def_node_matcher :sig_block?, <<~PATTERN
32
+ (block (send nil? :sig ...) _ _)
33
+ PATTERN
34
+
35
+ def move_before(other)
36
+ sig = preceding_sig_block(other)
37
+ super(sig || other)
38
+ end
39
+
40
+ private
41
+
42
+ def node_range(node)
43
+ sig = preceding_sig_block(node)
44
+ return super unless sig
45
+
46
+ ::Parser::Source::Range.new(
47
+ buffer,
48
+ begin_pos_with_comment(sig).begin_pos,
49
+ end_line_position(node).end_pos
50
+ )
51
+ end
52
+
53
+ def preceding_sig_block(node)
54
+ prev = node.left_sibling
55
+ prev if sig_block?(prev)
56
+ end
57
+ end
58
+
59
+ MoveNode.prepend(SigAwareMoveNode)
60
+ end
61
+ end
62
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Gusto
5
- VERSION = "10.8.0"
5
+ VERSION = "10.9.1"
6
6
  end
7
7
  end
data/lib/rubocop-gusto.rb CHANGED
@@ -6,6 +6,7 @@ require "rubocop-rspec"
6
6
  require_relative "rubocop/gusto"
7
7
  require_relative "rubocop/gusto/version"
8
8
  require_relative "rubocop/gusto/plugin"
9
+ require_relative "rubocop/gusto/move_node_patch"
9
10
 
10
11
  # Require all cops
11
12
  Dir.glob(File.join(File.dirname(__FILE__), "rubocop/cop/**/*.rb")).each do |file|
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-gusto
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.8.0
4
+ version: 10.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineering
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-03-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: lint_roller
@@ -148,10 +148,12 @@ files:
148
148
  - lib/rubocop/cop/internal_affairs/assignment_first.rb
149
149
  - lib/rubocop/cop/internal_affairs/require_restrict_on_send.rb
150
150
  - lib/rubocop/cop/rack/lowercase_header_keys.rb
151
+ - lib/rubocop/cop/rspec/scattered_let.rb
151
152
  - lib/rubocop/gusto.rb
152
153
  - lib/rubocop/gusto/cli.rb
153
154
  - lib/rubocop/gusto/config_yml.rb
154
155
  - lib/rubocop/gusto/init.rb
156
+ - lib/rubocop/gusto/move_node_patch.rb
155
157
  - lib/rubocop/gusto/plugin.rb
156
158
  - lib/rubocop/gusto/templates/rubocop.yml
157
159
  - lib/rubocop/gusto/version.rb
@@ -174,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
176
  - !ruby/object:Gem::Version
175
177
  version: '0'
176
178
  requirements: []
177
- rubygems_version: 3.6.2
179
+ rubygems_version: 4.0.10
178
180
  specification_version: 4
179
181
  summary: A gem for sharing gusto rubocop rules
180
182
  test_files: []