rubocop 1.56.2 → 1.56.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -6
  3. data/lib/rubocop/cop/autocorrect_logic.rb +3 -1
  4. data/lib/rubocop/cop/bundler/duplicated_group.rb +1 -1
  5. data/lib/rubocop/cop/internal_affairs/example_description.rb +42 -22
  6. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +11 -2
  7. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +2 -0
  8. data/lib/rubocop/cop/layout/dot_position.rb +1 -5
  9. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +41 -8
  10. data/lib/rubocop/cop/layout/heredoc_indentation.rb +3 -0
  11. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  12. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  13. data/lib/rubocop/cop/layout/space_after_not.rb +1 -1
  14. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +11 -4
  15. data/lib/rubocop/cop/lint/useless_assignment.rb +37 -10
  16. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  17. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -2
  18. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +5 -7
  19. data/lib/rubocop/cop/style/arguments_forwarding.rb +8 -6
  20. data/lib/rubocop/cop/style/array_intersect.rb +13 -5
  21. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  22. data/lib/rubocop/cop/style/combinable_loops.rb +3 -2
  23. data/lib/rubocop/cop/style/empty_case_condition.rb +6 -1
  24. data/lib/rubocop/cop/style/for.rb +1 -1
  25. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -1
  26. data/lib/rubocop/cop/style/operator_method_call.rb +6 -0
  27. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -9
  28. data/lib/rubocop/cop/style/return_nil.rb +6 -2
  29. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  30. data/lib/rubocop/cop/style/symbol_array.rb +10 -11
  31. data/lib/rubocop/cop/style/yoda_expression.rb +8 -7
  32. data/lib/rubocop/file_finder.rb +4 -7
  33. data/lib/rubocop/magic_comment.rb +12 -10
  34. data/lib/rubocop/rspec/shared_contexts.rb +2 -3
  35. data/lib/rubocop/version.rb +1 -1
  36. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 061c4fd159baec4f143f014ae432ec3607b0846cb46b3a4566a224922b6c5c7b
4
- data.tar.gz: 943c45cc27dbbb861f6b2a910216a016c06e2ace63824c30a7ad922caabfed59
3
+ metadata.gz: f3088047e83f70bb0665d28875e2498dce9f6c9066be1524691b3205b155953e
4
+ data.tar.gz: 37aa8682af6da472cb469924c7a5407b0b947f15a519356dc035f45e4e82982d
5
5
  SHA512:
6
- metadata.gz: 5d5900ca290d826af3658c1d481a7778b44a4d57455d69c96c603bac7679c4b275cd192339e683e6ba020201e0dbb23ee3e64f9fbc953cc5bf4832ce1c293bbb
7
- data.tar.gz: dfc761f98d05680a21093ce5092b933ff8534f7b7ed5f7c46ec52586673db9ad60b495e896b429499d151c6af827a30d1e67c2e1bf397f9a7947b1c0bff836ab
6
+ metadata.gz: d075e0720342cb33925fbbb41d13ddbfe6d060dd92df26890b839e7fb3a65ea6b78e0f0cc42ed31fb038e363e2ec57c5e068e8372ed2e39f48cba2c922bc8e9a
7
+ data.tar.gz: 7807c06a028201f01c168298f3587160d7d84d7eb8d968b01ecf33772180b8bdeba5dd1fd6a38b60ed54bb259fc306eb46d174baee5f66222986d467b38e2a85
data/config/default.yml CHANGED
@@ -3409,6 +3409,7 @@ Style/CollectionMethods:
3409
3409
  PreferredMethods:
3410
3410
  collect: 'map'
3411
3411
  collect!: 'map!'
3412
+ collect_concat: 'flat_map'
3412
3413
  inject: 'reduce'
3413
3414
  detect: 'find'
3414
3415
  find_all: 'select'
@@ -4130,12 +4131,6 @@ Style/InvertibleUnlessCondition:
4130
4131
  :none?: :any?
4131
4132
  :even?: :odd?
4132
4133
  :odd?: :even?
4133
- # `ActiveSupport` defines some common inverse methods. They are listed below,
4134
- # and not enabled by default.
4135
- # :present?: :blank?
4136
- # :blank?: :present?
4137
- # :include?: :exclude?
4138
- # :exclude?: :include?
4139
4134
 
4140
4135
  Style/IpAddresses:
4141
4136
  Description: "Don't include literal IP addresses in code."
@@ -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)
@@ -117,7 +117,7 @@ module RuboCop
117
117
  if argument.hash_type?
118
118
  argument.pairs.map(&:source).sort.join(', ')
119
119
  else
