rubocop 1.75.6 → 1.76.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -13
  3. data/config/default.yml +48 -5
  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/gemspec/duplicated_assignment.rb +50 -6
  8. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  9. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  10. data/lib/rubocop/cop/layout/class_structure.rb +35 -0
  11. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +6 -2
  12. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/space_before_brackets.rb +6 -32
  14. data/lib/rubocop/cop/lint/duplicate_methods.rb +41 -1
  15. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  16. data/lib/rubocop/cop/lint/float_comparison.rb +27 -0
  17. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  18. data/lib/rubocop/cop/lint/literal_as_condition.rb +16 -24
  19. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  20. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +87 -0
  21. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  22. data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
  23. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  24. data/lib/rubocop/cop/naming/predicate_method.rb +216 -0
  25. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +2 -2
  26. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -10
  27. data/lib/rubocop/cop/style/command_literal.rb +1 -1
  28. data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
  29. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  30. data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -4
  31. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -7
  32. data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
  33. data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
  34. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  35. data/lib/rubocop/cop/style/redundant_array_flatten.rb +48 -0
  36. data/lib/rubocop/cop/style/redundant_format.rb +6 -1
  37. data/lib/rubocop/cop/style/redundant_parentheses.rb +16 -5
  38. data/lib/rubocop/cop/style/regexp_literal.rb +1 -1
  39. data/lib/rubocop/cop/style/safe_navigation.rb +10 -7
  40. data/lib/rubocop/cop/team.rb +1 -1
  41. data/lib/rubocop/cop/variable_force/assignment.rb +7 -3
  42. data/lib/rubocop/version.rb +1 -1
  43. data/lib/rubocop.rb +6 -1
  44. metadata +12 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4692afe0e994fae9d158ac440d48f460623d28bbad07fc7ccf141aa3737e82a1
4
- data.tar.gz: 3dba40c9526a877cdc7269d54b3c89e3d64f8147cb5e2b885146c1d02d94bc19
3
+ metadata.gz: 236bdf0fa5879274099388142e17b886fc852cdc1e762d50c74a31254c7c6136
4
+ data.tar.gz: 03011b196c686aa5ce1cc617d56c19041dbdc76ba237626316048be9e75dc62c
5
5
  SHA512:
6
- metadata.gz: acd7a48eac01d73747fb22cef86c75c4b2da8a910f341ddda19572eabd9126e8a42edac565df8ce0e6946e69f44d892e08c358dd9c6d72455974c05277731e3e
7
- data.tar.gz: 2afef8e39089e476050a24aa63e4f637a4765759b31c74672740fb86067dada674140574c61a0d426950ea57e36c7b5bdc00db5cbab14e0000fa7aa065e83c6c
6
+ metadata.gz: 91eb23282705c816206704923efdea2cb45ad09677a5d72a8741a565d9ed96146ad688aa54c1dc5be7f6d83c6e4d099605bdd91cb941d0d2b1fb36ace05b02a8
7
+ data.tar.gz: 6ff783d52e62e4dda5cae1c78f8648ef8af4078c5f052134a23c9229018e33d29a21d3ecaa80bfdc74d382b73a1a666f67d9e1b3436b1dfb5745a96861cf92ec
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.76', 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
@@ -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
@@ -1923,7 +1923,7 @@ Lint/EmptyInterpolation:
1923
1923
  Enabled: true
1924
1924
  AutoCorrect: contextual
1925
1925
  VersionAdded: '0.20'
1926
- VersionChanged: '1.61'
1926
+ VersionChanged: '1.76'
1927
1927
 
1928
1928
  Lint/EmptyWhen:
1929
1929
  Description: 'Checks for `when` branches with empty bodies.'
@@ -2423,8 +2423,9 @@ Lint/ShadowingOuterLocalVariable:
2423
2423
  Description: >-
2424
2424
  Do not use the same name as outer local variable
2425
2425
  for block arguments or block local variables.
