rubocop 1.56.3 → 1.57.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +12 -0
  4. data/lib/rubocop/cli/command/auto_generate_config.rb +10 -5
  5. data/lib/rubocop/cli.rb +1 -1
  6. data/lib/rubocop/cop/autocorrect_logic.rb +3 -1
  7. data/lib/rubocop/cop/internal_affairs/example_description.rb +42 -22
  8. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +2 -0
  9. data/lib/rubocop/cop/layout/dot_position.rb +1 -5
  10. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +8 -0
  11. data/lib/rubocop/cop/layout/end_alignment.rb +7 -1
  12. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  13. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  14. data/lib/rubocop/cop/layout/space_inside_parens.rb +1 -1
  15. data/lib/rubocop/cop/lint/debugger.rb +10 -1
  16. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  17. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  18. data/lib/rubocop/cop/lint/mixed_case_range.rb +1 -1
  19. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +0 -1
  20. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -0
  21. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +20 -4
  22. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +11 -4
  23. data/lib/rubocop/cop/lint/void.rb +29 -11
  24. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  25. data/lib/rubocop/cop/metrics/class_length.rb +8 -3
  26. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +2 -2
  27. data/lib/rubocop/cop/mixin/comments_help.rb +16 -12
  28. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +14 -11
  29. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -2
  30. data/lib/rubocop/cop/style/class_equality_comparison.rb +5 -0
  31. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  32. data/lib/rubocop/cop/style/format_string.rb +24 -3
  33. data/lib/rubocop/cop/style/guard_clause.rb +26 -0
  34. data/lib/rubocop/cop/style/identical_conditional_branches.rb +25 -3
  35. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  36. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -11
  37. data/lib/rubocop/cop/style/operator_method_call.rb +6 -0
  38. data/lib/rubocop/cop/style/redundant_begin.rb +9 -1
  39. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -9
  40. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +93 -5
  41. data/lib/rubocop/cop/style/redundant_exception.rb +32 -12
  42. data/lib/rubocop/cop/style/redundant_filter_chain.rb +22 -5
  43. data/lib/rubocop/cop/style/redundant_parentheses.rb +38 -14
  44. data/lib/rubocop/cop/style/return_nil.rb +6 -2
  45. data/lib/rubocop/cop/style/single_argument_dig.rb +2 -1
  46. data/lib/rubocop/cop/style/single_line_do_end_block.rb +67 -0
  47. data/lib/rubocop/formatter/html_formatter.rb +4 -2
  48. data/lib/rubocop/magic_comment.rb +12 -10
  49. data/lib/rubocop/server/cache.rb +1 -0
  50. data/lib/rubocop/version.rb +1 -1
  51. data/lib/rubocop.rb +1 -0
  52. metadata +6 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19e42173e0ae7c2aec912c3dbd560db4189c426d6ba0f051694c6c5c11a913e1
4
- data.tar.gz: c1511a9611875e3d38869837d31840687e6c2004a6e7c27c19630a04b7f064fd
3
+ metadata.gz: adf1db90b03283f492e0e10f234d54d6efc215a8b182965087313e3954d533dd
4
+ data.tar.gz: fa84dfd9d61e4834df9287e0ba0619622f4b7eb9e229e8de1172bc1742c04df5
5
5
  SHA512:
6
- metadata.gz: 97f5377116914b0f45233a7ed193ae08c2a4e12f0bdd0739f3129b4a36fd13eee1a38a6f4486e5de602b00486c37fe271db2bcfd1fbe4289430ea61644edaef6
7
- data.tar.gz: d194d95290db68e6431b49d0dbad8559c657704fa283643057470d584f316c512f5d95681f07fda3f4f57f157b86add28fa135a60452944a0a6c6f8758659b13
6
+ metadata.gz: 44a683eed5e4b9d26caf4d0c775a6f4ba00960b6aaeee29c3f610320d1471326d650e8fc41ee2725af07b4e6a50bf17d442232134522709ebb2a46ec5b80bf40
7
+ data.tar.gz: d4f5dda0dbf7689c55b39de3922c2bd1a0c40aa62ac1046f06c98a19e7f2719cfea11babd480ba4e9b9ff2203d20422a51f096165d5919bf564e009c3a91c087
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.56', require: false
56
+ gem 'rubocop', '~> 1.57', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -2170,7 +2170,9 @@ Lint/RedundantRegexpQuantifiers:
2170
2170
  Lint/RedundantRequireStatement:
2171
2171
  Description: 'Checks for unnecessary `require` statement.'
2172
2172
  Enabled: true
2173
+ SafeAutoCorrect: false
2173
2174
  VersionAdded: '0.76'
2175
+ VersionChanged: '1.57'
2174
2176
 
2175
2177
  Lint/RedundantSafeNavigation:
2176
2178
  Description: 'Checks for redundant safe navigation calls.'
@@ -3354,7 +3356,9 @@ Style/ClassEqualityComparison:
3354
3356
  Description: 'Enforces the use of `Object#instance_of?` instead of class comparison for equality.'
3355
3357
  StyleGuide: '#instance-of-vs-class-comparison'
3356
3358
  Enabled: true
3359
+ SafeAutoCorrect: false
3357
3360
  VersionAdded: '0.93'
3361
+ VersionChanged: '1.57'
3358
3362
  AllowedMethods:
3359
3363
  - ==
3360
3364
  - equal?
@@ -3409,6 +3413,7 @@ Style/CollectionMethods:
3409
3413
  PreferredMethods:
3410
3414
  collect: 'map'
3411
3415
  collect!: 'map!'
3416
+ collect_concat: 'flat_map'
3412
3417
  inject: 'reduce'
3413
3418
  detect: 'find'
3414
3419
  find_all: 'select'
@@ -4919,7 +4924,9 @@ Style/RedundantFilterChain:
4919
4924
  Identifies usages of `any?`, `empty?`, `none?` or `one?` predicate methods chained to
4920
4925
  `select`/`filter`/`find_all` and change them to use predicate method instead.
4921
4926
  Enabled: pending
4927
+ SafeAutoCorrect: false
4922
4928
  VersionAdded: '1.52'
4929
+ VersionChanged: '1.57'
4923
4930
 
4924
4931
  Style/RedundantFreeze:
4925
4932
  Description: "Checks usages of Object#freeze on immutable objects."
@@ -5181,6 +5188,11 @@ Style/SingleLineBlockParams:
5181
5188
  - acc
5182
5189
  - elem
5183
5190
 
5191
+ Style/SingleLineDoEndBlock:
5192
+ Description: 'Checks for single-line `do`...`end` blocks.'
5193
+ Enabled: pending
5194
+ VersionAdded: '1.57'
5195
+
5184
5196
  Style/SingleLineMethods:
5185
5197
  Description: 'Avoid single-line methods.'
5186
5198
  StyleGuide: '#no-single-line-methods'
@@ -10,6 +10,7 @@ module RuboCop
10
10
 
11
11
  AUTO_GENERATED_FILE = '.rubocop_todo.yml'