120
- argument.value.to_s
120
+ argument.respond_to?(:value) ? argument.value.to_s : argument.source
121
121
  end
122
122
  end
123
123
  end
@@ -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)
@@ -14,6 +14,12 @@ module RuboCop
14
14
  # node.method_name
15
15
  #
16
16
  # # bad
17
+ # node.send_node.method?(:method_name)
18
+ #
19
+ # # good
20
+ # node.method?(:method_name)
21
+ #
22
+ # # bad
17
23
  # node.send_node.receiver
18
24
  #
19
25
  # # good
@@ -24,11 +30,14 @@ module RuboCop
24
30
  extend AutoCorrector
25
31
 
26
32
  MSG = 'Remove the redundant `send_node`.'
27
- RESTRICT_ON_SEND = %i[method_name receiver].freeze
33
+ RESTRICT_ON_SEND = %i[method_name method? receiver].freeze
28
34
 
29
35
  # @!method dispatch_method(node)
30
36
  def_node_matcher :dispatch_method, <<~PATTERN
31
- (send $(send _ :send_node) _)
37
+ {
38
+ (send $(send _ :send_node) {:method_name :receiver})
39
+ (send $(send _ :send_node) :method? _)
40
+ }
32
41
  PATTERN
33
42
 
34
43
  def on_send(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
@@ -3,7 +3,23 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Enforces empty line after guard clause
6
+ # Enforces empty line after guard clause.
7
+ #
8
+ # This cop allows `# :nocov:` directive after guard clause because
9
+ # SimpleCov excludes code from the coverage report by wrapping it in `# :nocov:`:
10
+ #
11
+ # [source,ruby]
12
+ # ----
13
+ # def foo
14
+ # # :nocov:
15
+ # return if condition
16
+ # # :nocov:
17
+ # bar
18
+ # end
19
+ # ----
20
+ #
21
+ # Refer to SimpleCov's documentation for more details:
22
+ # https://github.com/simplecov-ruby/simplecov#ignoringskipping-code
7
23
  #
8
24
  # @example
9
25
  #
@@ -42,19 +58,22 @@ module RuboCop
42
58
 
43
59
  MSG = 'Add empty line after guard clause.'
44
60
  END_OF_HEREDOC_LINE = 1
61
+ SIMPLE_DIRECTIVE_COMMENT_PATTERN = /\A# *:nocov:\z/.freeze
45
62
 
46
63
  def on_if(node)
47
64
  return if correct_style?(node)
48
65
  return if multiple_statements_on_line?(node)
49
66
 
50
67
  if node.modifier_form? && (heredoc_node = last_heredoc_argument(node))
51
- return if next_line_empty_or_enable_directive_comment?(heredoc_line(node, heredoc_node))
68
+ if next_line_empty_or_allowed_directive_comment?(heredoc_line(node, heredoc_node))
69
+ return
70
+ end
52
71
 
53
72
  add_offense(heredoc_node.loc.heredoc_end) do |corrector|
54
73
  autocorrect(corrector, heredoc_node)
55
74
  end
56
75
  else
57
- return if next_line_empty_or_enable_directive_comment?(node.last_line)
76
+ return if next_line_empty_or_allowed_directive_comment?(node.last_line)
58
77
 
59
78
  add_offense(offense_location(node)) { |corrector| autocorrect(corrector, node) }
60
79
  end
@@ -70,7 +89,7 @@ module RuboCop
70
89
  end
71
90
 
72
91
  next_line = node_range.last_line + 1
73
- if next_line_enable_directive_comment?(next_line)
92
+ if next_line_allowed_directive_comment?(next_line)
74
93
  node_range = processed_source.comment_at_line(next_line)
75
94
  end
76
95
 
@@ -88,21 +107,21 @@ module RuboCop
88
107
  node.if_branch&.guard_clause?
89
108
  end
90
109
 
91
- def next_line_empty_or_enable_directive_comment?(line)
110
+ def next_line_empty_or_allowed_directive_comment?(line)
92
111
  return true if next_line_empty?(line)
93
112
 
94
113
  next_line = line + 1
95
- next_line_enable_directive_comment?(next_line) && next_line_empty?(next_line)
114
+ next_line_allowed_directive_comment?(next_line) && next_line_empty?(next_line)
96
115
  end
97
116
 
98
117
  def next_line_empty?(line)
99
118
  processed_source[line].blank?
100
119
  end
101
120
 
102
- def next_line_enable_directive_comment?(line)
121
+ def next_line_allowed_directive_comment?(line)
103
122
  return false unless (comment = processed_source.comment_at_line(line))
104
123
 
105
- DirectiveComment.new(comment).enabled?
124
+ DirectiveComment.new(comment).enabled? || simplecov_directive_comment?(comment)
106
125
  end
107
126
 
108
127
  def next_line_rescue_or_ensure?(node)
@@ -145,6 +164,8 @@ module RuboCop
145
164
 
146
165
  if node.if_branch.and_type?
147
166
  node.if_branch.children.first
167
+ elsif use_heredoc_in_condition?(node.condition)
168
+ node.condition
148
169
  else
149
170
  node.if_branch.children.last
150
171
  end
@@ -161,6 +182,12 @@ module RuboCop
161
182
  node.respond_to?(:heredoc?) && node.heredoc?
162
183
  end
163
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
+
164
191
  def offense_location(node)
165
192
  if node.loc.respond_to?(:end) && node.loc.end
166
193
  node.loc.end
@@ -175,6 +202,12 @@ module RuboCop
175
202
 
176
203
  parent.begin_type? && parent.single_line?
177
204
  end
205
+
206
+ # SimpleCov excludes code from the coverage report by wrapping it in `# :nocov:`:
207
+ # https://github.com/simplecov-ruby/simplecov#ignoringskipping-code
208
+ def simplecov_directive_comment?(comment)
209
+ SIMPLE_DIRECTIVE_COMMENT_PATTERN.match?(comment.text)
210
+ end
178
211
  end
179
212
  end
180
213
  end
@@ -25,6 +25,9 @@ module RuboCop
25
25
  include Alignment
26
26
  include Heredoc
27
27
  extend AutoCorrector
28
+ extend TargetRubyVersion
29
+
30
+ minimum_target_ruby_version 2.3
28
31
 
29
32
  TYPE_MSG = 'Use %<indentation_width>d spaces for indentation in a ' \
30
33
  'heredoc by using `<<~` instead of `%<current_indent_type>s`.'
@@ -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,7 +179,7 @@ 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
185
  return unless node&.loc&.selector
@@ -108,7 +108,7 @@ module RuboCop
108
108
  !comment_within?(node) &&
109
109
  node.each_descendant(:if, :case, :kwbegin, :def, :defs).none? &&
110
110
  node.each_descendant(:dstr, :str).none? { |n| n.heredoc? || n.value.include?("\n") } &&
111
- node.each_descendant(:begin).none? { |b| !b.single_line? }
111
+ node.each_descendant(:begin, :sym).none? { |b| !b.single_line? }
112
112
  end
113
113
 
114
114
  def convertible_block?(node)
@@ -31,7 +31,7 @@ module RuboCop
31
31
  private
32
32
 
33
33
  def whitespace_after_operator?(node)
34
- node.receiver.loc.column - node.loc.column > 1
34
+ node.receiver.source_range.begin_pos - node.source_range.begin_pos > 1
35
35
  end
36
36
  end
37
37
  end
@@ -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
@@ -7,12 +7,18 @@ module RuboCop
7
7
  # scope.
8
8
  # The basic idea for this cop was from the warning of `ruby -cw`:
9
9
  #
10
- # assigned but unused variable - foo
10
+ # [source,console]
11
+ # ----
12
+ # assigned but unused variable - foo
13
+ # ----
11
14
  #
12
15
  # Currently this cop has advanced logic that detects unreferenced
13
16
  # reassignments and properly handles varied cases such as branch, loop,
14
17
  # rescue, ensure, etc.
15
18
  #
19
+ # NOTE: Given the assignment `foo = 1, bar = 2`, removing unused variables
20
+ # can lead to a syntax error, so this case is not autocorrected.
21
+ #
16
22
  # @safety
17
23
  # This cop's autocorrection is unsafe because removing assignment from
18
24
  # operator assignment can cause NameError if this assignment has been used to declare
@@ -51,25 +57,24 @@ module RuboCop
51
57
  scope.variables.each_value { |variable| check_for_unused_assignments(variable) }
52
58
  end
53
59
 
60
+ # rubocop:disable Metrics/AbcSize
54
61
  def check_for_unused_assignments(variable)
55
62
  return if variable.should_be_unused?
56
63
 
57
64
  variable.assignments.each do |assignment|
58
- next if assignment.used?
65
+ next if assignment.used? || part_of_ignored_node?(assignment.node)
59
66
 
60
67
  message = message_for_useless_assignment(assignment)
68
+ range = offense_range(assignment)
61
69
 
62
- location = if assignment.regexp_named_capture?
63
- assignment.node.children.first.source_range
64
- else
65
- assignment.node.loc.name
66
- end
67
-
68
- add_offense(location, message: message) do |corrector|
69
- autocorrect(corrector, assignment)
70
+ add_offense(range, message: message) do |corrector|
71
+ autocorrect(corrector, assignment) unless sequential_assignment?(assignment.node)
70
72
  end
73
+
74
+ ignore_node(assignment.node) if chained_assignment?(assignment.node)
71
75
  end
72
76
  end
77
+ # rubocop:enable Metrics/AbcSize
73
78
 
74
79
  def message_for_useless_assignment(assignment)
75
80
  variable = assignment.variable
@@ -77,6 +82,28 @@ module RuboCop
77
82
  format(MSG, variable: variable.name) + message_specification(assignment, variable).to_s
78
83
  end
79
84
 
85
+ def offense_range(assignment)
86
+ if assignment.regexp_named_capture?
87
+ assignment.node.children.first.source_range
88
+ else
89
+ assignment.node.loc.name
90
+ end
91
+ end
92
+
93
+ def sequential_assignment?(node)
94
+ if node.lvasgn_type? && node.expression&.array_type? &&
95
+ node.each_descendant.any?(&:assignment?)
96
+ return true
97
+ end
98
+ return false unless node.parent
99
+
100
+ sequential_assignment?(node.parent)
101
+ end
102
+
103
+ def chained_assignment?(node)
104
+ node.respond_to?(:expression) && node.expression&.lvasgn_type?
105
+ end
106
+
80
107
  def message_specification(assignment, variable)
81
108
  if assignment.multiple_assignment?
82
109
  multiple_assignment_message(variable.name)
@@ -54,7 +54,7 @@ module RuboCop
54
54
  alias on_defs on_def
55
55
 
56
56
  def on_block(node)
57
- return unless node.send_node.method?(:define_method)
57
+ return unless node.method?(:define_method)
58
58
 
59
59
  check_code_length(node)
60
60
  end
@@ -20,16 +20,17 @@ module RuboCop
20
20
  range = offending_range(node, lhs, rhs, style)
21
21
  check(range, node, lhs, rhs)
22
22
  end
23
+ alias on_csend on_send
23
24
 
24
25
  private
25
26
 
26
- # In a chain of method calls, we regard the top send node as the base
27
+ # In a chain of method calls, we regard the top call node as the base
27
28
  # for indentation of all lines following the first. For example:
28
29
  # a.
29
30
  # b c { block }. <-- b is indented relative to a
30
31
  # d <-- d is indented relative to a
31
32
  def left_hand_side(lhs)
32
- while lhs.parent&.send_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method?
33
+ while lhs.parent&.call_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method?
33
34
  lhs = lhs.parent
34
35
  end
35
36
  lhs
@@ -75,7 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def aligned_token?(range, line)
78
- aligned_words?(range, line) || aligned_dot?(range, line) || aligned_assignment?(range, line)
78
+ aligned_words?(range, line) || aligned_assignment?(range, line)
79
79
  end
80
80
 
81
81
  def aligned_operator?(range, line)
@@ -83,13 +83,11 @@ module RuboCop
83
83
  end
84
84
 
85
85
  def aligned_words?(range, line)
86
- /\s\S/.match?(line[range.column - 1, 2])
87
- end
88
-
89
- def aligned_dot?(range, line)
90
- char = line[range.column]
86
+ left_edge = range.column
87
+ return true if /\s\S/.match?(line[left_edge - 1, 2])
91
88
 
92
- char == '.' && char == range.source[0]
89
+ token = range.source
90
+ token == line[left_edge, token.length]
93
91
  end
94
92
 
95
93
  def aligned_assignment?(range, line)
@@ -116,11 +116,11 @@ module RuboCop
116
116
  end
117
117
 
118
118
  def only_forwards_all?(send_classifications)
119
- send_classifications.each_value.all? { |c, _, _| c == :all }
119
+ send_classifications.all? { |_, c, _, _| c == :all }
120
120
  end
121
121
 
122
122
  def add_forward_all_offenses(node, send_classifications, forwardable_args)
123
- send_classifications.each do |send_node, (_c, forward_rest, _forward_kwrest)|
123
+ send_classifications.each do |send_node, _c, forward_rest, _forward_kwrest|
124
124
  register_forward_all_offense(send_node, send_node, forward_rest)
125
125
  end
126
126
 
@@ -133,7 +133,7 @@ module RuboCop
133
133
 
134
134
  rest_arg, kwrest_arg, _block_arg = *forwardable_args
135
135
 
136
- send_classifications.each do |send_node, (_c, forward_rest, forward_kwrest)|
136
+ send_classifications.each do |send_node, _c, forward_rest, forward_kwrest|
137
137
  if forward_rest
138
138
  register_forward_args_offense(def_node.arguments, rest_arg)
139
139
  register_forward_args_offense(send_node, forward_rest)
@@ -157,7 +157,7 @@ module RuboCop
157
157
  end
158
158
 
159
159
  def classify_send_nodes(def_node, send_nodes, referenced_lvars, forwardable_args)
160
- send_nodes.to_h do |send_node|
160
+ send_nodes.filter_map do |send_node|
161
161
  classification_and_forwards = classification_and_forwards(
162
162
  def_node,
163
163
  send_node,
@@ -165,8 +165,10 @@ module RuboCop
165
165
  forwardable_args
166
166
  )
167
167
 
168
- [send_node, classification_and_forwards]
169
- end.compact
168
+ next unless classification_and_forwards
169
+
170
+ [send_node, *classification_and_forwards]
171
+ end
170
172
  end
171
173
 
172
174
  def classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args)