2426
- Enabled: true
2426
+ Enabled: false
2427
2427
  VersionAdded: '0.9'
2428
+ VersionChanged: '1.76'
2428
2429
 
2429
2430
  Lint/SharedMutableDefault:
2430
2431
  Description: 'Checks for mutable literals used as default arguments during Hash initialization.'
@@ -2612,6 +2613,13 @@ Lint/UselessConstantScoping:
2612
2613
  Enabled: pending
2613
2614
  VersionAdded: '1.72'
2614
2615
 
2616
+ Lint/UselessDefaultValueArgument:
2617
+ Description: 'Checks for usage of `fetch` or `Array.new` with default value argument and block.'
2618
+ Enabled: pending
2619
+ VersionAdded: '1.76'
2620
+ Safe: false
2621
+ AllowedReceivers: []
2622
+
2615
2623
  Lint/UselessDefined:
2616
2624
  Description: 'Checks for calls to `defined?` with strings and symbols. The result of such a call will always be truthy.'
2617
2625
  Enabled: pending
@@ -2636,6 +2644,11 @@ Lint/UselessNumericOperation:
2636
2644
  Enabled: pending
2637
2645
  VersionAdded: '1.66'
2638
2646
 
2647
+ Lint/UselessOr:
2648
+ Description: 'Checks for useless OR expressions.'
2649
+ Enabled: pending
2650
+ VersionAdded: '1.76'
2651
+
2639
2652
  Lint/UselessRescue:
2640
2653
  Description: 'Checks for useless `rescue`s.'
2641
2654
  Enabled: pending
@@ -3046,8 +3059,20 @@ Naming/MethodParameterName:
3046
3059
  # Forbidden names that will register an offense
3047
3060
  ForbiddenNames: []
3048
3061
 
3049
- Naming/PredicateName:
3050
- Description: 'Check the names of predicate methods.'
3062
+ Naming/PredicateMethod:
3063
+ Description: 'Checks that predicate methods end with `?` and non-predicate methods do not.'
3064
+ Enabled: pending
3065
+ VersionAdded: '1.76'
3066
+ # In `aggressive` mode, the cop will register an offense for predicate methods that
3067
+ # may return a non-boolean value.
3068
+ # In `conservative` mode, the cop will *not* register an offense for predicate methods
3069
+ # that may return a non-boolean value.
3070
+ Mode: conservative
3071
+ AllowedMethods:
3072
+ - call
3073
+
3074
+ Naming/PredicatePrefix:
3075
+ Description: 'Predicate method names should not be prefixed and end with a `?`.'
3051
3076
  StyleGuide: '#bool-methods-qmark'
3052
3077
  Enabled: true
3053
3078
  VersionAdded: '0.50'
@@ -3939,6 +3964,16 @@ Style/EmptyMethod:
3939
3964
  - compact
3940
3965
  - expanded
3941
3966
 
3967
+ Style/EmptyStringInsideInterpolation:
3968
+ Description: 'Checks for empty strings being assigned inside string interpolation.'
3969
+ StyleGuide: '#empty-strings-in-interpolation'
3970
+ Enabled: pending
3971
+ EnforcedStyle: trailing_conditional
3972
+ SupportedStyles:
3973
+ - trailing_conditional
3974
+ - ternary
3975
+ VersionAdded: '1.76'
3976
+
3942
3977
  Style/Encoding:
3943
3978
  Description: 'Use UTF-8 as the source file encoding.'
3944
3979
  StyleGuide: '#utf-8'
@@ -4433,12 +4468,14 @@ Style/ItAssignment:
4433
4468
  Style/ItBlockParameter:
4434
4469
  Description: 'Checks for blocks with one argument where `it` block parameter can be used.'
4435
4470
  Enabled: pending
4436
- EnforcedStyle: only_numbered_parameters
4471
+ EnforcedStyle: allow_single_line
4437
4472
  SupportedStyles:
