rubocop 1.53.1 → 1.57.2

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/config/default.yml +34 -8
  4. data/config/obsoletion.yml +5 -0
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +10 -5
  6. data/lib/rubocop/cli.rb +1 -1
  7. data/lib/rubocop/config_finder.rb +2 -2
  8. data/lib/rubocop/config_obsoletion/parameter_rule.rb +9 -1
  9. data/lib/rubocop/cop/autocorrect_logic.rb +3 -1
  10. data/lib/rubocop/cop/base.rb +1 -1
  11. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -0
  12. data/lib/rubocop/cop/bundler/duplicated_group.rb +127 -0
  13. data/lib/rubocop/cop/bundler/ordered_gems.rb +9 -1
  14. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -4
  15. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +9 -1
  16. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  17. data/lib/rubocop/cop/internal_affairs/example_description.rb +42 -21
  18. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +3 -1
  19. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +11 -2
  20. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +2 -0
  21. data/lib/rubocop/cop/layout/dot_position.rb +1 -5
  22. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +42 -9
  23. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +26 -3
  24. data/lib/rubocop/cop/layout/end_alignment.rb +7 -1
  25. data/lib/rubocop/cop/layout/heredoc_indentation.rb +3 -0
  26. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  27. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  28. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +17 -9
  29. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  30. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +2 -0
  31. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  32. data/lib/rubocop/cop/layout/redundant_line_break.rb +13 -3
  33. data/lib/rubocop/cop/layout/space_after_comma.rb +9 -1
  34. data/lib/rubocop/cop/layout/space_after_not.rb +1 -1
  35. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +2 -2
  36. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -1
  37. data/lib/rubocop/cop/layout/space_inside_parens.rb +1 -1
  38. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +5 -0
  39. data/lib/rubocop/cop/lint/debugger.rb +17 -4
  40. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  42. data/lib/rubocop/cop/lint/mixed_case_range.rb +3 -1
  43. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -7
  44. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +10 -0
  45. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -0
  46. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +20 -4
  47. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +11 -4
  48. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +7 -1
  49. data/lib/rubocop/cop/lint/struct_new_override.rb +12 -12
  50. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  51. data/lib/rubocop/cop/lint/to_enum_arguments.rb +5 -3
  52. data/lib/rubocop/cop/lint/useless_assignment.rb +38 -12
  53. data/lib/rubocop/cop/lint/void.rb +32 -20
  54. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  55. data/lib/rubocop/cop/metrics/class_length.rb +8 -3
  56. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  57. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +32 -4
  58. data/lib/rubocop/cop/mixin/comments_help.rb +16 -12
  59. data/lib/rubocop/cop/mixin/def_node.rb +1 -1
  60. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +14 -11
  61. data/lib/rubocop/cop/mixin/heredoc.rb +6 -2
  62. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -2
  63. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +5 -7
  64. data/lib/rubocop/cop/mixin/string_help.rb +4 -2
  65. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  66. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  67. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +3 -1
  68. data/lib/rubocop/cop/style/alias.rb +9 -8
  69. data/lib/rubocop/cop/style/arguments_forwarding.rb +280 -63
  70. data/lib/rubocop/cop/style/array_intersect.rb +13 -5
  71. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  72. data/lib/rubocop/cop/style/class_equality_comparison.rb +7 -0
  73. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  74. data/lib/rubocop/cop/style/combinable_loops.rb +4 -2
  75. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  76. data/lib/rubocop/cop/style/empty_case_condition.rb +6 -1
  77. data/lib/rubocop/cop/style/for.rb +1 -1
  78. data/lib/rubocop/cop/style/format_string.rb +24 -3
  79. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +3 -1
  80. data/lib/rubocop/cop/style/guard_clause.rb +26 -0
  81. data/lib/rubocop/cop/style/hash_conversion.rb +10 -0
  82. data/lib/rubocop/cop/style/identical_conditional_branches.rb +25 -3
  83. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  84. data/lib/rubocop/cop/style/lambda.rb +3 -3
  85. data/lib/rubocop/cop/style/lambda_call.rb +5 -0
  86. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +8 -1
  87. data/lib/rubocop/cop/style/mixin_grouping.rb +1 -1
  88. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  89. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -1
  90. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -11
  91. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  92. data/lib/rubocop/cop/style/operator_method_call.rb +6 -0
  93. data/lib/rubocop/cop/style/redundant_argument.rb +6 -1
  94. data/lib/rubocop/cop/style/redundant_begin.rb +9 -1
  95. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -9
  96. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +93 -5
  97. data/lib/rubocop/cop/style/redundant_exception.rb +32 -12
  98. data/lib/rubocop/cop/style/redundant_filter_chain.rb +22 -5
  99. data/lib/rubocop/cop/style/redundant_parentheses.rb +41 -15
  100. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -1
  101. data/lib/rubocop/cop/style/redundant_return.rb +7 -2
  102. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +5 -0
  103. data/lib/rubocop/cop/style/return_nil.rb +6 -2
  104. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +23 -9
  105. data/lib/rubocop/cop/style/semicolon.rb +0 -3
  106. data/lib/rubocop/cop/style/single_argument_dig.rb +2 -1
  107. data/lib/rubocop/cop/style/single_line_do_end_block.rb +67 -0
  108. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  109. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +30 -5
  110. data/lib/rubocop/cop/style/symbol_array.rb +35 -15
  111. data/lib/rubocop/cop/style/yoda_condition.rb +4 -2
  112. data/lib/rubocop/cop/style/yoda_expression.rb +8 -7
  113. data/lib/rubocop/cop/utils/regexp_ranges.rb +26 -13
  114. data/lib/rubocop/cop/variable_force/assignment.rb +14 -5
  115. data/lib/rubocop/file_finder.rb +4 -7
  116. data/lib/rubocop/formatter/html_formatter.rb +4 -2
  117. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  118. data/lib/rubocop/lsp/routes.rb +41 -18
  119. data/lib/rubocop/lsp/runtime.rb +22 -2
  120. data/lib/rubocop/lsp/server.rb +10 -4
  121. data/lib/rubocop/magic_comment.rb +12 -10
  122. data/lib/rubocop/result_cache.rb +4 -0
  123. data/lib/rubocop/rspec/shared_contexts.rb +2 -3
  124. data/lib/rubocop/runner.rb +5 -3
  125. data/lib/rubocop/server/cache.rb +1 -0
  126. data/lib/rubocop/server/client_command/exec.rb +1 -1
  127. data/lib/rubocop/string_interpreter.rb +3 -3
  128. data/lib/rubocop/target_finder.rb +7 -3
  129. data/lib/rubocop/target_ruby.rb +9 -5
  130. data/lib/rubocop/version.rb +1 -1
  131. data/lib/rubocop.rb +2 -0
  132. metadata +16 -14