@@ -11,6 +11,15 @@ module RuboCop
11
11
  # The `array1.intersect?(array2)` method is faster than
12
12
  # `(array1 & array2).any?` and is more readable.
13
13
  #
14
+ # In cases like the following, compatibility is not ensured,
15
+ # so it will not be detected when using block argument.
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # ([1] & [1,2]).any? { |x| false } # => false
20
+ # [1].intersect?([1,2]) { |x| false } # => true
21
+ # ----
22
+ #
14
23
  # @safety
15
24
  # This cop cannot guarantee that `array1` and `array2` are
16
25
  # actually arrays while method `intersect?` is for arrays only.
@@ -68,16 +77,15 @@ module RuboCop
68
77
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
69
78
 
70
79
  def on_send(node)
80
+ return if (parent = node.parent) && (parent.block_type? || parent.numblock_type?)
71
81
  return unless (receiver, argument, method_name = bad_intersection_check?(node))
72
82
 
73
83
  message = message(receiver.source, argument.source, method_name)
74
84
 
75
85
  add_offense(node, message: message) do |corrector|
76
- if straight?(method_name)
77
- corrector.replace(node, "#{receiver.source}.intersect?(#{argument.source})")
78
- else
79
- corrector.replace(node, "!#{receiver.source}.intersect?(#{argument.source})")
80
- end
86
+ bang = straight?(method_name) ? '' : '!'
87
+
88
+ corrector.replace(node, "#{bang}#{receiver.source}.intersect?(#{argument.source})")
81
89
  end
