rubocop 1.75.7 → 1.77.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -14
  3. data/config/default.yml +72 -7
  4. data/config/obsoletion.yml +6 -3
  5. data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
  6. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  7. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  8. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  9. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
  10. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  11. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  12. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  14. data/lib/rubocop/cop/layout/class_structure.rb +35 -0
  15. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  16. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -3
  17. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  18. data/lib/rubocop/cop/layout/line_length.rb +26 -5
  19. data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
  20. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
  21. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  22. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  23. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  24. data/lib/rubocop/cop/lint/float_comparison.rb +31 -4
  25. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  26. data/lib/rubocop/cop/lint/literal_as_condition.rb +19 -27
  27. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  28. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  29. data/lib/rubocop/cop/lint/self_assignment.rb +25 -0
  30. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  31. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  32. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  33. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  34. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  35. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  36. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  37. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  38. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  39. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  40. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  41. data/lib/rubocop/cop/naming/predicate_method.rb +281 -0
  42. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  43. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  44. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  45. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  46. data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
  47. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  48. data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
  49. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  50. data/lib/rubocop/cop/style/hash_conversion.rb +12 -3
  51. data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
  52. data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
  53. data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
  54. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  55. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  56. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  57. data/lib/rubocop/cop/style/redundant_format.rb +6 -1
  58. data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
  59. data/lib/rubocop/cop/style/redundant_parentheses.rb +26 -5
  60. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  61. data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
  62. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -1
  63. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  64. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  65. data/lib/rubocop/cop/team.rb +1 -1
  66. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  67. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  68. data/lib/rubocop/lsp/diagnostic.rb +4 -4
  69. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  70. data/lib/rubocop/version.rb +1 -1
  71. data/lib/rubocop.rb +8 -1
  72. data/lib/ruby_lsp/rubocop/addon.rb +2 -2
  73. metadata +14 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef87f85272e845ecc9477a7c890be9f5d8daa78ef1c9625c3252ca8bee58ee40
4
- data.tar.gz: aeb5a1576ba984944521e10cd92933cbf37080d492a25ed8e51bf46926dc0c4a
3
+ metadata.gz: 871a632c96e6a5c92e57b651037eaf7efd7412e64ac77440d5275027ef791d4e
4
+ data.tar.gz: f375ec6479b1f90193cd39e863411707d60cd071e170888005339d89a19ea65b
5
5
  SHA512:
6
- metadata.gz: 7f668097cd95658c24f521bed8e11b43c3b5ff01bdb49bc2e1cc5a71af630cb751a402bf8026f90cb95907fea56ff2cabe5ea0139329fa3e7fc31a46ca615fc4
7
- data.tar.gz: cb542cf88e2de605a086e69009dcb8ae2afa4d0b7a713671e6c134cf8b77e5ccd4f6e408c6566be62d57809552e7ef0de30b849b619b0192c358a1441d4060a8
6
+ metadata.gz: f8dac3eb5cb12741c110c8726ec64595cefb89700527144cc590d7febb676164e3902dff171594278f7895d1c3df336dea3e14f869d285a0c7080b1467bd6362
7
+ data.tar.gz: 4aadc7a712774810e1815f4f31bea43fc6d9e121a547621f57894c7df848a6459dfc962af0fb725f4fef6e2017b6a499c203cc05a4d2879d0e50aceaee156b95
data/README.md CHANGED
@@ -36,10 +36,11 @@ Working on RuboCop is often fun, but it also requires a great deal of time and e
36
36
  **RuboCop**'s installation is pretty standard:
37
37
 
38
38
  ```sh
39
- $ gem install rubocop
39
+ gem install rubocop
40
40
  ```
41
41
 
42
- If you'd rather install RuboCop using `bundler`, add a line for it in your `Gemfile` (but set the `require` option to `false`, as it is a standalone tool):
42
+ If you'd rather install RuboCop using `bundler`, add a line for it in your
43
+ `Gemfile` (but set the `require` option to `false`, as it is a standalone tool):
43
44
 