@@ -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
@@ -142,7 +142,9 @@ module RuboCop
142
142
  end
143
143
 
144
144
  next_token = processed_source.tokens[token_number]
145
- token = next_token if Encoding::ENCODING_PATTERN.match?(next_token&.text)
145
+ if next_token&.text&.valid_encoding? && Encoding::ENCODING_PATTERN.match?(next_token.text)
146
+ token = next_token
147
+ end
146
148
 
147
149
  token
148
150
  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
 
@@ -10,6 +10,16 @@ module RuboCop
10
10
  # `Hash[*ary]` can be replaced with `ary.each_slice(2).to_h` but it will be complicated.
11
11
  # So, `AllowSplatArgument` option is true by default to allow splat argument for simple code.
12
12
  #
13
+ # @safety
14
+ # This cop's autocorrection is unsafe because `ArgumentError` occurs
15
+ # if the number of elements is odd:
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # Hash[[[1, 2], [3]]] #=> {1=>2, 3=>nil}
20
+ # [[1, 2], [5]].to_h #=> wrong array length at 1 (expected 2, was 1) (ArgumentError)
21
+ # ----
22
+ #
13
23
  # @example
14
24
  # # bad
15
25
  # Hash[ary]
@@ -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,23 @@ 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
+
157
+ head = heads.first
158
+ if head.assignment?
159
+ # The `send` node is used instead of the `indexasgn` node, so `name` cannot be used.
160
+ # https://github.com/rubocop/rubocop-ast/blob/v1.29.0/lib/rubocop/ast/node/indexasgn_node.rb
161
+ assigned_value = head.send_type? ? head.receiver.source : head.name.to_s
162
+
163
+ return if condition_variable == assigned_value
164
+ end
165
+
166
+ check_expressions(node, heads, :before_condition)
153
167
  end