4473
+ - allow_single_line
4438
4474
  - only_numbered_parameters
4439
4475
  - always
4440
4476
  - disallow
4441
4477
  VersionAdded: '1.75'
4478
+ VersionChanged: '1.76'
4442
4479
 
4443
4480
  Style/KeywordArgumentsMerging:
4444
4481
  Description: >-
@@ -5151,6 +5188,12 @@ Style/RedundantArrayConstructor:
5151
5188
  Enabled: pending
5152
5189
  VersionAdded: '1.52'
5153
5190
 
5191
+ Style/RedundantArrayFlatten:
5192
+ Description: 'Checks for redundant calls of `Array#flatten`.'
5193
+ Enabled: pending
5194
+ Safe: false
5195
+ VersionAdded: '1.76'
5196
+
5154
5197
  Style/RedundantAssignment:
5155
5198
  Description: 'Checks for redundant assignment before returning.'
5156
5199
  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)
@@ -6,10 +6,11 @@ module RuboCop
6
6
  # An attribute assignment method calls should be listed only once
7
7
  # in a gemspec.
8
8
  #
9
- # Assigning to an attribute with the same name using `spec.foo =` will be
10
- # an unintended usage. On the other hand, duplication of methods such
11
- # as `spec.requirements`, `spec.add_runtime_dependency`, and others are
12
- # permitted because it is the intended use of appending values.
9
+ # Assigning to an attribute with the same name using `spec.foo =` or
10
+ # `spec.attribute#[]=` will be an unintended usage. On the other hand,
11
+ # duplication of methods such # as `spec.requirements`,
12
+ # `spec.add_runtime_dependency`, and others are permitted because it is
13
+ # the intended use of appending values.
13
14
  #
14
15
  # @example
15
16
  # # bad
@@ -34,6 +35,18 @@ module RuboCop
34
35
  # spec.add_dependency('parallel', '~> 1.10')
35
36
  # spec.add_dependency('parser', '>= 2.3.3.1', '< 3.0')
36
37
  # end
38
+ #
39
+ # # bad
40
+ # Gem::Specification.new do |spec|
41
+ # spec.metadata["key"] = "value"
42
+ # spec.metadata["key"] = "value"
43
+ # end
44
+ #
45
+ # # good
46
+ # Gem::Specification.new do |spec|
47
+ # spec.metadata["key"] = "value"
48
+ # end
49
+ #
37
50
  class DuplicatedAssignment < Base
38
51
  include RangeHelp
39
52
  include GemspecHelp
@@ -44,12 +57,29 @@ module RuboCop
44
57
  # @!method assignment_method_declarations(node)
45
58
  def_node_search :assignment_method_declarations, <<~PATTERN
46
59
  (send
47
- (lvar #match_block_variable_name?) _ ...)
60
+ (lvar {#match_block_variable_name? :_1 :it}) _ ...)
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? :_1 :it}) _)
67
+ :[]=
68
+ literal?
69
+ _
70
+ )
48
71
  PATTERN
49
72
 
50
73
  def on_new_investigation
51
74
  return if processed_source.blank?
52
75
 
76
+ process_assignment_method_nodes
77
+ process_indexed_assignment_method_nodes
78
+ end
79
+
80
+ private
81
+
82
+ def process_assignment_method_nodes
53
83
  duplicated_assignment_method_nodes.each do |nodes|
54
84
  nodes[1..].each do |node|
55
85
  register_offense(node, node.method_name, nodes.first.first_line)
@@ -57,7 +87,14 @@ module RuboCop
57
87
  end
58
88
  end
59
89
 
60
- private
90
+ def process_indexed_assignment_method_nodes
91
+ duplicated_indexed_assignment_method_nodes.each do |nodes|
92
+ nodes[1..].each do |node|
93
+ assignment = "#{node.children.first.method_name}[#{node.first_argument.source}]="
94
+ register_offense(node, assignment, nodes.first.first_line)
95
+ end
96
+ end
97
+ end
61
98
 
