rubocop 1.56.0 → 1.57.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +12 -6
  4. data/lib/rubocop/cli.rb +1 -1
  5. data/lib/rubocop/cop/autocorrect_logic.rb +3 -1
  6. data/lib/rubocop/cop/bundler/duplicated_group.rb +54 -8
  7. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -4
  8. data/lib/rubocop/cop/internal_affairs/example_description.rb +42 -22
  9. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +11 -2
  10. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +2 -0
  11. data/lib/rubocop/cop/layout/dot_position.rb +1 -5
  12. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +41 -8
  13. data/lib/rubocop/cop/layout/heredoc_indentation.rb +3 -0
  14. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  15. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  16. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +17 -9
  17. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  18. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +13 -2
  19. data/lib/rubocop/cop/layout/redundant_line_break.rb +5 -3
  20. data/lib/rubocop/cop/layout/space_after_not.rb +1 -1
  21. data/lib/rubocop/cop/layout/space_inside_parens.rb +1 -1
  22. data/lib/rubocop/cop/lint/debugger.rb +10 -1
  23. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  24. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  25. data/lib/rubocop/cop/lint/mixed_case_range.rb +1 -1
  26. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -7
  27. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -0
  28. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +16 -4
  29. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +11 -4
  30. data/lib/rubocop/cop/lint/to_enum_arguments.rb +5 -3
  31. data/lib/rubocop/cop/lint/useless_assignment.rb +38 -12
  32. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  33. data/lib/rubocop/cop/metrics/class_length.rb +2 -2
  34. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  35. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +2 -2
  36. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -2
  37. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +5 -7
  38. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  39. data/lib/rubocop/cop/style/arguments_forwarding.rb +22 -11
  40. data/lib/rubocop/cop/style/array_intersect.rb +13 -5
  41. data/lib/rubocop/cop/style/class_equality_comparison.rb +5 -0
  42. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  43. data/lib/rubocop/cop/style/combinable_loops.rb +3 -2
  44. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  45. data/lib/rubocop/cop/style/empty_case_condition.rb +6 -1
  46. data/lib/rubocop/cop/style/for.rb +1 -1
  47. data/lib/rubocop/cop/style/format_string.rb +24 -3
  48. data/lib/rubocop/cop/style/guard_clause.rb +26 -0
  49. data/lib/rubocop/cop/style/identical_conditional_branches.rb +17 -3
  50. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -1
  51. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  52. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -1
  53. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -11
  54. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  55. data/lib/rubocop/cop/style/operator_method_call.rb +6 -0
  56. data/lib/rubocop/cop/style/redundant_begin.rb +9 -1
  57. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -9
  58. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +69 -6
  59. data/lib/rubocop/cop/style/redundant_filter_chain.rb +18 -2
  60. data/lib/rubocop/cop/style/redundant_parentheses.rb +24 -6
  61. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +5 -0
  62. data/lib/rubocop/cop/style/return_nil.rb +6 -2
  63. data/lib/rubocop/cop/style/single_line_do_end_block.rb +65 -0
  64. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  65. data/lib/rubocop/cop/style/symbol_array.rb +12 -11
  66. data/lib/rubocop/cop/style/yoda_expression.rb +8 -7
  67. data/lib/rubocop/cop/utils/regexp_ranges.rb +26 -13
  68. data/lib/rubocop/file_finder.rb +4 -7
  69. data/lib/rubocop/lsp/routes.rb +4 -1
  70. data/lib/rubocop/magic_comment.rb +12 -10
  71. data/lib/rubocop/rspec/shared_contexts.rb +2 -3
  72. data/lib/rubocop/server/cache.rb +1 -0
  73. data/lib/rubocop/target_ruby.rb +9 -5
  74. data/lib/rubocop/version.rb +1 -1
  75. data/lib/rubocop.rb +1 -0
  76. metadata +13 -12
@@ -4,13 +4,25 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # Enforces the use of a single string formatting utility.
7
- # Valid options include Kernel#format, Kernel#sprintf and String#%.
7
+ # Valid options include `Kernel#format`, `Kernel#sprintf`, and `String#%`.
8
8
  #
9
- # The detection of String#% cannot be implemented in a reliable
9
+ # The detection of `String#%` cannot be implemented in a reliable
10
10
  # manner for all cases, so only two scenarios are considered -