82
90
  end
83
91
 
@@ -25,6 +25,7 @@ module RuboCop
25
25
  # # bad
26
26
  # items.collect
27
27
  # items.collect!
28
+ # items.collect_concat
28
29
  # items.inject
29
30
  # items.detect
30
31
  # items.find_all
@@ -33,6 +34,7 @@ module RuboCop
33
34
  # # good
34
35
  # items.map
35
36
  # items.map!
37
+ # items.flat_map
36
38
  # items.reduce
37
39
  # items.find
38
40
  # items.select
@@ -93,8 +93,9 @@ module RuboCop
93
93
  end
94
94
 
95
95
  def same_collection_looping_block?(node, sibling)
96
- (sibling&.block_type? || sibling&.numblock_type?) &&
97
- sibling.send_node.method?(node.method_name) &&
96
+ return false if sibling.nil? || (!sibling.block_type? && !sibling.numblock_type?)
97
+
98
+ sibling.method?(node.method_name) &&
98
99
  sibling.receiver == node.receiver &&
99
100
  sibling.send_node.arguments == node.send_node.arguments
100
101
  end
@@ -40,9 +40,13 @@ module RuboCop
40
40
  extend AutoCorrector
41
41
 
42
42
  MSG = 'Do not use empty `case` condition, instead use an `if` expression.'