62
99
  def match_block_variable_name?(receiver_name)
63
100
  gem_specification(processed_source.ast) do |block_variable_name|
@@ -73,6 +110,13 @@ module RuboCop
73
110
  .select { |nodes| nodes.size > 1 }
74
111
  end
75
112
 
113
+ def duplicated_indexed_assignment_method_nodes
114
+ indexed_assignment_method_declarations(processed_source.ast)
115
+ .group_by { |node| [node.children.first.method_name, node.first_argument] }
116
+ .values
117
+ .select { |nodes| nodes.size > 1 }
118
+ end
119
+
76
120
  def register_offense(node, assignment, line_of_first_occurrence)
77
121
  line_range = node.loc.column...node.loc.last_column
78
122
  offense_location = source_range(processed_source.buffer, node.first_line, line_range)
@@ -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
 
@@ -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
@@ -116,7 +116,7 @@ module RuboCop
116
116
  def allowed_only_before_style?(node)
117
117
  if node.special_modifier?
118
118
  return true if processed_source[node.last_line] == 'end'
119
- return false if next_line_empty?(node.last_line)
119
+ return false if next_line_empty_and_exists?(node.last_line)
120
120
  end
121
121
 
122
122
  previous_line_empty?(node.first_line)
@@ -129,7 +129,7 @@ module RuboCop
129
129
  when :around
130
130
  corrector.insert_after(line, "\n") unless next_line_empty?(node.last_line)
131
131
  when :only_before
132
- if next_line_empty?(node.last_line)
132
+ if next_line_empty_and_exists?(node.last_line)
133
133
  range = next_empty_line_range(node)
134
134
 
135
135
  corrector.remove(range)
@@ -154,6 +154,10 @@ module RuboCop
154
154
  body_end?(last_send_line) || next_line.blank?
155
155
  end
156
156
 
157
+ def next_line_empty_and_exists?(last_send_line)
158
+ next_line_empty?(last_send_line) && last_send_line.next != processed_source.lines.size
159
+ end
160
+
157
161
  def empty_lines_around?(node)
158
162
  previous_line_empty?(node.first_line) && next_line_empty?(node.last_line)
159
163
  end
@@ -155,7 +155,7 @@ module RuboCop
155
155
  def on_send(node)
156
156
  return unless should_check?(node)
157
157
  return if same_line?(node, node.first_argument)
158
- return if style != :consistent && enforce_first_argument_with_fixed_indentation? &&
158
+ return if enforce_first_argument_with_fixed_indentation? &&
159
159
  !enable_layout_first_method_argument_line_break?
160
160
 
161
161
  indent = base_indentation(node) + configured_indentation_width
@@ -22,51 +22,25 @@ module RuboCop
22
22
  RESTRICT_ON_SEND = %i[[] []=].freeze
23
23
 
24
24
  def on_send(node)
25
- return unless (first_argument = node.first_argument)
26
-
27
- begin_pos = first_argument.source_range.begin_pos
28
- return unless (range = offense_range(node, begin_pos))
29
-
30
- register_offense(range)
31
- end
32
-
33
- private
34
-
35
- def offense_range(node, begin_pos)
36
25
  receiver_end_pos = node.receiver.source_range.end_pos
37
26
  selector_begin_pos = node.loc.selector.begin_pos
38
27
  return if receiver_end_pos >= selector_begin_pos
39
28
  return if dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
40
29
 
41
- if reference_variable_with_brackets?(node)
42
- range_between(receiver_end_pos, selector_begin_pos)
43
- elsif node.method?(:[]=)
44
- offense_range_for_assignment(node, begin_pos)
30
+ range = range_between(receiver_end_pos, selector_begin_pos)
31
+
32
+ add_offense(range) do |corrector|
33
+ corrector.remove(range)
45
34
  end
46
35
  end