11
11
  # if the first argument is a string literal and if the second
12
12
  # argument is an array literal.
13
13
  #
14
+ # Autocorrection will be applied when using argument is a literal or known built-in conversion
15
+ # methods such as `to_d`, `to_f`, `to_h`, `to_i`, `to_r`, `to_s`, and `to_sym` on variables,
16
+ # provided that their return value is not an array. For example, when using `to_s`,
17
+ # `'%s' % [1, 2, 3].to_s` can be autocorrected without any incompatibility:
18
+ #
19
+ # [source,ruby]
20
+ # ----
21
+ # '%s' % [1, 2, 3] #=> '1'
22
+ # format('%s', [1, 2, 3]) #=> '[1, 2, 3]'
23
+ # '%s' % [1, 2, 3].to_s #=> '[1, 2, 3]'
24
+ # ----
25
+ #
14
26
  # @example EnforcedStyle: format (default)
15
27
  # # bad
16
28
  # puts sprintf('%10s', 'hoge')
@@ -42,6 +54,9 @@ module RuboCop
42
54
  MSG = 'Favor `%<prefer>s` over `%<current>s`.'
43
55
  RESTRICT_ON_SEND = %i[format sprintf %].freeze
44
56
 
57
+ # Known conversion methods whose return value is not an array.
58
+ AUTOCORRECTABLE_METHODS = %i[to_d to_f to_h to_i to_r to_s to_sym].freeze
59
+
45
60
  # @!method formatter(node)
46
61
  def_node_matcher :formatter, <<~PATTERN