12
12
  YAML_OPTIONAL_DOC_START = /\A---(\s+#|\s*\z)/.freeze
13
+ PLACEHOLDER = '###rubocop:inherit_here'
13
14
 
14
15
  PHASE_1 = 'Phase 1 of 2: run Layout/LineLength cop'
15
16
  PHASE_2 = 'Phase 2 of 2: run all cops'
@@ -125,15 +126,19 @@ module RuboCop
125
126
 
126
127
  def existing_configuration(config_file)
127
128
  File.read(config_file, encoding: Encoding::UTF_8)
128
- .sub(/^inherit_from: *[^\n]+/, '')
129
- .sub(/^inherit_from: *(\n *- *[^\n]+)+/, '')
129
+ .sub(/^inherit_from: *[^\n]+/, PLACEHOLDER)
130
+ .sub(/^inherit_from: *(\n *- *[^\n]+)+/, PLACEHOLDER)
130
131
  end
131
132
 
132
133
  def write_config_file(file_name, file_string, rubocop_yml_contents)
133
134
  lines = /\S/.match?(rubocop_yml_contents) ? rubocop_yml_contents.split("\n", -1) : []
134
- doc_start_index = lines.index { |line| YAML_OPTIONAL_DOC_START.match?(line) } || -1
135
- lines.insert(doc_start_index + 1, "inherit_from:#{file_string}\n")
136
- File.write(file_name, lines.join("\n"))
135
+ unless rubocop_yml_contents&.include?(PLACEHOLDER)
136
+ doc_start_index = lines.index { |line| YAML_OPTIONAL_DOC_START.match?(line) } || -1
137
+ lines.insert(doc_start_index + 1, PLACEHOLDER)
138
+ end
139
+ File.write(file_name, lines.join("\n")
140
+ .sub(/#{PLACEHOLDER}\n*/o, "inherit_from:#{file_string}\n\n")
141
+ .sub(/\n\n+\Z/, "\n"))
137
142
  end
138
143
 
139
144
  def relative_path_to_todo_from_options_config
data/lib/rubocop/cli.rb CHANGED
@@ -11,7 +11,7 @@ module RuboCop
11
11
  STATUS_ERROR = 2
12
12
  STATUS_INTERRUPTED = Signal.list['INT'] + 128
13
13
  DEFAULT_PARALLEL_OPTIONS = %i[
14
- color debug display_style_guide display_time display_only_fail_level_offenses
14
+ color config debug display_style_guide display_time display_only_fail_level_offenses
15
15
  display_only_failed except extra_details fail_level fix_layout format
16
16
  ignore_disable_comments lint only only_guide_cops require safe
17
17
  autocorrect safe_autocorrect autocorrect_all
@@ -82,7 +82,9 @@ module RuboCop
82
82
  node.array_type? && node.percent_literal?
83
83
  end
84
84
 
85
- percent_array.map(&:source_range).find { |range| range.overlaps?(offense_range) }
85
+ percent_array.map(&:source_range).find do |range|
86
+ offense_range.begin_pos > range.begin_pos && range.overlaps?(offense_range)
87
+ end
86
88
  end
87
89
 
88
90
  def range_of_first_line(range)
@@ -26,9 +26,7 @@ module RuboCop
26
26
  # expect_no_offenses('...')
27
27
  # end
28
28
  class ExampleDescription < Base
29
- class << self
30
- attr_accessor :descriptions
31
- end
29
+ extend AutoCorrector
32
30
 
33
31
  MSG = 'Description does not match use of `%<method_name>s`.'
34
32
 
@@ -39,22 +37,31 @@ module RuboCop
39
37
  expect_no_corrections
40
38
  ].to_set.freeze
41
39
 
42
- EXPECT_NO_OFFENSES_INCORRECT_DESCRIPTIONS = [
43
- /^(adds|registers|reports|finds) (an? )?offense/,
44
- /^(flags|handles|works)\b/
45
- ].freeze
40
+ EXPECT_NO_OFFENSES_DESCRIPTION_MAPPING = {
41
+ /\A(adds|registers|reports|finds) (an? )?offense/ => 'does not register an offense',
42
+ /\A(flags|handles|works)\b/ => 'does not register'
43
+ }.freeze
44
+
45
+ EXPECT_OFFENSE_DESCRIPTION_MAPPING = {
46
+ /\A(does not|doesn't) (register|find|flag|report)/ => 'registers',
47
+ /\A(does not|doesn't) add (a|an|any )?offense/ => 'registers an offense',
48
+ /\Aaccepts\b/ => 'registers'
49
+ }.freeze
46
50
 
47
- EXPECT_OFFENSE_INCORRECT_DESCRIPTIONS = [
48
- /^(does not|doesn't) (register|find|flag|report)/,
49
- /^(does not|doesn't) add (a|an|any )?offense/,
50
- /^accepts\b/
51
- ].freeze
51
+ EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING = {
52
+ /\A(auto[- ]?)?correct/ => 'does not correct'
53
+ }.freeze
52
54
 
53
- EXPECT_NO_CORRECTIONS_INCORRECT_DESCRIPTIONS = [/^(auto[- ]?)?correct/].freeze
55
+ EXPECT_CORRECTION_DESCRIPTION_MAPPING = {
56
+ /\b(does not|doesn't) (auto[- ]?)?correct/ => 'autocorrects'
57
+ }.freeze
54
58
 
55
- EXPECT_CORRECTION_INCORRECT_DESCRIPTIONS = [
56
- /\b(does not|doesn't) (auto[- ]?)?correct/
57
- ].freeze
59
+ EXAMPLE_DESCRIPTION_MAPPING = {
60
+ expect_no_offenses: EXPECT_NO_OFFENSES_DESCRIPTION_MAPPING,
61
+ expect_offense: EXPECT_OFFENSE_DESCRIPTION_MAPPING,
62
+ expect_no_corrections: EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING,
63
+ expect_correction: EXPECT_CORRECTION_DESCRIPTION_MAPPING
64
+ }.freeze
58
65
 
59
66
  # @!method offense_example?(node)
60
67
  def_node_matcher :offense_example?, <<~PATTERN
@@ -67,21 +74,34 @@ module RuboCop
67
74
 
68
75
  def on_send(node)
69
76
  parent = node.each_ancestor(:block).first
70
- return unless parent && (description = offense_example?(parent))
77
+ return unless parent && (current_description = offense_example?(parent))
71
78
 
72
79
  method_name = node.method_name
73
80
  message = format(MSG, method_name: method_name)
74
81
 
75
- regexp_group = self.class.const_get("#{method_name}_incorrect_descriptions".upcase)
76
- check_description(description, regexp_group, message)
82
+ description_map = EXAMPLE_DESCRIPTION_MAPPING[method_name]
83
+ check_description(current_description, description_map, message)
77
84
  end
78
85
 
79
86
  private
80
87
 
81
- def check_description(description, regexps, message)
82
- return unless regexps.any? { |regexp| regexp.match?(string_contents(description)) }
88
+ def check_description(current_description, description_map, message)
89
+ description_text = string_contents(current_description)
90
+ return unless (new_description = correct_description(description_text, description_map))
91
+
92
+ add_offense(current_description, message: message) do |corrector|
93
+ corrector.replace(current_description, "'#{new_description}'")
94
+ end
95
+ end
96
+
97
+ def correct_description(current_description, description_map)
98
+ description_map.each do |incorrect_description_pattern, preferred_description|
99
+ if incorrect_description_pattern.match?(current_description)
100
+ return current_description.gsub(incorrect_description_pattern, preferred_description)
101
+ end
102
+ end
83
103
 
84
- add_offense(description, message: message)
104
+ nil
85
105
  end
86
106
 
87
107
  def string_contents(node)
@@ -27,6 +27,8 @@ module RuboCop
27
27
  PATTERN
28
28
 
29
29
  def on_new_investigation
30
+ return if processed_source.blank?
31
+
30
32
  assertions_using_described_class_msg.each { |node| add_offense(node) }
31
33
  end
32
34
 
@@ -32,7 +32,7 @@ module RuboCop
32
32
  end
33
33
 
34
34
  def on_send(node)
35
- return unless node.dot? || ampersand_dot?(node)
35
+ return unless node.dot? || node.safe_navigation?
36
36
 
37
37
  return correct_style_detected if proper_dot_position?(node)
38
38
 
@@ -133,10 +133,6 @@ module RuboCop
133
133
  # l.(1) has no selector, so we use the opening parenthesis instead
134
134
  node.loc.selector || node.loc.begin
135
135
  end
136
-
137
- def ampersand_dot?(node)
138
- node.loc.respond_to?(:dot) && node.loc.dot && node.loc.dot.is?('&.')
139
- end
140
136
  end
141
137
  end
142
138
  end
@@ -164,6 +164,8 @@ module RuboCop
164
164
 
165
165
  if node.if_branch.and_type?
166
166
  node.if_branch.children.first
167
+ elsif use_heredoc_in_condition?(node.condition)
168
+ node.condition
167
169
  else
168
170
  node.if_branch.children.last
169
171
  end
@@ -180,6 +182,12 @@ module RuboCop
180
182
  node.respond_to?(:heredoc?) && node.heredoc?
181
183
  end
182
184
 
185
+ def use_heredoc_in_condition?(condition)
186
+ condition.descendants.any? do |descendant|
187
+ descendant.respond_to?(:heredoc?) && descendant.heredoc?
188
+ end
189
+ end
190
+
183
191
  def offense_location(node)
184
192
  if node.loc.respond_to?(:end) && node.loc.end
185
193
  node.loc.end
@@ -163,7 +163,13 @@ module RuboCop
163
163
  when :keyword
164
164
  node
165
165
  when :variable
166
- alignment_node_for_variable_style(node)
166
+ align_to = alignment_node_for_variable_style(node)
167
+
168
+ while (parent = align_to.parent) && parent.send_type? && same_line?(align_to, parent)
169
+ align_to = parent
170
+ end
171
+
172
+ align_to
167
173
  else
168
174
  start_line_range(node)
169
175
  end
@@ -354,7 +354,7 @@ module RuboCop
354
354
  # Don't check indentation if the line doesn't start with the body.
355
355
  # For example, lines like "else do_something".
356
356
  first_char_pos_on_line = body_node.source_range.source_line =~ /\S/
357
- true unless body_node.loc.column == first_char_pos_on_line
357
+ body_node.loc.column != first_char_pos_on_line
358
358
  end
359
359
 
360
360
  def offending_range(body_node, indentation)
@@ -75,7 +75,7 @@ module RuboCop
75
75
  def right_hand_side(send_node)
76
76
  dot = send_node.loc.dot
77
77
  selector = send_node.loc.selector
78
- if send_node.dot? && selector && same_line?(dot, selector)
78
+ if (send_node.dot? || send_node.safe_navigation?) && selector && same_line?(dot, selector)
79
79
  dot.join(selector)
80
80
  elsif selector
81
81
  selector
@@ -179,10 +179,10 @@ module RuboCop
179
179
  # a.b
180
180
  # .c
181
181
  def semantic_alignment_base(node, rhs)
182
- return unless rhs.source.start_with?('.')
182
+ return unless rhs.source.start_with?('.', '&.')
183
183
 
184
184
  node = semantic_alignment_node(node)
185
- return unless node&.loc&.selector
185
+ return unless node&.loc&.selector && node.loc.dot
186
186
 
187
187
  node.loc.dot.join(node.loc.selector)
188
188
  end
@@ -204,6 +204,10 @@ module RuboCop
204
204
  dot_right_above = get_dot_right_above(node)
205
205
  return dot_right_above if dot_right_above
206
206
 
207
+ if (multiline_block_chain_node = find_multiline_block_chain_node(node))
208
+ return multiline_block_chain_node
209
+ end
210
+
207
211
  node = first_call_has_a_dot(node)
208
212
  return if node.loc.dot.line != node.first_line
209
213
 
@@ -219,6 +223,17 @@ module RuboCop
219
223
  end
220
224
  end
221
225
 
226
+ def find_multiline_block_chain_node(node)
227
+ return unless (block_node = node.each_descendant(:block, :numblock).first)
228
+ return unless block_node.multiline? && block_node.parent.call_type?
229
+
230
+ if node.receiver.call_type?
231
+ node.receiver
232
+ else
233
+ block_node.parent
234
+ end
235
+ end
236
+
222
237
  def first_call_has_a_dot(node)
223
238
  # descend to root of method chain
224
239
  node = node.receiver while node.receiver
@@ -168,7 +168,7 @@ module RuboCop
168
168
  # follows, and that the rules for space inside don't apply.
169
169
  return true if token2.comment?
170
170
 
171
- true unless same_line?(token1, token2) && !token1.space_after?
171
+ !same_line?(token1, token2) || token1.space_after?
172
172
  end
173
173
  end
174
174
  end
@@ -95,8 +95,11 @@ module RuboCop
95
95
  def assumed_usage_context?(node)
96
96
  # Basically, debugger methods are not used as a method argument without arguments.
97
97
  return false unless node.arguments.empty? && node.each_ancestor(:send, :csend).any?
98
+ return true if assumed_argument?(node)
98
99
 
99
- node.each_ancestor.none?(&:lambda_or_proc?)
100
+ node.each_ancestor.none? do |ancestor|
101
+ ancestor.block_type? || ancestor.numblock_type? || ancestor.lambda_or_proc?
102
+ end
100
103
  end
101
104
 
102
105
  def chained_method_name(send_node)
@@ -109,6 +112,12 @@ module RuboCop
109
112
  end
110
113
  chained_method_name
111
114
  end
115
+
116
+ def assumed_argument?(node)
117
+ parent = node.parent
118
+
119
+ parent.call_type? || parent.literal? || parent.pair_type?
120
+ end
112
121
  end
113
122
  end
114
123
  end
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Lint
6
6
  # Checks for blocks without a body.
7
7
  # Such empty blocks are typically an oversight or we should provide a comment
8
- # be clearer what we're aiming for.
8
+ # to clarify what we're aiming for.
9
9
  #
10
10
  # Empty lambdas and procs are ignored by default.
11
11
  #
@@ -34,7 +34,7 @@ module RuboCop
34
34
  # interpolation should not be removed if the expanded value
35
35
  # contains a space character.
36
36
  expanded_value = autocorrected_value(final_node)
37
- return if in_array_percent_literal?(begin_node) && /\s/.match?(expanded_value)
37
+ return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
38
38
 
39
39
  add_offense(final_node) do |corrector|
40
40
  return if final_node.dstr_type? # nested, fixed in next iteration
@@ -95,7 +95,7 @@ module RuboCop
95
95
 
96
96
  def skip_range?(range_start, range_end)
97
97
  [range_start, range_end].any? do |bound|
98
- bound.type == :escape
98
+ bound.type != :literal
99
99
  end
100
100
  end
101
101
 
@@ -43,7 +43,6 @@ module RuboCop
43
43
  #
44
44
  class NonAtomicFileOperation < Base
45
45
  extend AutoCorrector
46
- include Alignment
47
46
 
48
47
  MSG_REMOVE_FILE_EXIST_CHECK = 'Remove unnecessary existence check ' \
49
48
  '`%<receiver>s.%<method_name>s`.'
@@ -24,6 +24,10 @@ module RuboCop
24
24
  #
25
25
  # This cop target those features.
26
26
  #
27
+ # @safety
28
+ # This cop's autocorrection is unsafe because if `require 'pp'` is removed from one file,
29
+ # `NameError` can be encountered when another file uses `PP.pp`.
30
+ #
27
31
  # @example
28
32
  # # bad
29
33
  # require 'unloaded_feature'
@@ -4,8 +4,12 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Checks for redundant safe navigation calls.
7
- # `instance_of?`, `kind_of?`, `is_a?`, `eql?`, `respond_to?`, and `equal?` methods
8
- # are checked by default. These are customizable with `AllowedMethods` option.
7
+ # Use cases where a constant, named in camel case for classes and modules is `nil` are rare,
8
+ # and an offense is not detected when the receiver is a snake case constant.
9
+ #
10
+ # For all receivers, the `instance_of?`, `kind_of?`, `is_a?`, `eql?`, `respond_to?`,
11
+ # and `equal?` methods are checked by default.
12
+ # These are customizable with `AllowedMethods` option.
9
13
  #
10
14
  # The `AllowedMethods` option specifies nil-safe methods,
11
15
  # in other words, it is a method that is allowed to skip safe navigation.
@@ -22,6 +26,9 @@ module RuboCop
22
26
  #
23
27
  # @example
24
28
  # # bad
29
+ # CamelCaseConst&.do_something
30
+ #
31
+ # # bad
25
32
  # do_something if attrs&.respond_to?(:[])
26
33
  #
27
34
  # # good
@@ -33,6 +40,9 @@ module RuboCop
33
40
  # end
34
41
  #
35
42
  # # good
43
+ # CamelCaseConst.do_something
44
+ #
45
+ # # good
36
46
  # while node.is_a?(BeginNode)
37
47
  # node = node.parent
38
48
  # end
@@ -57,18 +67,24 @@ module RuboCop
57
67
 
58
68
  NIL_SPECIFIC_METHODS = (nil.methods - Object.new.methods).to_set.freeze
59
69
 
70
+ SNAKE_CASE = /\A[[:digit:][:upper:]_]+\z/.freeze
71
+
60
72
  # @!method respond_to_nil_specific_method?(node)
61
73
  def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN
62
74
  (csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS))
63
75
  PATTERN
64
76
 
77
+ # rubocop:disable Metrics/AbcSize
65
78
  def on_csend(node)
66
- return unless check?(node) && allowed_method?(node.method_name)
67
- return if respond_to_nil_specific_method?(node)
79
+ unless node.receiver.const_type? && !node.receiver.source.match?(SNAKE_CASE)
80
+ return unless check?(node) && allowed_method?(node.method_name)
81
+ return if respond_to_nil_specific_method?(node)
82
+ end
68
83
 
69
84
  range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
70
85
  add_offense(range) { |corrector| corrector.replace(node.loc.dot, '.') }
71
86
  end
87
+ # rubocop:enable Metrics/AbcSize
72
88
 
73
89
  private
74
90
 
@@ -82,16 +82,23 @@ module RuboCop
82
82
  def autocorrect(corrector, offense_range:, send_node:)
83
83
  corrector.replace(
84
84
  offense_range,
85
- add_safe_navigation_operator(
86
- offense_range: offense_range,
87
- send_node: send_node
88
- )
85
+ add_safe_navigation_operator(offense_range: offense_range, send_node: send_node)
89
86
  )
87
+
88
+ corrector.wrap(send_node, '(', ')') if require_parentheses?(send_node)
90
89
  end
91
90
 
92
91
  def brackets?(send_node)
93
92
  send_node.method?(:[]) || send_node.method?(:[]=)
94
93
  end
94
+
95
+ def require_parentheses?(send_node)
96
+ return false unless send_node.comparison_method?
97
+ return false unless (node = send_node.parent)
98
+
99
+ (node.respond_to?(:logical_operator?) && node.logical_operator?) ||
100
+ (node.respond_to?(:comparison_method?) && node.comparison_method?)
101
+ end
95
102
  end
96
103
  end
97
104
  end
@@ -6,6 +6,16 @@ module RuboCop
6
6
  # Checks for operators, variables, literals, lambda, proc and nonmutating
7
7
  # methods used in void context.
8
8
  #
9
+ # `each` blocks are allowed to prevent false positives.
10
+ # For example, the expression inside the `each` block below.
11
+ # It's not void, especially when the receiver is an `Enumerator`:
12
+ #
13
+ # [source,ruby]
14
+ # ----
15
+ # enumerator = [1, 2, 3].filter
16
+ # enumerator.each { |item| item >= 2 } #=> [2, 3]
17
+ # ----
18
+ #
9
19
  # @example CheckForMethodsWithNoSideEffects: false (default)
10
20
  # # bad
11
21
  # def some_method
@@ -47,6 +57,7 @@ module RuboCop
47
57
 
48
58
  OP_MSG = 'Operator `%<op>s` used in void context.'
49
59
  VAR_MSG = 'Variable `%<var>s` used in void context.'
60
+ CONST_MSG = 'Constant `%<var>s` used in void context.'
50
61
  LIT_MSG = 'Literal `%<lit>s` used in void context.'
51
62
  SELF_MSG = '`self` used in void context.'
52
63
  EXPRESSION_MSG = '`%<expression>s` used in void context.'
@@ -72,6 +83,7 @@ module RuboCop
72
83
  return unless node.body && !node.body.begin_type?
73
84
  return unless in_void_context?(node.body)
74
85
 
86
+ check_void_op(node.body) { node.method?(:each) }
75
87
  check_expression(node.body)
76
88
  end
77
89
 
@@ -87,11 +99,13 @@ module RuboCop
87
99
  def check_begin(node)
88
100
  expressions = *node
89
101
  expressions.pop unless in_void_context?(node)
90
- expressions.each { |expr| check_expression(expr) }
102
+ expressions.each do |expr|
103
+ check_void_op(expr)
104
+ check_expression(expr)
105
+ end
91
106
  end
92
107
 
93
108
  def check_expression(expr)
94
- check_void_op(expr)
95
109
  check_literal(expr)
96
110
  check_var(expr)
97
111
  check_self(expr)
@@ -101,8 +115,9 @@ module RuboCop
101
115
  check_nonmutating(expr)
102
116
  end
103
117
 
104
- def check_void_op(node)
118
+ def check_void_op(node, &block)
105
119
  return unless node.send_type? && OPERATORS.include?(node.method_name)
120
+ return if block && yield(node)
106
121
 
107
122
  add_offense(node.loc.selector,
108
123
  message: format(OP_MSG, op: node.method_name)) do |corrector|
@@ -113,15 +128,18 @@ module RuboCop
113
128
  def check_var(node)
114
129
  return unless node.variable? || node.const_type?
115
130
 
116
- if node.const_type? && node.special_keyword?
117
- add_offense(node, message: format(VAR_MSG, var: node.source)) do |corrector|
118
- autocorrect_void_expression(corrector, node)
119
- end
131
+ if node.const_type?
132
+ template = node.special_keyword? ? VAR_MSG : CONST_MSG
133
+
134
+ offense_range = node
135
+ message = format(template, var: node.source)
120
136
  else
121
- add_offense(node.loc.name,
122
- message: format(VAR_MSG, var: node.loc.name.source)) do |corrector|
123
- autocorrect_void_expression(corrector, node)
124
- end
137
+ offense_range = node.loc.name
138
+ message = format(VAR_MSG, var: node.loc.name.source)
139
+ end
140
+
141
+ add_offense(offense_range, message: message) do |corrector|
142
+ autocorrect_void_expression(corrector, node)
125
143
  end
126
144
  end
127
145
 
@@ -12,6 +12,7 @@ module RuboCop
12
12
  # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct
13
13
  # will be counted as one line regardless of its actual size.
14
14
  #
15
+ # NOTE: This cop does not apply for `Struct` definitions.
15
16
  #
16
17
  # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
17
18
  # for backwards compatibility. Please use `AllowedMethods` and `AllowedPatterns`
@@ -40,7 +41,6 @@ module RuboCop
40
41
  # )
41
42
  # end # 6 points
42
43
  #
43
- # NOTE: This cop does not apply for `Struct` definitions.
44
44
  class BlockLength < Base
45
45
  include CodeLength
46
46
  include AllowedMethods