44
45
  ```rb
45
46
  gem 'rubocop', require: false
@@ -52,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
52
53
  in your `Gemfile`:
53
54
 
54
55
  ```rb
55
- gem 'rubocop', '~> 1.75', require: false
56
+ gem 'rubocop', '~> 1.77', require: false
56
57
  ```
57
58
 
58
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
@@ -61,12 +62,15 @@ See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) fo
61
62
 
62
63
  Just type `rubocop` in a Ruby project's folder and watch the magic happen.
63
64
 
64
- ```
65
- $ cd my/cool/ruby/project
66
- $ rubocop
65
+ ```sh
66
+ cd my/cool/ruby/project
67
+ rubocop
67
68
  ```
68
69
 
69
- You can also use this magic in your favorite editor with RuboCop's [built-in LSP server](https://docs.rubocop.org/rubocop/usage/lsp.html).
70
+ > [!TIP]
71
+ >
72
+ > You can also use this magic in your favorite editor with RuboCop's
73
+ > [built-in LSP server](https://docs.rubocop.org/rubocop/usage/lsp.html).
70
74
 
71
75
  ## Documentation
72
76
 
@@ -79,7 +83,7 @@ RuboCop officially supports the following runtime Ruby implementations:
79
83
  * MRI 2.7+
80
84
  * JRuby 9.4+
81
85
 
82
- Targets Ruby 2.0+ code analysis.
86
+ It targets Ruby 2.0+ for code analysis.
83
87
 
84
88
  See the [compatibility documentation](https://docs.rubocop.org/rubocop/compatibility.html) for further details.
85
89
 
@@ -91,7 +95,6 @@ If you use RuboCop in your project, you can include one of these badges in your
91
95
 
92
96
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-community-brightgreen.svg)](https://rubystyle.guide)
93
97
 
94
-
95
98
  Here are the Markdown snippets for the two badges:
96
99
 
97
100
  ``` markdown
@@ -109,7 +112,7 @@ Here's a list of RuboCop's core developers:
109
112
  * [Yuji Nakayama](https://github.com/yujinakayama) (retired)
110
113
  * [Evgeni Dzhelyov](https://github.com/edzhelyov) (retired)
111
114
  * [Ted Johansson](https://github.com/drenmi)
112
- * [Masataka Kuwabara](https://github.com/pocke)
115
+ * [Masataka Kuwabara](https://github.com/pocke) (retired)
113
116
  * [Koichi Ito](https://github.com/koic)
114
117
  * [Maxim Krizhanovski](https://github.com/darhazer)
115
118
  * [Benjamin Quorning](https://github.com/bquorning)
@@ -157,8 +160,8 @@ wide array of funding channels to account for your preferences
157
160
  currently [Open Collective](https://opencollective.com/rubocop) is our
158
161
  preferred funding platform).
159
162
 
160
- **If you're working in a company that's making significant use of RuboCop we'd appreciate it if you suggest to your company
161
- to become a RuboCop sponsor.**
163
+ **If you're working in a company that's making significant use of RuboCop we'd
164
+ appreciate it if you suggest to your company to become a RuboCop sponsor.**
162
165
 
163
166
  You can support the development of RuboCop via
164
167
  [GitHub Sponsors](https://github.com/sponsors/bbatsov),
@@ -168,8 +171,11 @@ You can support the development of RuboCop via
168
171
  and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-rubocop?utm_source=rubygems-rubocop&utm_medium=referral&utm_campaign=readme)
169
172
  .
170
173
 
171
- **Note:** If doing a sponsorship in the form of donation is problematic for your company from an accounting standpoint, we'd recommend
172
- the use of Tidelift, where you can get a support-like subscription instead.
174
+ > [!NOTE]
175
+ >
176
+ > If doing a sponsorship in the form of donation is problematic for your company
177
+ > from an accounting standpoint, we'd recommend the use of Tidelift, where you
178
+ > can get a support-like subscription instead.
173
179
 
174
180
  ### Open Collective for Individuals
175
181
 
data/config/default.yml CHANGED
@@ -281,6 +281,13 @@ Gemspec/AddRuntimeDependency:
281
281
  Include:
282
282
  - '**/*.gemspec'
283
283
 
284
+ Gemspec/AttributeAssignment:
285
+ Description: 'Use consistent style for Gemspec attributes assignment.'
286
+ Enabled: pending
287
+ VersionAdded: '1.77'
288
+ Include:
289
+ - '**/*.gemspec'
290
+
284
291
  Gemspec/DependencyVersion:
285
292
  Description: 'Requires or forbids specifying gem dependency versions.'
286
293
  Enabled: false
@@ -1095,6 +1102,7 @@ Layout/LineLength:
1095
1102
  # To make it possible to copy or click on URIs in the code, we allow lines
1096
1103
  # containing a URI to be longer than Max.
1097
1104
  AllowURI: true
1105
+ AllowQualifiedName: true
1098
1106
  URISchemes:
1099
1107
  - http
1100
1108
  - https
@@ -1923,7 +1931,7 @@ Lint/EmptyInterpolation:
1923
1931
  Enabled: true
1924
1932
  AutoCorrect: contextual
1925
1933
  VersionAdded: '0.20'
1926
- VersionChanged: '1.61'
1934
+ VersionChanged: '1.76'
1927
1935
 
1928
1936
  Lint/EmptyWhen:
1929
1937
  Description: 'Checks for `when` branches with empty bodies.'
@@ -2400,6 +2408,7 @@ Lint/SelfAssignment:
2400
2408
  Description: 'Checks for self-assignments.'
2401
2409
  Enabled: true
2402
2410
  VersionAdded: '0.89'
2411
+ AllowRBSInlineAnnotation: false
2403
2412
 
2404
2413
  Lint/SendWithMixinArgument:
2405
2414
  Description: 'Checks for `send` method when using mixin.'
@@ -2423,8 +2432,9 @@ Lint/ShadowingOuterLocalVariable:
2423
2432
  Description: >-
2424
2433
  Do not use the same name as outer local variable
2425
2434
  for block arguments or block local variables.
2426
- Enabled: true
2435
+ Enabled: false
2427
2436
  VersionAdded: '0.9'
2437
+ VersionChanged: '1.76'
2428
2438
 
2429
2439
  Lint/SharedMutableDefault:
2430
2440
  Description: 'Checks for mutable literals used as default arguments during Hash initialization.'
@@ -2612,6 +2622,13 @@ Lint/UselessConstantScoping:
2612
2622
  Enabled: pending
2613
2623
  VersionAdded: '1.72'
2614
2624
 
2625
+ Lint/UselessDefaultValueArgument:
2626
+ Description: 'Checks for usage of `fetch` or `Array.new` with default value argument and block.'
2627
+ Enabled: pending
2628
+ VersionAdded: '1.76'
2629
+ Safe: false
2630
+ AllowedReceivers: []
2631
+
2615
2632
  Lint/UselessDefined:
2616
2633
  Description: 'Checks for calls to `defined?` with strings and symbols. The result of such a call will always be truthy.'
2617
2634
  Enabled: pending
@@ -2636,6 +2653,11 @@ Lint/UselessNumericOperation:
2636
2653
  Enabled: pending
2637
2654
  VersionAdded: '1.66'
2638
2655
 
2656
+ Lint/UselessOr:
2657
+ Description: 'Checks for useless OR expressions.'
2658
+ Enabled: pending
2659
+ VersionAdded: '1.76'
2660
+
2639
2661
  Lint/UselessRescue:
2640
2662
  Description: 'Checks for useless `rescue`s.'
2641
2663
  Enabled: pending
@@ -2676,7 +2698,7 @@ Metrics/AbcSize:
2676
2698
  A calculated magnitude based on number of assignments,
2677
2699
  branches, and conditions.
2678
2700
  References:
2679
- - http://c2.com/cgi/wiki?AbcMetric
2701
+ - https://wiki.c2.com/?AbcMetric
2680
2702
  - https://en.wikipedia.org/wiki/ABC_Software_Metric
2681
2703
  Enabled: true
2682
2704
  VersionAdded: '0.27'
@@ -3046,8 +3068,23 @@ Naming/MethodParameterName:
3046
3068
  # Forbidden names that will register an offense
3047
3069
  ForbiddenNames: []
3048
3070
 
3049
- Naming/PredicateName:
3050
- Description: 'Check the names of predicate methods.'
3071
+ Naming/PredicateMethod:
3072
+ Description: 'Checks that predicate methods end with `?` and non-predicate methods do not.'
3073
+ Enabled: pending
3074
+ VersionAdded: '1.76'
3075
+ VersionChanged: '1.76'
3076
+ # In `aggressive` mode, the cop will register an offense for predicate methods that
3077
+ # may return a non-boolean value.
3078
+ # In `conservative` mode, the cop will *not* register an offense for predicate methods
3079
+ # that may return a non-boolean value.
3080
+ Mode: conservative
3081
+ AllowedMethods:
3082
+ - call
3083
+ AllowedPatterns: []
3084
+ AllowBangMethods: false
3085
+
3086
+ Naming/PredicatePrefix:
3087
+ Description: 'Predicate method names should not be prefixed and end with a `?`.'
3051
3088
  StyleGuide: '#bool-methods-qmark'
3052
3089
  Enabled: true
3053
3090
  VersionAdded: '0.50'
@@ -3632,6 +3669,13 @@ Style/CollectionMethods:
3632
3669
  - inject
3633
3670
  - reduce
3634
3671
 
3672
+ Style/CollectionQuerying:
3673
+ Description: 'Prefer `Enumerable` predicate methods over expressions with `count`.'
3674
+ StyleGuide: '#collection-querying'
3675
+ Enabled: pending
3676
+ VersionAdded: '1.77'
3677
+ Safe: false
3678
+
3635
3679
  Style/ColonMethodCall:
3636
3680
  Description: 'Do not use :: for method call.'
3637
3681
  StyleGuide: '#double-colons'
@@ -3939,6 +3983,16 @@ Style/EmptyMethod:
3939
3983
  - compact
3940
3984
  - expanded
3941
3985
 
3986
+ Style/EmptyStringInsideInterpolation:
3987
+ Description: 'Checks for empty strings being assigned inside string interpolation.'
3988
+ StyleGuide: '#empty-strings-in-interpolation'
3989
+ Enabled: pending
3990
+ EnforcedStyle: trailing_conditional
3991
+ SupportedStyles:
3992
+ - trailing_conditional
3993
+ - ternary
3994
+ VersionAdded: '1.76'
3995
+
3942
3996
  Style/Encoding:
3943
3997
  Description: 'Use UTF-8 as the source file encoding.'
3944
3998
  StyleGuide: '#utf-8'
@@ -4023,6 +4077,9 @@ Style/FetchEnvVar:
4023
4077
  VersionAdded: '1.28'
4024
4078
  # Environment variables to be excluded from the inspection.
4025
4079
  AllowedVars: []
4080
+ # When `true`, autocorrects `ENV["key"]` to `ENV.fetch("key", nil)`.
4081
+ # When `false`, autocorrects `ENV["key"]` to `ENV.fetch("key")`.
4082
+ DefaultToNil: true
4026
4083
 
4027
4084
  Style/FileEmpty:
4028
4085
  Description: >-
@@ -4433,12 +4490,14 @@ Style/ItAssignment:
4433
4490
  Style/ItBlockParameter:
4434
4491
  Description: 'Checks for blocks with one argument where `it` block parameter can be used.'
4435
4492
  Enabled: pending
4436
- EnforcedStyle: only_numbered_parameters
4493
+ EnforcedStyle: allow_single_line
4437
4494
  SupportedStyles:
4495
+ - allow_single_line
4438
4496
  - only_numbered_parameters
4439
4497
  - always
4440
4498
  - disallow
4441
4499
  VersionAdded: '1.75'
4500
+ VersionChanged: '1.76'
4442
4501
 
4443
4502
  Style/KeywordArgumentsMerging:
4444
4503
  Description: >-
@@ -4969,7 +5028,7 @@ Style/OpenStructUse:
4969
5028
  Avoid using OpenStruct. As of Ruby 3.0, use is officially discouraged due to performance,
4970
5029
  version compatibility, and potential security issues.
4971
5030
  References:
4972
- - https://docs.ruby-lang.org/en/3.0.0/OpenStruct.html#class-OpenStruct-label-Caveats
5031
+ - https://docs.ruby-lang.org/en/3.0/OpenStruct.html#class-OpenStruct-label-Caveats
4973
5032
 
4974
5033
  Enabled: pending
4975
5034
  Safe: false
@@ -5151,6 +5210,12 @@ Style/RedundantArrayConstructor:
5151
5210
  Enabled: pending
5152
5211
  VersionAdded: '1.52'
5153
5212
 
5213
+ Style/RedundantArrayFlatten:
5214
+ Description: 'Checks for redundant calls of `Array#flatten`.'
5215
+ Enabled: pending
5216
+ Safe: false
5217
+ VersionAdded: '1.76'
5218
+
5154
5219
  Style/RedundantAssignment:
5155
5220
  Description: 'Checks for redundant assignment before returning.'
5156
5221
  Enabled: true
@@ -31,6 +31,9 @@ renamed:
31
31
  Lint/UnneededRequireStatement: Lint/RedundantRequireStatement
32
32
  Lint/UnneededSplatExpansion: Lint/RedundantSplatExpansion
33
33
  Metrics/LineLength: Layout/LineLength
34
+ Naming/PredicateName:
35
+ new_name: Naming/PredicatePrefix
36
+ severity: warning
34
37
  Naming/UncommunicativeBlockParamName: Naming/BlockParameterName
35
38
  Naming/UncommunicativeMethodParamName: Naming/MethodParameterName
36
39
  Style/AccessorMethodName: Naming/AccessorMethodName
@@ -44,7 +47,7 @@ renamed:
44
47
  Style/MethodName: Naming/MethodName
45
48
  Style/OpMethod: Naming/BinaryOperatorParameterName
46
49
  Style/PredicateName:
47
- new_name: Naming/PredicateName
50
+ new_name: Naming/PredicatePrefix
48
51
  severity: warning
49
52
  Style/SingleSpaceBeforeFirstArg: Layout/SpaceBeforeFirstArg
50
53
  Style/UnneededCapitalW: Style/RedundantCapitalW
@@ -179,10 +182,10 @@ changed_parameters:
179
182
  - cops: Naming/HeredocDelimiterNaming
180
183
  parameters: Blacklist
181
184
  alternative: ForbiddenDelimiters
182
- - cops: Naming/PredicateName
185
+ - cops: Naming/PredicatePrefix
183
186
  parameters: NamePrefixBlacklist
184
187
  alternative: ForbiddenPrefixes
185
- - cops: Naming/PredicateName
188
+ - cops: Naming/PredicatePrefix
186
189
  parameters: NameWhitelist
187
190
  alternative: AllowedMethods
188
191
  - cops:
@@ -50,7 +50,7 @@ module RuboCop
50
50
 
51
51
  def disable_offense(offense_range)
52
52
  unbreakable_range = multiline_ranges(offense_range)&.find do |range|
53
- range_overlaps_offense?(offense_range, range)
53
+ eol_comment_would_be_inside_literal?(offense_range, range)
54
54
  end
55
55
 
56
56
  if unbreakable_range
@@ -75,18 +75,22 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def disable_offense_with_eol_or_surround_comment(range)
78
- eol_comment = " # rubocop:todo #{cop_name}"
79
- needed_line_length = (range.source_line + eol_comment).length
80
-
81
- if needed_line_length <= max_line_length
82
- disable_offense_at_end_of_line(range_of_first_line(range), eol_comment)
83
- else
78
+ if line_with_eol_comment_too_long?(range)
84
79
  disable_offense_before_and_after(range_by_lines(range))
80
+ else
81
+ disable_offense_at_end_of_line(range_of_first_line(range))
85
82
  end
86
83
  end
87
84
 
88
- def range_overlaps_offense?(offense_range, range)
89
- offense_range.begin_pos > range.begin_pos && range.overlaps?(offense_range)
85
+ def eol_comment_would_be_inside_literal?(offense_range, literal_range)
86
+ return true if line_with_eol_comment_too_long?(offense_range)
87
+
88
+ offense_line = offense_range.line
89
+ offense_line >= literal_range.first_line && offense_line < literal_range.last_line
90
+ end
91
+
92
+ def line_with_eol_comment_too_long?(range)
93
+ (range.source_line + eol_comment).length > max_line_length
90
94
  end
91
95
 
92
96
  def surrounding_heredoc?(node)
@@ -132,10 +136,14 @@ module RuboCop
132
136
  config.for_cop('Layout/LineLength')['Max'] || 120
133
137
  end
134
138
 
135
- def disable_offense_at_end_of_line(range, eol_comment)
139
+ def disable_offense_at_end_of_line(range)
136
140
  Corrector.new(range).insert_after(range, eol_comment)
137
141
  end
138
142
 
143
+ def eol_comment
144
+ " # rubocop:todo #{cop_name}"
145
+ end
146
+
139
147
  def disable_offense_before_and_after(range_by_lines)
140
148
  range_with_newline = range_by_lines.resize(range_by_lines.size + 1)
141
149
  leading_whitespace = range_by_lines.source_line[/^\s*/]
@@ -45,7 +45,7 @@ module RuboCop
45
45
 
46
46
  gem_declarations(processed_source.ast)
47
47
  .each_cons(2) do |previous, current|
48
- next unless consecutive_lines(previous, current)
48
+ next unless consecutive_lines?(previous, current)
49
49
  next unless case_insensitive_out_of_order?(gem_name(current), gem_name(previous))
50
50
 
51
51
  register_offense(previous, current)
@@ -10,8 +10,11 @@ module RuboCop
10
10
  COMMA_REGEXP = /(?<=\))\s*,/.freeze