47
62
  {
@@ -53,7 +68,7 @@ module RuboCop
53
68
 
54
69
  # @!method variable_argument?(node)
55
70
  def_node_matcher :variable_argument?, <<~PATTERN
56
- (send {str dstr} :% {send_type? lvar_type?})
71
+ (send {str dstr} :% #autocorrectable?)
57
72
  PATTERN
58
73
 
59
74
  def on_send(node)
@@ -70,6 +85,12 @@ module RuboCop
70
85
 
71
86
  private
72
87
 
88
+ def autocorrectable?(node)
89
+ return true if node.lvar_type?
90
+
91
+ node.send_type? && !AUTOCORRECTABLE_METHODS.include?(node.method_name)
92
+ end
93
+
73
94
  def message(detected_style)
74
95
  format(MSG, prefer: method_name(style), current: method_name(detected_style))
75
96
  end
@@ -55,6 +55,25 @@ module RuboCop
55
55
  # foo || raise('exception') if something
56
56
  # ok
57
57
  #
58
+ # # bad
59
+ # define_method(:test) do
60
+ # if something
61
+ # work
62
+ # end
63
+ # end
64
+ #
65
+ # # good
66
+ # define_method(:test) do
67
+ # return unless something
68
+ #
69
+ # work
70
+ # end
71
+ #
72
+ # # also good
73
+ # define_method(:test) do
74
+ # work if something
75
+ # end
76
+ #
58
77
  # @example AllowConsecutiveConditionals: false (default)
59
78
  # # bad
60
79
  # def test
@@ -110,6 +129,13 @@ module RuboCop
110
129
  end
111
130
  alias on_defs on_def
112
131
 
132
+ def on_block(node)
133
+ return unless node.method?(:define_method) || node.method?(:define_singleton_method)
134
+
135
+ on_def(node)
136
+ end
137
+ alias on_numblock on_block
138
+
113
139
  def on_if(node)
114
140
  return if accepted_form?(node)
115
141
 
@@ -136,7 +136,7 @@ module RuboCop
136
136
 
137
137
  private
138
138
 
139
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
139
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
140
140
  def check_branches(node, branches)
141
141
  # return if any branch is empty. An empty branch can be an `if`
142
142
  # without an `else` or a branch that contains only comments.
@@ -149,9 +149,15 @@ module RuboCop
149
149
  branches.any? { |branch| single_child_branch?(branch) }
150
150
 
151
151
  heads = branches.map { |branch| head(branch) }
152
- check_expressions(node, heads, :before_condition) if duplicated_expressions?(node, heads)
152
+
153
+ return unless duplicated_expressions?(node, heads)
154
+
155
+ condition_variable = assignable_condition_value(node)
156
+ return if heads.first.assignment? && condition_variable == heads.first.name.to_s
157
+
158
+ check_expressions(node, heads, :before_condition)
153
159
  end
154
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
160
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
155
161
 
156
162
  def duplicated_expressions?(node, expressions)
157
163
  unique_expressions = expressions.uniq
@@ -164,6 +170,14 @@ module RuboCop
164
170
  node.condition.child_nodes.none? { |n| n.source == lhs.source if n.variable? }
165
171
  end
166
172
 
173
+ def assignable_condition_value(node)
174
+ if node.condition.call_type?
175
+ (receiver = node.condition.receiver) ? receiver.source : node.condition.source
176
+ elsif node.condition.variable?
177
+ node.condition.source
178
+ end
179
+ end
180
+
167
181
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
168
182
  def check_expressions(node, expressions, insert_position)
169
183
  return if expressions.any?(&:nil?)
@@ -146,7 +146,9 @@ module RuboCop
146
146
  end
147
147
 
148
148
  def call_in_match_pattern?(node)
149
- node.parent&.match_pattern_type?
149
+ return false unless (parent = node.parent)
150
+
151
+ parent.match_pattern_type? || parent.match_pattern_p_type?
150
152
  end
151
153
 
152
154
  def hash_literal_in_arguments?(node)
@@ -28,7 +28,7 @@ module RuboCop
28
28
  MSG = 'Avoid multi-line chains of blocks.'
29
29
 
30
30
  def on_block(node)
31
- node.send_node.each_node(:send) do |send_node|
31
+ node.send_node.each_node(:send, :csend) do |send_node|
32
32
  receiver = send_node.receiver
33
33
 
34
34
  next unless (receiver&.block_type? || receiver&.numblock_type?) && receiver&.multiline?
@@ -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)
@@ -27,24 +27,16 @@ module RuboCop
27
27
 
28
28
  node.each_descendant(:if).select(&:ternary?).each do |nested_ternary|
29
29
  add_offense(nested_ternary) do |corrector|
30
- if_node = if_node(nested_ternary)
31
- next if part_of_ignored_node?(if_node)
30
+ next if part_of_ignored_node?(node)
32
31
 
33
- autocorrect(corrector, if_node)
34
- ignore_node(if_node)
32
+ autocorrect(corrector, node)
33
+ ignore_node(node)
35
34
  end
36
35
  end
37
36
  end
38
37
 
39
38
  private
40
39
 
41
- def if_node(node)
42
- node = node.parent
43
- return node if node.if_type?
44
-
45
- if_node(node)
46
- end
47
-
48
40
  def autocorrect(corrector, if_node)
49
41
  replace_loc_and_whitespace(corrector, if_node.loc.question, "\n")
50
42
  replace_loc_and_whitespace(corrector, if_node.loc.colon, "\nelse\n")
@@ -45,7 +45,7 @@ module RuboCop
45
45
  MSG = 'Avoid using `OpenStruct`; use `Struct`, `Hash`, a class or test doubles instead.'
46
46
 
47
47
  # @!method uses_open_struct?(node)
48
- def_node_matcher :uses_open_struct?, <<-PATTERN
48
+ def_node_matcher :uses_open_struct?, <<~PATTERN
49
49
  (const {nil? (cbase)} :OpenStruct)
50
50
  PATTERN
51
51
 
@@ -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
 
@@ -114,7 +114,7 @@ module RuboCop
114
114
  if node.parent&.assignment?
115
115
  replace_begin_with_statement(corrector, offense_range, node)
116
116
  else
117
- corrector.remove(offense_range)
117
+ remove_begin(corrector, offense_range, node)
118
118
  end
119
119
 
120
120
  if use_modifier_form_after_multiline_begin_block?(node)
@@ -136,6 +136,14 @@ module RuboCop
136
136
  restore_removed_comments(corrector, offense_range, node, first_child)
137
137
  end
138
138
 
139
+ def remove_begin(corrector, offense_range, node)
140
+ if node.parent.respond_to?(:endless?) && node.parent.endless?
141
+ offense_range = range_with_surrounding_space(offense_range, newlines: true)
142
+ end
143
+
144
+ corrector.remove(offense_range)
145
+ end
146
+
139
147
  # Restore comments that occur between "begin" and "first_child".
140
148
  # These comments will be moved to above the assignment line.
141
149
  def restore_removed_comments(corrector, offense_range, node, first_child)
@@ -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
@@ -13,25 +13,50 @@ module RuboCop
13
13
  # # good
14
14
  # do_something(foo: bar, baz: qux)
15
15
  #
16
+ # # bad
17
+ # do_something(**{foo: bar, baz: qux}.merge(options))
18
+ #
19
+ # # good
20
+ # do_something(foo: bar, baz: qux, **options)
21
+ #
16
22
  class RedundantDoubleSplatHashBraces < Base
17
23
  extend AutoCorrector
18
24
 
19
25
  MSG = 'Remove the redundant double splat and braces, use keyword arguments directly.'
26
+ MERGE_METHODS = %i[merge merge!].freeze
20
27
 
28
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
21
29
  def on_hash(node)
22
- return if node.pairs.empty? || node.pairs.any?(&:hash_rocket?)
30
+ return if !node.braces? || node.pairs.empty? || node.pairs.any?(&:hash_rocket?)
23
31
  return unless (parent = node.parent)
24
- return unless parent.kwsplat_type?
32
+ return unless (kwsplat = node.each_ancestor(:kwsplat).first)
33
+ return if parent.call_type? && !merge_method?(parent)
25
34
 
26
- add_offense(parent) do |corrector|
27
- corrector.remove(parent.loc.operator)
28
- corrector.remove(opening_brace(node))
29
- corrector.remove(closing_brace(node))
35
+ add_offense(kwsplat) do |corrector|
36
+ autocorrect(corrector, node, kwsplat)
30
37
  end
31
38
  end
39
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
32
40
 
33
41
  private
34
42
 
43
+ def autocorrect(corrector, node, kwsplat)
44
+ corrector.remove(kwsplat.loc.operator)
45
+ corrector.remove(opening_brace(node))
46
+ corrector.remove(closing_brace(node))
47
+
48
+ merge_methods = select_merge_method_nodes(kwsplat)
49
+ return if merge_methods.empty?
50
+
51
+ autocorrect_merge_methods(corrector, merge_methods, kwsplat)
52
+ end
53
+
54
+ def select_merge_method_nodes(kwsplat)
55
+ extract_send_methods(kwsplat).select do |node|
56
+ merge_method?(node)
57
+ end
58
+ end
59
+
35
60
  def opening_brace(node)
36
61
  node.loc.begin.join(node.children.first.source_range.begin)
37
62
  end
@@ -39,6 +64,44 @@ module RuboCop
39
64
  def closing_brace(node)
40
65
  node.children.last.source_range.end.join(node.loc.end)
41
66
  end
67
+
68
+ def autocorrect_merge_methods(corrector, merge_methods, kwsplat)
69
+ range = range_of_merge_methods(merge_methods)
70
+
71
+ new_kwsplat_arguments = extract_send_methods(kwsplat).map do |descendant|
72
+ convert_to_new_arguments(descendant)
73
+ end
74
+ new_source = new_kwsplat_arguments.compact.reverse.unshift('').join(', ')
75
+
76
+ corrector.replace(range, new_source)
77
+ end
78
+
79
+ def range_of_merge_methods(merge_methods)
80
+ begin_merge_method = merge_methods.last
81
+ end_merge_method = merge_methods.first
82
+
83
+ begin_merge_method.loc.dot.begin.join(end_merge_method.source_range.end)
84
+ end
85
+
86
+ def extract_send_methods(kwsplat)
87
+ @extract_send_methods ||= kwsplat.each_descendant(:send, :csend)
88
+ end
89
+
90
+ def convert_to_new_arguments(node)
91
+ return unless merge_method?(node)
92
+
93
+ node.arguments.map do |arg|
94
+ if arg.hash_type?
95
+ arg.source
96
+ else
97
+ "**#{arg.source}"
98
+ end
99
+ end
100
+ end
101
+
102
+ def merge_method?(node)
103
+ MERGE_METHODS.include?(node.method_name)
104
+ end
42
105
  end
43
106
  end
44
107
  end
@@ -6,6 +6,12 @@ module RuboCop
6
6
  # Identifies usages of `any?`, `empty?` or `none?` predicate methods
7
7
  # chained to `select`/`filter`/`find_all` and change them to use predicate method instead.
8
8
  #
9
+ # @safety
10
+ # This cop's autocorrection is unsafe because `array.select.any?` evaluates all elements
11
+ # through the `select` method, while `array.any?` uses short-circuit evaluation.
12
+ # In other words, `array.select.any?` guarantees the evaluation of every element,
13
+ # but `array.any?` does not necessarily evaluate all of them.
14
+ #
9
15
  # @example
10
16
  # # bad
11
17
  # arr.select { |x| x > 1 }.any?
@@ -28,6 +34,9 @@ module RuboCop
28
34
  # # good
29
35
  # arr.select { |x| x > 1 }.many?
30
36
  #
37
+ # # good
38
+ # arr.select { |x| x > 1 }.present?
39
+ #
31
40
  # @example AllCops:ActiveSupportExtensionsEnabled: true
32
41
  # # bad
33
42
  # arr.select { |x| x > 1 }.many?
@@ -35,12 +44,18 @@ module RuboCop
35
44
  # # good
36
45
  # arr.many? { |x| x > 1 }
37
46
  #
47
+ # # bad
48
+ # arr.select { |x| x > 1 }.present?
49
+ #
50
+ # # good
51
+ # arr.any? { |x| x > 1 }
52
+ #
38
53
  class RedundantFilterChain < Base
39
54
  extend AutoCorrector
40
55
 
41
56
  MSG = 'Use `%<prefer>s` instead of `%<first_method>s.%<second_method>s`.'
42
57
 
43
- RAILS_METHODS = %i[many?].freeze
58
+ RAILS_METHODS = %i[many? present?].freeze
44
59
  RESTRICT_ON_SEND = (%i[any? empty? none? one?] + RAILS_METHODS).freeze
45
60
 
46
61
  # @!method select_predicate?(node)
@@ -58,7 +73,8 @@ module RuboCop
58
73
  empty?: :none?,
59
74
  none?: :none?,
60
75
  one?: :one?,
61
- many?: :many?
76
+ many?: :many?,
77
+ present?: :any?
62
78
  }.freeze