43
+ NOT_SUPPORTED_PARENT_TYPES = %i[return break next send csend].freeze
43
44
 
45
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
44
46
  def on_case(case_node)
45
- return if case_node.condition
47
+ if case_node.condition || NOT_SUPPORTED_PARENT_TYPES.include?(case_node.parent&.type)
48
+ return
49
+ end
46
50
 
47
51
  branch_bodies = [*case_node.when_branches.map(&:body), case_node.else_branch].compact
48
52
 
@@ -52,6 +56,7 @@ module RuboCop
52
56
 
53
57
  add_offense(case_node.loc.keyword) { |corrector| autocorrect(corrector, case_node) }
54
58
  end
59
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
55
60
 
56
61
  private
57
62
 
@@ -80,7 +80,7 @@ module RuboCop
80
80
  private
81
81
 
82
82
  def suspect_enumerable?(node)
83
- node.multiline? && node.send_node.method?(:each) && !node.send_node.arguments?
83
+ node.multiline? && node.method?(:each) && !node.send_node.arguments?
84
84
  end
85
85
  end
86
86
  end
@@ -39,7 +39,7 @@ module RuboCop
39
39
 
40
40
  MSG_IF = 'Avoid multi-line ternary operators, use `if` or `unless` instead.'
41
41
  MSG_SINGLE_LINE = 'Avoid multi-line ternary operators, use single-line instead.'