11
11
 
12
12
  def correct(corrector, node)
13
- corrector.remove(node.loc.begin)
14
- corrector.remove(node.loc.end)
13
+ buffer = node.source_range.source_buffer
14
+ corrector.remove(range_with_surrounding_space(range: node.loc.begin, buffer: buffer,
15
+ side: :right, whitespace: true))
16
+ corrector.remove(range_with_surrounding_space(range: node.loc.end, buffer: buffer,
17
+ side: :left))
15
18
  handle_orphaned_comma(corrector, node)
16
19
 
17
20
  return unless ternary_condition?(node) && next_char_is_question_mark?(node)
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Gemspec
6
+ # Use consistent style for Gemspec attributes assignment.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # # This example uses two styles for assignment of metadata attribute.
12
+ # Gem::Specification.new do |spec|
13
+ # spec.metadata = { 'key' => 'value' }
14
+ # spec.metadata['another-key'] = 'another-value'
15
+ # end
16
+ #
17
+ # # good
18
+ # Gem::Specification.new do |spec|
19
+ # spec.metadata['key'] = 'value'
20
+ # spec.metadata['another-key'] = 'another-value'
21
+ # end
22
+ #
23
+ # # good
24
+ # Gem::Specification.new do |spec|
25
+ # spec.metadata = { 'key' => 'value', 'another-key' => 'another-value' }
26
+ # end
27
+ #
28
+ # # bad
29
+ # # This example uses two styles for assignment of authors attribute.
30
+ # Gem::Specification.new do |spec|
31
+ # spec.authors = %w[author-0 author-1]
32
+ # spec.authors[2] = 'author-2'
33
+ # end
34
+ #
35
+ # # good
36
+ # Gem::Specification.new do |spec|
37
+ # spec.authors = %w[author-0 author-1 author-2]
38
+ # end
39
+ #
40
+ # # good
41
+ # Gem::Specification.new do |spec|
42
+ # spec.authors[0] = 'author-0'
43
+ # spec.authors[1] = 'author-1'
44
+ # spec.authors[2] = 'author-2'
45
+ # end
46
+ #
47
+ # # good
48
+ # # This example uses consistent assignment per attribute,
49
+ # # even though two different styles are used overall.
50
+ # Gem::Specification.new do |spec|
51
+ # spec.metadata = { 'key' => 'value' }
52
+ # spec.authors[0] = 'author-0'
53
+ # spec.authors[1] = 'author-1'
54
+ # spec.authors[2] = 'author-2'
55
+ # end
56
+ #
57
+ class AttributeAssignment < Base
58
+ include GemspecHelp
59
+
60
+ MSG = 'Use consistent style for Gemspec attributes assignment.'
61
+
62
+ def on_new_investigation
63
+ return if processed_source.blank?
64
+
65
+ assignments = source_assignments(processed_source.ast)
66
+ indexed_assignments = source_indexed_assignments(processed_source.ast)
67
+
68
+ assignments.keys.intersection(indexed_assignments.keys).each do |attribute|
69
+ indexed_assignments[attribute].each do |node|
70
+ add_offense(node)
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def source_assignments(ast)
78
+ assignment_method_declarations(ast)
79
+ .select(&:assignment_method?)
80
+ .group_by(&:method_name)
81
+ .transform_keys { |method_name| method_name.to_s.delete_suffix('=').to_sym }
82
+ end
83
+
84
+ def source_indexed_assignments(ast)
85
+ indexed_assignment_method_declarations(ast)
86
+ .group_by { |node| node.children.first.method_name }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -54,22 +54,6 @@ module RuboCop
54
54
  MSG = '`%<assignment>s` method calls already given on line ' \