154
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
168
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
155
169
 
156
170
  def duplicated_expressions?(node, expressions)
157
171
  unique_expressions = expressions.uniq
@@ -164,6 +178,14 @@ module RuboCop
164
178
  node.condition.child_nodes.none? { |n| n.source == lhs.source if n.variable? }
165
179
  end
166
180
 
181
+ def assignable_condition_value(node)
182
+ if node.condition.call_type?
183
+ (receiver = node.condition.receiver) ? receiver.source : node.condition.source
184
+ elsif node.condition.variable?
185
+ node.condition.source
186
+ end
187
+ end
188
+
167
189
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
168
190
  def check_expressions(node, expressions, insert_position)
169
191
  return if expressions.any?(&:nil?)
@@ -47,7 +47,7 @@ module RuboCop
47
47
  def correct_elsif(node)
48
48
  <<~RUBY.chop
49
49
  if #{node.condition.source}
50
- #{node.if_branch.source}
50
+ #{node.if_branch&.source}
51
51
  #{build_else_branch(node.else_branch).chop}
52
52
  end
53
53
  RUBY
@@ -56,7 +56,7 @@ module RuboCop
56
56
  def build_else_branch(second_condition)
57
57
  result = <<~RUBY
58
58
  elsif #{second_condition.condition.source}
59
- #{second_condition.if_branch.source}
59
+ #{second_condition.if_branch&.source}
60
60
  RUBY
61
61
 
62
62
  if second_condition.else_branch
@@ -69,10 +69,10 @@ module RuboCop
69
69
  return unless offending_selector?(node, selector)
70
70
 
71
71
  add_offense(node.send_node.source_range, message: message(node, selector)) do |corrector|
72
- if node.send_node.source == 'lambda'
73
- autocorrect_method_to_literal(corrector, node)
74
- else
72
+ if node.send_node.lambda_literal?
75
73
  LambdaLiteralToMethodCorrector.new(node).call(corrector)
74
+ else
75
+ autocorrect_method_to_literal(corrector, node)
76
76
  end
77
77
  end
78
78
  end
@@ -20,6 +20,7 @@ module RuboCop
20
20
  # lambda.(x, y)
21
21
  class LambdaCall < Base
22
22
  include ConfigurableEnforcedStyle
23
+ include IgnoredNode
23
24
  extend AutoCorrector
24
25
 
25
26
  MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
@@ -33,8 +34,12 @@ module RuboCop
33
34
  current = node.source
34
35
 
35
36
  add_offense(node, message: format(MSG, prefer: prefer, current: current)) do |corrector|
37
+ next if part_of_ignored_node?(node)
38
+
36
39
  opposite_style_detected
37
40
  corrector.replace(node, prefer)
41
+
42
+ ignore_node(node)
38
43
  end
39
44
  else
40
45
  correct_style_detected
@@ -124,9 +124,10 @@ module RuboCop
124
124
  node.parent&.class_type? && node.parent&.single_line?
125
125
  end
126
126
 
127
- def call_with_ambiguous_arguments?(node)
127
+ def call_with_ambiguous_arguments?(node) # rubocop:disable Metrics/PerceivedComplexity
128
128
  call_with_braced_block?(node) ||
129
129
  call_as_argument_or_chain?(node) ||
130
+ call_in_match_pattern?(node) ||
130
131
  hash_literal_in_arguments?(node) ||
131
132
  node.descendants.any? do |n|
132
133
  n.forwarded_args_type? || ambiguous_literal?(n) || logical_operator?(n) ||
@@ -144,6 +145,12 @@ module RuboCop
144
145
  node.parent.csend_type? || node.parent.super_type? || node.parent.yield_type?)
145
146
  end
146
147
 
148
+ def call_in_match_pattern?(node)
149
+ return false unless (parent = node.parent)
150
+
151
+ parent.match_pattern_type? || parent.match_pattern_p_type?
152
+ end
153
+
147
154
  def hash_literal_in_arguments?(node)