42
- SINGLE_LINE_TYPES = %i[return break next send].freeze
42
+ SINGLE_LINE_TYPES = %i[return break next send csend].freeze
43
43
 
44
44
  def on_if(node)
45
45
  return unless offense?(node)
@@ -23,6 +23,7 @@ module RuboCop
23
23
  MSG = 'Redundant dot detected.'
24
24
  RESTRICT_ON_SEND = %i[| ^ & <=> == === =~ > >= < <= << >> + - * / % ** ~ ! != !~].freeze
25
25
 
26
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
26
27
  def on_send(node)
27
28
  return unless (dot = node.loc.dot)
28
29
  return if node.receiver.const_type? || !node.arguments.one?
@@ -33,8 +34,12 @@ module RuboCop
33
34
  add_offense(dot) do |corrector|
34
35
  wrap_in_parentheses_if_chained(corrector, node)
35
36
  corrector.replace(dot, ' ')
37
+
38
+ selector = node.loc.selector
39
+ corrector.insert_after(selector, ' ') if selector.end_pos == rhs.source_range.begin_pos
36
40
  end
37
41
  end
42
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
38
43
 
39
44
  private
40
45
 
@@ -54,6 +59,7 @@ module RuboCop
54
59
 
55
60
  def wrap_in_parentheses_if_chained(corrector, node)
56
61
  return unless node.parent&.call_type?
62
+ return if node.parent.first_argument == node
57
63
 
58
64
  operator = node.loc.selector
59
65
 
@@ -70,19 +70,11 @@ module RuboCop
70
70
 
71
71
  def replacement_condition(node)
72
72
  condition = node.condition.source
73
- expression = invert_expression?(node) ? "!(#{condition})" : condition
73
+ expression = redundant_condition_inverted?(node) ? "!(#{condition})" : condition
74
74
 
75
75
  node.elsif? ? indented_else_node(expression, node) : expression
76
76
  end
77
77
 
78
- def invert_expression?(node)
79
- (
80
- (node.if? || node.elsif? || node.ternary?) && redundant_condition_inverted?(node)
81
- ) || (
82
- node.unless? && redundant_condition?(node)
83
- )
84
- end
85
-
86
78
  def indented_else_node(expression, node)
87
79
  "else\n#{indentation(node)}#{expression}"
88
80
  end
@@ -3,9 +3,13 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Enforces consistency between 'return nil' and 'return'.
6
+ # Enforces consistency between `return nil` and `return`.
7
7
  #
8
- # Supported styles are: return, return_nil.
8
+ # This cop is disabled by default. Because there seems to be a perceived semantic difference
9
+ # between `return` and `return nil`. The former can be seen as just halting evaluation,
10
+ # while the latter might be used when the return value is of specific concern.
11
+ #
12
+ # Supported styles are `return` and `return_nil`.
9
13
  #
10
14
  # @example EnforcedStyle: return (default)
11
15
  # # bad
@@ -173,7 +173,9 @@ module RuboCop
173
173
  end
174
174
 
175
175
  def correct_for_comment(corrector, node, if_branch)
176
- comments = processed_source.ast_with_comments[if_branch]
176
+ comments = processed_source.ast_with_comments[if_branch].select do |comment|
177
+ comment.loc.line < if_branch.condition.first_line
178
+ end
177
179
  comment_text = comments.map(&:text).join("\n") << "\n"
178
180
 
179
181
  corrector.insert_before(node.loc.keyword, comment_text) unless comments.empty?
@@ -50,6 +50,14 @@ module RuboCop
50
50
  PERCENT_MSG = 'Use `%i` or `%I` for an array of symbols.'
51
51
  ARRAY_MSG = 'Use %<prefer>s for an array of symbols.'
52
52
  DELIMITERS = ['[', ']', '(', ')'].freeze