63
79
  private_constant :REPLACEMENT_METHODS
64
80
 
@@ -98,7 +98,9 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def like_method_argument_parentheses?(node)
101
- node.send_type? && node.arguments.one? && !node.parenthesized? &&
101
+ return false if !node.send_type? && !node.super_type? && !node.yield_type?
102
+
103
+ node.arguments.one? && !node.parenthesized? &&
102
104
  !node.arithmetic_operation? && node.first_argument.begin_type?
103
105
  end
104
106
 
@@ -124,16 +126,32 @@ module RuboCop
124
126
 
125
127
  def check(begin_node)
126
128
  node = begin_node.children.first
127
- return offense(begin_node, 'a keyword') if keyword_with_redundant_parentheses?(node)
128
- return offense(begin_node, 'a literal') if disallowed_literal?(begin_node, node)
129
- return offense(begin_node, 'a variable') if node.variable?
130
- return offense(begin_node, 'a constant') if node.const_type?
131
129
 
132
- return offense(begin_node, 'an interpolated expression') if interpolation?(begin_node)
130
+ if (message = find_offense_message(begin_node, node))
131
+ return offense(begin_node, message)
132
+ end
133
133
 
134
134
  check_send(begin_node, node) if node.call_type?
135
135
  end
136
136
 
137
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
138
+ def find_offense_message(begin_node, node)
139
+ return 'a keyword' if keyword_with_redundant_parentheses?(node)
140
+ return 'a literal' if disallowed_literal?(begin_node, node)
141
+ return 'a variable' if node.variable?
142
+ return 'a constant' if node.const_type?
143
+ return 'an interpolated expression' if interpolation?(begin_node)
144
+
145
+ return if begin_node.chained? || !begin_node.parent.nil?
146
+
147
+ if node.and_type? || node.or_type?
148
+ 'a logical expression'
149
+ elsif node.respond_to?(:comparison_method?) && node.comparison_method?
150
+ 'a comparison expression'
151
+ end
152
+ end
153
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
154
+
137
155
  # @!method interpolation?(node)