148
155
  node.arguments.any? do |n|
149
156
  hash_literal?(n) ||
@@ -40,7 +40,7 @@ module RuboCop
40
40
  def on_class(node)
41
41
  begin_node = node.child_nodes.find(&:begin_type?) || node
42
42
  begin_node.each_child_node(:send).select(&:macro?).each do |macro|
43
- next unless MIXIN_METHODS.include?(macro.method_name)
43
+ next if !MIXIN_METHODS.include?(macro.method_name) || macro.arguments.empty?
44
44
 
45
45
  check(macro)
46
46
  end
@@ -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
 
@@ -35,6 +35,8 @@ module RuboCop
35
35
  # array.join('')
36
36
  # [1, 2, 3].join("")
37
37
  # array.sum(0)
38
+ # exit(true)
39
+ # exit!(false)
38
40
  # string.split(" ")
39
41
  # "first\nsecond".split(" ")
40
42
  # string.chomp("\n")
@@ -45,6 +47,8 @@ module RuboCop
45
47
  # array.join
46
48
  # [1, 2, 3].join
47
49
  # array.sum
50
+ # exit
51
+ # exit!
48
52
  # string.split
49
53
  # "first second".split
50
54
  # string.chomp
@@ -55,9 +59,10 @@ module RuboCop
55
59
  extend AutoCorrector
56
60
 
57
61
  MSG = 'Argument %<arg>s is redundant because it is implied by default.'
62
+ NO_RECEIVER_METHODS = %i[exit exit!].freeze
58
63
 
59
64
  def on_send(node)
60
- return if node.receiver.nil?
65
+ return if !NO_RECEIVER_METHODS.include?(node.method_name) && node.receiver.nil?
61
66
  return if node.arguments.count != 1
62
67
  return unless redundant_argument?(node)
63
68
 
@@ -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,71 @@ 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
30
  return if node.pairs.empty? || node.pairs.any?(&:hash_rocket?)
23
31
  return unless (parent = node.parent)
24
- return unless parent.kwsplat_type?
32
+ return unless parent.call_type? || parent.kwsplat_type?
33
+ return unless mergeable?(parent)
34
+ return unless (kwsplat = node.each_ancestor(:kwsplat).first)
35
+ return if allowed_double_splat_receiver?(kwsplat)
25
36
 
26
- add_offense(parent) do |corrector|
27
- corrector.remove(parent.loc.operator)
28
- corrector.remove(opening_brace(node))
29
- corrector.remove(closing_brace(node))
37
+ add_offense(kwsplat) do |corrector|
38
+ autocorrect(corrector, node, kwsplat)
30
39
  end
31
40
  end
41
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
32
42
 
33
43
  private
34
44
 
45
+ def allowed_double_splat_receiver?(kwsplat)
46
+ first_child = kwsplat.children.first
47
+ return true if first_child.block_type? || first_child.numblock_type?
48
+ return false unless first_child.call_type?
49
+
50
+ root_receiver = root_receiver(first_child)
51
+
52
+ !root_receiver&.hash_type?
53
+ end
54
+
55
+ def autocorrect(corrector, node, kwsplat)
56
+ corrector.remove(kwsplat.loc.operator)
57
+ corrector.remove(opening_brace(node))
58
+ corrector.remove(closing_brace(node))
59
+
60
+ merge_methods = select_merge_method_nodes(kwsplat)
61
+ return if merge_methods.empty?
62
+
63
+ autocorrect_merge_methods(corrector, merge_methods, kwsplat)
64
+ end
65
+
66
+ def root_receiver(node)
67
+ receiver = node.receiver
68
+ if receiver&.receiver
69
+ root_receiver(receiver)
70
+ else
71
+ receiver
72
+ end
73
+ end
74
+
75
+ def select_merge_method_nodes(kwsplat)
76
+ extract_send_methods(kwsplat).select do |node|
77
+ mergeable?(node)
78
+ end
79
+ end
80
+
35
81
  def opening_brace(node)
36
82
  node.loc.begin.join(node.children.first.source_range.begin)
37
83
  end
@@ -39,6 +85,48 @@ module RuboCop
39
85
  def closing_brace(node)