47
36
 
37
+ private
38
+
48
39
  def dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
49
40
  return false unless node.loc.respond_to?(:dot) && (dot = node.loc.dot)
50
41
 
51
42
  dot.begin_pos == receiver_end_pos && dot.end_pos == selector_begin_pos
52
43
  end
53
-
54
- def offense_range_for_assignment(node, begin_pos)
55
- end_pos = node.receiver.source_range.end_pos
56
-
57
- return if begin_pos - end_pos == 1 ||
58
- (range = range_between(end_pos, begin_pos - 1)).source.start_with?('[')
59
-
60
- range
61
- end
62
-
63
- def register_offense(range)
64
- add_offense(range) { |corrector| corrector.remove(range) }
65
- end
66
-
67
- def reference_variable_with_brackets?(node)
68
- node.receiver&.variable? && node.method?(:[]) && node.arguments.size == 1
69
- end
70
44
  end
71
45
  end
72
46
  end
@@ -64,6 +64,23 @@ module RuboCop
64
64
  # end
65
65
  #
66
66
  # delegate :baz, to: :bar
67
+ #
68
+ # # good - delegate with splat arguments is ignored
69
+ # def foo
70
+ # 1
71
+ # end
72
+ #
73
+ # delegate :foo, **options
74
+ #
75
+ # # good - delegate inside a condition is ignored
76
+ # def foo
77
+ # 1
78
+ # end
79
+ #
80
+ # if cond
81
+ # delegate :foo, to: :bar
82
+ # end
83
+ #
67
84
  class DuplicateMethods < Base
68
85
  MSG = 'Method `%<method>s` is defined at both %<defined>s and %<current>s.'
69
86
  RESTRICT_ON_SEND = %i[alias_method attr_reader attr_writer attr_accessor attr
@@ -113,7 +130,10 @@ module RuboCop
113
130
 
114
131
  # @!method delegate_method?(node)
115
132
  def_node_matcher :delegate_method?, <<~PATTERN
116
- (send nil? :delegate (sym $_)+ (hash _))
133
+ (send nil? :delegate
134
+ ({sym str} $_)+
135
+ (hash <(pair (sym :to) {sym str}) ...>)
136
+ )
117
137
  PATTERN
118
138
 
119
139
  # @!method sym_name(node)
@@ -155,11 +175,31 @@ module RuboCop
155
175
  end
156
176
 
157
177
  def on_delegate(node, method_names)
178
+ name_prefix = delegate_prefix(node)
179
+
158
180
  method_names.each do |name|
181
+ name = "#{name_prefix}_#{name}" if name_prefix
182
+
159
183
  found_instance_method(node, name)
160
184
  end
161
185
  end
162
186
 
187
+ def delegate_prefix(node)
188
+ kwargs_node = node.last_argument
189
+
190
+ return unless (prefix = hash_value(kwargs_node, :prefix))
191
+
192
+ if prefix.true_type?
193
+ hash_value(kwargs_node, :to).value
194
+ elsif prefix.type?(:sym, :str)
195
+ prefix.value
196
+ end
197
+ end
198
+
199
+ def hash_value(node, key)
200
+ node.pairs.find { |pair| pair.key.value == key }&.value
201
+ end
202
+
163
203
  def found_instance_method(node, name)
164
204
  return found_sclass_method(node, name) unless (scope = node.parent_module_name)
165
205
 
@@ -19,7 +19,9 @@ module RuboCop
19
19
  MSG = 'Empty interpolation detected.'
20
20
 
21
21
  def on_interpolation(begin_node)
22
- return unless begin_node.children.empty?
22
+ node_children = begin_node.children.dup
23
+ node_children.delete_if { |e| e&.nil_type? || (e&.basic_literal? && e&.value&.empty?) }
24
+ return unless node_children.empty?
23
25
 
24
26
  add_offense(begin_node) { |corrector| corrector.remove(begin_node) }
25
27
  end