138
156
  def_node_matcher :interpolation?, '[^begin ^^dstr]'
139
157
 
@@ -75,6 +75,11 @@ module RuboCop
75
75
  add_offense(offense_branch) do |corrector|
76
76
  assignment_value = opposite_branch ? opposite_branch.source : 'nil'
77
77
  replacement = "#{assignment_value} #{keyword} #{if_node.condition.source}"
78
+ if opposite_branch.respond_to?(:heredoc?) && opposite_branch.heredoc?
79
+ replacement += opposite_branch.loc.heredoc_end.with(
80
+ begin_pos: opposite_branch.source_range.end_pos
81
+ ).source
82
+ end
78
83
 
79
84
  corrector.replace(if_node, replacement)
80
85
  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
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for single-line `do`...`end` block.
7
+ #
8
+ # In practice a single line `do`...`end` is autocorrected when `EnforcedStyle: semantic`
9
+ # in `Style/BlockDelimiters`. The autocorrection maintains the `do` ... `end` syntax to
10
+ # preserve semantics and does not change it to `{`...`}` block.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # foo do |arg| bar(arg) end
16
+ #
17
+ # # good
18
+ # foo do |arg|
19
+ # bar(arg)
20
+ # end
21
+ #
22
+ # # bad
23
+ # ->(arg) do bar(arg) end
24
+ #
25
+ # # good
26
+ # ->(arg) { bar(arg) }
27
+ #
28
+ class SingleLineDoEndBlock < Base
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Prefer multiline `do`...`end` block.'
32
+
33
+ def on_block(node)
34
+ return if !node.single_line? || node.braces?
35
+
36
+ add_offense(node) do |corrector|
37
+ corrector.insert_after(do_line(node), "\n")
38
+
39
+ node_body = node.body
40
+
41
+ if node_body.respond_to?(:heredoc?) && node_body.heredoc?
42
+ corrector.remove(node.loc.end)
43
+ corrector.insert_after(node_body.loc.heredoc_end, "\nend")
44
+ else
45
+ corrector.insert_after(node_body, "\n")
46
+ end
47
+ end
48
+ end
49
+ alias on_numblock on_block
50
+
51
+ private
52
+
53
+ def do_line(node)
54
+ if node.numblock_type? || node.arguments.children.empty? || node.send_node.lambda_literal?
55
+ node.loc.begin
56
+ else
57
+ node.arguments
58
+ end
59
+ end
60
+
61
+ def x(corrector, node); end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -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
@@ -69,6 +77,8 @@ module RuboCop
69
77
 