53
+ SPECIAL_GVARS = %w[
54
+ $! $" $$ $& $' $* $+ $, $/ $; $: $. $< $= $> $? $@ $\\ $_ $` $~ $0
55
+ $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w
56
+ ].freeze
57
+ REDEFINABLE_OPERATORS = %w(
58
+ | ^ & <=> == === =~ > >= < <= << >>
59
+ + - * / % ** ~ +@ -@ [] []= ` ! != !~
60
+ ).freeze
53
61
 
54
62
  class << self
55
63
  attr_accessor :largest_brackets
@@ -109,15 +117,6 @@ module RuboCop
109
117
  end
110
118
 
111
119
  def symbol_without_quote?(string)
112
- special_gvars = %w[
113
- $! $" $$ $& $' $* $+ $, $/ $; $: $. $< $= $> $? $@ $\\ $_ $` $~ $0
114
- $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w
115
- ]
116
- redefinable_operators = %w(
117
- | ^ & <=> == === =~ > >= < <= << >>
118
- + - * / % ** ~ +@ -@ [] []= ` ! != !~
119
- )
120
-
121
120
  # method name
122
121
  /\A[a-zA-Z_]\w*[!?]?\z/.match?(string) ||
123
122
  # instance / class variable
@@ -125,8 +124,8 @@ module RuboCop
125
124
  # global variable
126
125
  /\A\$[1-9]\d*\z/.match?(string) ||
127
126
  /\A\$[a-zA-Z_]\w*\z/.match?(string) ||
128
- special_gvars.include?(string) ||
129
- redefinable_operators.include?(string)
127
+ SPECIAL_GVARS.include?(string) ||
128
+ REDEFINABLE_OPERATORS.include?(string)
130
129
  end
131
130
  end
132
131
  end
@@ -19,22 +19,23 @@ module RuboCop
19
19
  # differently on different classes, and are not guaranteed to
20
20
  # have the same result if reversed.
21
21
  #
22
- # @example SupportedOperators: ['*', '+', '&'']
22
+ # @example SupportedOperators: ['*', '+', '&', '|', '^'] (default)
23
23
  # # bad
24
- # 1 + x
25
24
  # 10 * y
25
+ # 1 + x
26
26
  # 1 & z
27
+ # 1 | x
28
+ # 1 ^ x
27
29
  # 1 + CONST
28
30
  #
29
31
  # # good
30
- # 60 * 24
31
- # x + 1
32
32
  # y * 10
33
+ # x + 1
33
34
  # z & 1
35
+ # x | 1
36
+ # x ^ 1
34
37
  # CONST + 1
35
- #
36
- # # good
37
- # 1 | x
38
+ # 60 * 24
38
39
  #
39
40
  class YodaExpression < Base
40
41
  extend AutoCorrector
@@ -6,12 +6,8 @@ module RuboCop
6
6
  # Common methods for finding files.
7
7
  # @api private
8
8
  module FileFinder
9
- def self.root_level=(level)
10
- @root_level = level
11
- end
12
-
13
- def self.root_level?(path, stop_dir)
14
- (@root_level || stop_dir) == path.to_s
9
+ class << self
10
+ attr_accessor :root_level
15
11
  end
16
12
 
17
13
  def find_file_upwards(filename, start_dir, stop_dir = nil)
@@ -34,7 +30,8 @@ module RuboCop
34
30
  file = dir + filename
35
31
  yield(file.to_s) if file.exist?
36
32
 
37
- break if FileFinder.root_level?(dir, stop_dir)
33
+ dir = dir.to_s
34
+ break if dir == stop_dir || dir == FileFinder.root_level
38
35
  end
39
36
  end
40
37
  end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  class MagicComment
8
8
  # IRB's pattern for matching magic comment tokens.
9
9
  # @see https://github.com/ruby/ruby/blob/b4a55c1/lib/irb/magic-file.rb#L5
10
- TOKEN = /[[:alnum:]\-_]+/.freeze
10
+ TOKEN = '(?<token>[[:alnum:]\-_]+)'
11
11
  KEYWORDS = {
12
12
  encoding: '(?:en)?coding',
13
13
  frozen_string_literal: 'frozen[_-]string[_-]literal',
@@ -129,7 +129,7 @@ module RuboCop
129
129
  # @return [String] if pattern matched
130
130
  # @return [nil] otherwise
131
131
  def extract(pattern)
132
- @comment[pattern, 1]
132
+ @comment[pattern, :token]
133
133
  end
134
134
 
135
135
  # Parent to Vim and Emacs magic comment handling.
@@ -157,10 +157,10 @@ module RuboCop
157
157
  # @return [String] extracted value if it is found
158
158
  # @return [nil] otherwise
159
159
  def match(keyword)
160
- pattern = /\A#{keyword}\s*#{self.class::OPERATOR}\s*(#{TOKEN})\z/
160
+ pattern = /\A#{keyword}\s*#{self.class::OPERATOR}\s*#{TOKEN}\z/
161
161
 
162
162
  tokens.each do |token|
163
- next unless (value = token[pattern, 1])
163
+ next unless (value = token[pattern, :token])
164
164
 
165
165
  return value.downcase
166
166
  end
@@ -188,7 +188,7 @@ module RuboCop
188
188
  # @see https://www.gnu.org/software/emacs/manual/html_node/emacs/Specify-Coding.html
189
189
  # @see https://github.com/ruby/ruby/blob/3f306dc/parse.y#L6873-L6892 Emacs handling in parse.y
190
190
  class EmacsComment < EditorComment
191
- REGEXP = /-\*-(.+)-\*-/.freeze
191
+ REGEXP = /-\*-(?<token>.+)-\*-/.freeze
192
192
  FORMAT = '# -*- %s -*-'
193
193
  SEPARATOR = ';'
194
194
  OPERATOR = ':'
@@ -216,7 +216,7 @@ module RuboCop
216
216
  #
217
217
  # comment.encoding # => 'ascii-8bit'
218
218
  class VimComment < EditorComment
219
- REGEXP = /#\s*vim:\s*(.+)/.freeze
219
+ REGEXP = /#\s*vim:\s*(?<token>.+)/.freeze
220
220
  FORMAT = '# vim: %s'
221
221
  SEPARATOR = ', '
222
222
  OPERATOR = '='
@@ -259,9 +259,11 @@ module RuboCop
259
259
  # comment2.frozen_string_literal # => nil
260
260
  # comment2.encoding # => 'utf-8'
261
261
  class SimpleComment < MagicComment
262
+ FSTRING_LITERAL_COMMENT = 'frozen_string_literal:\s*(true|false)'
263
+
262
264
  # Match `encoding` or `coding`
263
265
  def encoding
264
- extract(/\A\s*\#.*\b#{KEYWORDS[:encoding]}: (#{TOKEN})/io)
266
+ extract(/\A\s*\#\s*(#{FSTRING_LITERAL_COMMENT})?\s*#{KEYWORDS[:encoding]}: (#{TOKEN})/io)
265
267
  end
266
268
 
267
269
  # Rewrite the comment without a given token type
@@ -283,15 +285,15 @@ module RuboCop
283
285
  # Case-insensitive and dashes/underscores are acceptable.
284
286
  # @see https://github.com/ruby/ruby/blob/78b95b4/parse.y#L7134-L7138
285
287
  def extract_frozen_string_literal
286
- extract(/\A\s*#\s*#{KEYWORDS[:frozen_string_literal]}:\s*(#{TOKEN})\s*\z/io)
288
+ extract(/\A\s*#\s*#{KEYWORDS[:frozen_string_literal]}:\s*#{TOKEN}\s*\z/io)
287
289
  end
288
290
 
289
291
  def extract_shareable_constant_value
290
- extract(/\A\s*#\s*#{KEYWORDS[:shareable_constant_value]}:\s*(#{TOKEN})\s*\z/io)
292
+ extract(/\A\s*#\s*#{KEYWORDS[:shareable_constant_value]}:\s*#{TOKEN}\s*\z/io)
291
293
  end
292
294
 
293
295
  def extract_typed
294
- extract(/\A\s*#\s*#{KEYWORDS[:typed]}:\s*(#{TOKEN})\s*\z/io)
296
+ extract(/\A\s*#\s*#{KEYWORDS[:typed]}:\s*#{TOKEN}\s*\z/io)
295
297
  end
296
298
  end
297
299
  end
@@ -11,6 +11,8 @@ RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLe
11
11
  # Make sure to expand all symlinks in the path first. Otherwise we may
12
12
  # get mismatched pathnames when loading config files later on.
13
13
  tmpdir = File.realpath(tmpdir)
14
+ # Make upwards search for .rubocop.yml files stop at this directory.
15
+ RuboCop::FileFinder.root_level = tmpdir
14
16
 
15
17
  virtual_home = File.expand_path(File.join(tmpdir, 'home'))
16
18
  Dir.mkdir(virtual_home)
@@ -21,9 +23,6 @@ RSpec.shared_context 'isolated environment' do # rubocop:disable Metrics/BlockLe
21
23
  root = example.metadata[:root]
22
24
  working_dir = root ? File.join(base_dir, 'work', root) : File.join(base_dir, 'work')
23
25
 
24
- # Make upwards search for .rubocop.yml files stop at this directory.
25
- RuboCop::FileFinder.root_level = working_dir
26
-
27
26
  begin
28
27
  FileUtils.mkdir_p(working_dir)
29
28
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.56.2'
6
+ STRING = '1.56.4'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.56.2
4
+ version: 1.56.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-08-29 00:00:00.000000000 Z
13
+ date: 2023-09-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: base64