55
55
  '%<line_of_first_occurrence>d of the gemspec.'
56
56
 
57
- # @!method assignment_method_declarations(node)
58
- def_node_search :assignment_method_declarations, <<~PATTERN
59
- (send
60
- (lvar #match_block_variable_name?) _ ...)
61
- PATTERN
62
-
63
- # @!method indexed_assignment_method_declarations(node)
64
- def_node_search :indexed_assignment_method_declarations, <<~PATTERN
65
- (send
66
- (send (lvar #match_block_variable_name?) _)
67
- :[]=
68
- literal?
69
- _
70
- )
71
- PATTERN
72
-
73
57
  def on_new_investigation
74
58
  return if processed_source.blank?
75
59
 
@@ -96,12 +80,6 @@ module RuboCop
96
80
  end
97
81
  end
98
82
 
99
- def match_block_variable_name?(receiver_name)
100
- gem_specification(processed_source.ast) do |block_variable_name|
101
- return block_variable_name == receiver_name
102
- end
103
- end
104
-
105
83
  def duplicated_assignment_method_nodes
106
84
  assignment_method_declarations(processed_source.ast)
107
85
  .select(&:assignment_method?)
@@ -71,7 +71,7 @@ module RuboCop
71
71
 
72
72
  dependency_declarations(processed_source.ast)
73
73
  .each_cons(2) do |previous, current|
74
- next unless consecutive_lines(previous, current)
74
+ next unless consecutive_lines?(previous, current)
75
75
  next unless case_insensitive_out_of_order?(gem_name(current), gem_name(previous))
76
76
  next unless get_dependency_name(previous) == get_dependency_name(current)
77
77
 
@@ -74,6 +74,14 @@ module RuboCop
74
74
  }
75
75
  PATTERN
76
76
 
77
+ # @!method metadata_assignment(node)
78
+ def_node_search :metadata_assignment, <<~PATTERN
79
+ `{
80
+ (send _ :metadata= _)
81
+ (send (send _ :metadata) :[]= (str _) _)
82
+ }
83
+ PATTERN
84
+
77
85
  # @!method rubygems_mfa_required(node)
78
86
  def_node_search :rubygems_mfa_required, <<~PATTERN
79
87
  (pair (str "rubygems_mfa_required") $_)
@@ -131,9 +139,15 @@ module RuboCop
131
139
  end
132
140
 
133
141
  def insert_mfa_required(corrector, node, block_var)
134
- corrector.insert_before(node.loc.end, <<~RUBY)
142
+ require_mfa_directive = <<~RUBY.strip
135
143
  #{block_var}.metadata['rubygems_mfa_required'] = 'true'
136
144
  RUBY
145
+
146
+ if (last_assignment = metadata_assignment(processed_source.ast).to_a.last)
147
+ corrector.insert_after(last_assignment, "\n#{require_mfa_directive}")
148
+ else
149
+ corrector.insert_before(node.loc.end, "#{require_mfa_directive}\n")
150
+ end
137
151
  end
138
152
 
139
153
  def change_value(corrector, value)
@@ -110,8 +110,8 @@ module RuboCop
110
110
  def directive_offense_type(directive, actual_name)
111
111
  return :missing_directive unless directive
112
112
 
113
- return :wrong_scope if wrong_scope(directive, actual_name)
114
- return :no_scope if no_scope(directive, actual_name)
113
+ return :wrong_scope if wrong_scope?(directive, actual_name)
114
+ return :no_scope if no_scope?(directive, actual_name)
115
115
 
116
116
  # The method directive being prefixed by 'self.' is always an offense.
117
117
  # The matched method_name does not contain the receiver but the
@@ -121,11 +121,11 @@ module RuboCop
121
121
  end
122
122
  end
123
123
 
124
- def wrong_scope(directive, actual_name)
124
+ def wrong_scope?(directive, actual_name)
125
125
  !actual_name.start_with?('self.') && directive[:has_scope_directive]
126
126
  end
127
127
 
128
- def no_scope(directive, actual_name)
128
+ def no_scope?(directive, actual_name)
129
129
  actual_name.start_with?('self.') && !directive[:has_scope_directive]
130
130
  end
131
131
 
@@ -29,6 +29,7 @@ module RuboCop
29
29
  NODE_GROUPS = {
30
30
  any_block: %i[block numblock itblock],
31
31
  any_def: %i[def defs],
32
+ any_match_pattern: %i[match_pattern match_pattern_p],
32
33
  argument: %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg forward_arg shadowarg],
33
34
  boolean: %i[true false],
34
35
  call: %i[send csend],
@@ -22,6 +22,11 @@ module RuboCop
22
22
  # * Private attribute macros (`attr_accessor`, `attr_writer`, `attr_reader`)
23
23
  # * Private instance methods
24
24
  #
25
+ # NOTE: Simply enabling the cop with `Enabled: true` will not use
26
+ # the example order shown below.
27
+ # To enforce the order of macros like `attr_reader`,
28
+ # you must define both `ExpectedOrder` *and* `Categories`.
29
+ #
25
30
  # You can configure the following order:
26
31
  #
27
32
  # [source,yaml]
@@ -68,6 +73,36 @@ module RuboCop
68
73
  # - extend
69
74
  # ----
70
75
  #
76
+ # If you only set `ExpectedOrder`
77
+ # without defining `Categories`,
78
+ # macros such as `attr_reader` or `has_many`
79
+ # will not be recognized as part of a category, and their order will not be validated.
80
+ # For example, the following will NOT raise any offenses, even if the order is incorrect:
81
+ #
82
+ # [source,yaml]
83
+ # ----
84
+ # Layout/ClassStructure:
85
+ # Enabled: true
86
+ # ExpectedOrder:
87
+ # - public_attribute_macros
88
+ # - initializer
89
+ # ----
90
+ #
91
+ # To make it work as expected, you must also specify `Categories` like this:
92
+ #
93
+ # [source,yaml]
94
+ # ----
95
+ # Layout/ClassStructure:
96
+ # ExpectedOrder:
97
+ # - public_attribute_macros
98
+ # - initializer
99
+ # Categories:
100
+ # attribute_macros:
101
+ # - attr_reader
102
+ # - attr_writer
103
+ # - attr_accessor
104
+ # ----
105
+ #
71
106
  # @safety
72
107
  # Autocorrection is unsafe because class methods and module inclusion
73
108
  # can behave differently, based on which methods or constants have
@@ -161,7 +161,7 @@ module RuboCop
161
161
  elements.flat_map do |e|
162
162
  e.loc.column
163
163
  end
164
- end.uniq.count == 1
164
+ end.uniq.one?
165
165
  end
166
166
 
167
167
  def first_argument_line(elements)