70
78
  def complex_content?(node)
71
79
  node.children.any? do |sym|
80
+ return false if DELIMITERS.include?(sym.source)
81
+
72
82
  content = *sym
73
83
  content = content.map { |c| c.is_a?(AST::Node) ? c.source : c }.join
74
84
  content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s\(\)]*\))/, '')
@@ -107,15 +117,6 @@ module RuboCop
107
117
  end
108
118
 
109
119
  def symbol_without_quote?(string)
110
- special_gvars = %w[
111
- $! $" $$ $& $' $* $+ $, $/ $; $: $. $< $= $> $? $@ $\\ $_ $` $~ $0
112
- $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w
113
- ]
114
- redefinable_operators = %w(
115
- | ^ & <=> == === =~ > >= < <= << >>
116
- + - * / % ** ~ +@ -@ [] []= ` ! != !~
117
- )
118
-
119
120
  # method name
120
121
  /\A[a-zA-Z_]\w*[!?]?\z/.match?(string) ||
121
122
  # instance / class variable
@@ -123,8 +124,8 @@ module RuboCop
123
124
  # global variable
124
125
  /\A\$[1-9]\d*\z/.match?(string) ||
125
126
  /\A\$[a-zA-Z_]\w*\z/.match?(string) ||
126
- special_gvars.include?(string) ||
127
- redefinable_operators.include?(string)
127
+ SPECIAL_GVARS.include?(string) ||
128
+ REDEFINABLE_OPERATORS.include?(string)
128
129
  end
129
130
  end
130
131
  end