40
86
  node.children.last.source_range.end.join(node.loc.end)
41
87
  end
88
+
89
+ def autocorrect_merge_methods(corrector, merge_methods, kwsplat)
90
+ range = range_of_merge_methods(merge_methods)
91
+
92
+ new_kwsplat_arguments = extract_send_methods(kwsplat).map do |descendant|
93
+ convert_to_new_arguments(descendant)
94
+ end
95
+ new_source = new_kwsplat_arguments.compact.reverse.unshift('').join(', ')
96
+
97
+ corrector.replace(range, new_source)
98
+ end
99
+
100
+ def range_of_merge_methods(merge_methods)
101
+ begin_merge_method = merge_methods.last
102
+ end_merge_method = merge_methods.first
103
+
104
+ begin_merge_method.loc.dot.begin.join(end_merge_method.source_range.end)
105
+ end
106
+
107
+ def extract_send_methods(kwsplat)
108
+ kwsplat.each_descendant(:send, :csend)
109
+ end
110
+
111
+ def convert_to_new_arguments(node)
112
+ return unless mergeable?(node)
113
+
114
+ node.arguments.map do |arg|
115
+ if arg.hash_type?
116
+ arg.source
117
+ else
118
+ "**#{arg.source}"
119
+ end
120
+ end
121
+ end
122
+
123
+ def mergeable?(node)
124
+ return true unless node.call_type?
125
+ return false unless MERGE_METHODS.include?(node.method_name)
126
+ return true unless (parent = node.parent)
127
+
128
+ mergeable?(parent)
129
+ end
42
130
  end
43
131
  end
44
132
  end
@@ -5,17 +5,21 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for RuntimeError as the argument of raise/fail.
7
7
  #
8
- # It checks for code like this:
9
- #
10
8
  # @example
11
- # # Bad
9
+ # # bad
12
10
  # raise RuntimeError, 'message'
13
- #
14
- # # Bad
15
11
  # raise RuntimeError.new('message')
16
12
  #
17
- # # Good
13
+ # # good
18
14
  # raise 'message'
15
+ #
16
+ # # bad - message is not a string
17
+ # raise RuntimeError, Object.new
18
+ # raise RuntimeError.new(Object.new)
19
+ #
20
+ # # good
21
+ # raise Object.new.to_s
22
+ #
19
23
  class RedundantException < Base
20
24
  extend AutoCorrector
21
25
 
@@ -30,26 +34,42 @@ module RuboCop
30
34
  fix_exploded(node) || fix_compact(node)
31
35
  end
32
36
 
37
+ private
38
+
33
39
  def fix_exploded(node)
34
40
  exploded?(node) do |command, message|
35
41
  add_offense(node, message: MSG_1) do |corrector|
36
- if node.parenthesized?
37
- corrector.replace(node, "#{command}(#{message.source})")
38
- else
39
- corrector.replace(node, "#{command} #{message.source}")
40
- end
42
+ corrector.replace(node, replaced_exploded(node, command, message))
41
43
  end
42
44
  end
43
45
  end
44
46
 
47
+ def replaced_exploded(node, command, message)
48
+ arg = string_message?(message) ? message.source : "#{message.source}.to_s"
49
+ arg = node.parenthesized? ? "(#{arg})" : " #{arg}"
50
+ "#{command}#{arg}"
51
+ end
52
+
53
+ def string_message?(message)
54
+ message.str_type? || message.dstr_type? || message.xstr_type?
55
+ end
56
+
45
57
  def fix_compact(node)
46
58
  compact?(node) do |new_call, message|
47
59
  add_offense(node, message: MSG_2) do |corrector|
48
- corrector.replace(new_call, message.source)
60
+ corrector.replace(new_call, replaced_compact(message))
49
61
  end
50
62
  end
51
63
  end
52
64
 
65
+ def replaced_compact(message)
66
+ if string_message?(message)
67
+ message.source
68
+ else
69
+ "#{message.source}.to_s"
70
+ end
71
+ end
72
+
53
73
  # @!method exploded?(node)
54
74
  def_node_matcher :exploded?, <<~PATTERN
55
75
  (send nil? ${:raise :fail} (const {nil? cbase} :RuntimeError) $_)