rubocop 1.28.0 → 1.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/config/default.yml +21 -6
  4. data/lib/rubocop/cop/badge.rb +1 -1
  5. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  6. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  7. data/lib/rubocop/cop/gemspec/dependency_version.rb +156 -0
  8. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -6
  9. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  10. data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +80 -0
  11. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  12. data/lib/rubocop/cop/layout/comment_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  14. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
  15. data/lib/rubocop/cop/layout/space_before_brackets.rb +7 -0
  16. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -1
  17. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +1 -1
  18. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -1
  19. data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
  20. data/lib/rubocop/cop/lint/erb_new_arguments.rb +0 -3
  21. data/lib/rubocop/cop/lint/loop.rb +1 -1
  22. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  23. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -1
  24. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  25. data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
  26. data/lib/rubocop/cop/lint/return_in_void_context.rb +5 -17
  27. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  28. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +5 -0
  29. data/lib/rubocop/cop/mixin/duplication.rb +1 -1
  30. data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
  31. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  32. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  33. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  34. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  35. data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
  36. data/lib/rubocop/cop/naming/variable_name.rb +9 -0
  37. data/lib/rubocop/cop/naming/variable_number.rb +10 -0
  38. data/lib/rubocop/cop/security/yaml_load.rb +1 -1
  39. data/lib/rubocop/cop/style/alias.rb +3 -3
  40. data/lib/rubocop/cop/style/and_or.rb +1 -1
  41. data/lib/rubocop/cop/style/bisected_attr_accessor/macro.rb +1 -1
  42. data/lib/rubocop/cop/style/block_delimiters.rb +5 -0
  43. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  44. data/lib/rubocop/cop/style/character_literal.rb +1 -1
  45. data/lib/rubocop/cop/style/collection_compact.rb +3 -3
  46. data/lib/rubocop/cop/style/date_time.rb +1 -1
  47. data/lib/rubocop/cop/style/double_negation.rb +28 -2
  48. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -1
  49. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  50. data/lib/rubocop/cop/style/env_home.rb +56 -0
  51. data/lib/rubocop/cop/style/fetch_env_var.rb +256 -15
  52. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -2
  53. data/lib/rubocop/cop/style/map_to_hash.rb +0 -3
  54. data/lib/rubocop/cop/style/mixin_grouping.rb +1 -1
  55. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +5 -1
  56. data/lib/rubocop/cop/style/next.rb +1 -1
  57. data/lib/rubocop/cop/style/optional_arguments.rb +1 -1
  58. data/lib/rubocop/cop/style/optional_boolean_parameter.rb +1 -1
  59. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  60. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  61. data/lib/rubocop/cop/style/redundant_condition.rb +77 -7
  62. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  63. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  64. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  65. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  66. data/lib/rubocop/cop/style/single_argument_dig.rb +1 -0
  67. data/lib/rubocop/cop/style/slicing_with_range.rb +0 -3
  68. data/lib/rubocop/cop/style/string_chars.rb +1 -1
  69. data/lib/rubocop/cop/style/trivial_accessors.rb +7 -8
  70. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  71. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  72. data/lib/rubocop/formatter/html_formatter.rb +2 -9
  73. data/lib/rubocop/formatter/markdown_formatter.rb +76 -0
  74. data/lib/rubocop/magic_comment.rb +4 -3
  75. data/lib/rubocop/options.rb +4 -3
  76. data/lib/rubocop/result_cache.rb +1 -1
  77. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  78. data/lib/rubocop/rspec/parallel_formatter.rb +1 -1
  79. data/lib/rubocop/rspec/shared_contexts.rb +1 -5
  80. data/lib/rubocop/runner.rb +1 -1
  81. data/lib/rubocop/string_interpreter.rb +4 -4
  82. data/lib/rubocop/target_ruby.rb +8 -2
  83. data/lib/rubocop/version.rb +1 -1
  84. data/lib/rubocop.rb +3 -1
  85. metadata +17 -8
  86. data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +0 -45
@@ -9,17 +9,32 @@ module RuboCop
9
9
  # On the other hand, `ENV.fetch` raises KeyError or returns the explicitly
10
10
  # specified default value.
11
11
  #
12
+ # When an `ENV[]` is the LHS of `||`, the autocorrect makes the RHS
13
+ # the default value of `ENV.fetch`.
14
+ #
12
15
  # @example
13
16
  # # bad
14
17
  # ENV['X']
15
- # ENV['X'] || z
18
+ # ENV['X'] || 'string literal'
19
+ # ENV['X'] || some_method
16
20
  # x = ENV['X']
17
21
  #
22
+ # ENV['X'] || y.map do |a|
23
+ # puts a * 2
24
+ # end
25
+ #
18
26
  # # good
19
27
  # ENV.fetch('X')
20
- # ENV.fetch('X', nil) || z
28
+ # ENV.fetch('X', 'string literal')
29
+ # ENV.fetch('X') { some_method }
21
30
  # x = ENV.fetch('X')
22
31
  #
32
+ # ENV.fetch('X') do
33
+ # y.map do |a|
34
+ # puts a * 2
35
+ # end
36
+ # end
37
+ #
23
38
  # # also good
24
39
  # !ENV['X']
25
40
  # ENV['X'].some_method # (e.g. `.nil?`)
@@ -27,48 +42,274 @@ module RuboCop
27
42
  class FetchEnvVar < Base
28
43
  extend AutoCorrector
29
44
 
30
- MSG = 'Use `ENV.fetch(%<key>s)` or `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
45
+ # rubocop:disable Layout/LineLength
46
+ MSG_DEFAULT_NIL = 'Use `ENV.fetch(%<key>s)` or `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
47
+ MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH = 'Use `ENV.fetch(%<key>s, %<default>s)` instead of `ENV[%<key>s] || %<default>s`.'
48
+ MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK = 'Use `ENV.fetch(%<key>s) { %<default>s }` instead of `ENV[%<key>s] || %<default>s`.'
49
+ MSG_DEFAULT_RHS_MULTILINE_BLOCK = 'Use `ENV.fetch(%<key>s)` with a block containing `%<default>s ...`'
50
+ # rubocop:enable Layout/LineLength
31
51
 
32
52
  # @!method env_with_bracket?(node)
33
53
  def_node_matcher :env_with_bracket?, <<~PATTERN
34
54
  (send (const nil? :ENV) :[] $_)
35
55
  PATTERN
36
56
 
57
+ # @!method operand_of_or?(node)
58
+ def_node_matcher :operand_of_or?, <<~PATTERN
59
+ (^or ...)
60
+ PATTERN
61
+
62
+ # @!method block_control?(node)
63
+ def_node_matcher :block_control?, <<~PATTERN
64
+ ({next | break | retry | redo})
65
+ PATTERN
66
+
67
+ # @!method env_with_bracket_in_descendants?(node)
68
+ def_node_matcher :env_with_bracket_in_descendants?, <<~PATTERN
69
+ `(send (const nil? :ENV) :[] $_)
70
+ PATTERN
71
+
37
72
  def on_send(node)
38
73
  env_with_bracket?(node) do |expression|
39
- break if allowed_var?(expression)
40
- break if allowable_use?(node)
74
+ break unless offensive?(node)
75
+
76
+ if operand_of_or?(node)
77
+ target_node = lookahead_target_node(node)
78
+ target_expr = env_with_bracket?(target_node)
41
79
 
42
- add_offense(node, message: format(MSG, key: expression.source)) do |corrector|
43
- corrector.replace(node, "ENV.fetch(#{expression.source}, nil)")
80
+ if default_to_rhs?(target_node)
81
+ default_rhs(target_node, target_expr)
82
+ else
83
+ default_nil(target_node, target_expr)
84
+ end
85
+ else
86
+ default_nil(node, expression)
44
87
  end
45
88
  end
46
89
  end
47
90
 
48
91
  private
49
92
 
50
- def allowed_var?(expression)
51
- expression.str_type? && cop_config['AllowedVars'].include?(expression.value)
93
+ def allowed_var?(node)
94
+ env_key_node = node.children.last
95
+ env_key_node.str_type? && cop_config['AllowedVars'].include?(env_key_node.value)
52
96
  end
53
97
 
54
98
  def used_as_flag?(node)
55
99
  return false if node.root?
100
+ return true if node.parent.if_type?
56
101
 
57
- node.parent.if_type? || (node.parent.send_type? && node.parent.prefix_bang?)
102
+ node.parent.send_type? && (node.parent.prefix_bang? || node.parent.comparison_method?)
103
+ end
104
+
105
+ def offensive?(node)
106
+ !(allowed_var?(node) || allowable_use?(node))
107
+ end
108
+
109
+ def default_to_rhs?(node)
110
+ operand_of_or?(node) && !right_end_of_or_chains?(node) && rhs_can_be_default_value?(node)
58
111
  end
59
112
 
60
113
  # Check if the node is a receiver and receives a message with dot syntax.
61
114
  def message_chained_with_dot?(node)
62
115
  return false if node.root?
63
116
 
64
- node.parent.send_type? && node.parent.children.first == node && node.parent.dot?
117
+ parent = node.parent
118
+ return false if !parent.call_type? || parent.children.first != node
119
+
120
+ parent.dot? || parent.safe_navigation?
65
121
  end
66
122
 
67
- # Allow if used as a flag (e.g., `if ENV['X']` or `!ENV['X']`) because
68
- # it simply checks whether the variable is set.
69
- # Also allow if receiving a message with dot syntax, e.g. `ENV['X'].nil?`.
123
+ # The following are allowed cases:
124
+ #
125
+ # - Used as a flag (e.g., `if ENV['X']` or `!ENV['X']`) because
126
+ # it simply checks whether the variable is set.
127
+ # - Receiving a message with dot syntax, e.g. `ENV['X'].nil?`.
128
+ # - `ENV['key']` assigned by logical AND/OR assignment.
70
129
  def allowable_use?(node)
71
- used_as_flag?(node) || message_chained_with_dot?(node)
130
+ used_as_flag?(node) || message_chained_with_dot?(node) || assigned?(node)
131
+ end
132
+
133
+ # The following are allowed cases:
134
+ #
135
+ # - `ENV['key']` is a receiver of `||=`, e.g. `ENV['X'] ||= y`.
136
+ # - `ENV['key']` is a receiver of `&&=`, e.g. `ENV['X'] &&= y`.
137
+ def assigned?(node)
138
+ return false unless (parent = node.parent)&.assignment?
139
+
140
+ lhs, _method, _rhs = *parent
141
+ node == lhs
142
+ end
143
+
144
+ def left_end_of_or_chains?(node)
145
+ return false unless operand_of_or?(node)
146
+
147
+ node.parent.lhs == node
148
+ end
149
+
150
+ def right_end_of_or_chains?(node)
151
+ !(left_end_of_or_chains?(node) || node.parent&.parent&.or_type?)
152
+ end
153
+
154
+ # Returns the node and expression of the rightmost `ENV[]` in `||` chains.
155
+ # e.g.,
156
+ # `ENV['X'] || y || z || ENV['A'] || b`
157
+ # ^^^^^^^^ Matches this one
158
+ def rightmost_offense_in_or_chains(base_node)
159
+ or_nodes = [base_node.parent]
160
+
161
+ while (grand_parent = or_nodes.last&.parent)&.or_type?
162
+ or_nodes << grand_parent
163
+ end
164
+
165
+ # Finds the rightmost `ENV[]` in `||` chains.
166
+ or_node = or_nodes.reverse.find do |n|
167
+ env_with_bracket?(n.rhs)
168
+ end
169
+
170
+ or_node ? or_node.rhs : base_node
171
+ end
172
+
173
+ def no_env_with_bracket_in_descendants?(node)
174
+ !env_with_bracket_in_descendants?(node)
175
+ end
176
+
177
+ def conterpart_rhs_of(node)
178
+ left_end_of_or_chains?(node) ? node.parent.rhs : node.parent.parent.rhs
179
+ end
180
+
181
+ # Looks ahead to the `ENV[]` that must be corrected first, avoiding a cross correction.
182
+ # ```
183
+ # ENV['X'] || y.map do |a|
184
+ # a.map do |b|
185
+ # ENV['Z'] + b
186
+ # ^^^^^^^^ This must be corrected first.
187
+ # end
188
+ # end
189
+ # ```
190
+ def lookahead_target_node(base_node)
191
+ return base_node unless operand_of_or?(base_node)
192
+
193
+ candidate_node = rightmost_offense_in_or_chains(base_node)
194
+ return candidate_node if right_end_of_or_chains?(candidate_node)
195
+
196
+ counterpart_rhs = conterpart_rhs_of(candidate_node)
197
+ return candidate_node if no_env_with_bracket_in_descendants?(counterpart_rhs)
198
+
199
+ new_base_node = counterpart_rhs.each_descendant.find do |d|
200
+ env_with_bracket?(d) && offensive?(d)
201
+ end
202
+ return candidate_node unless new_base_node
203
+
204
+ lookahead_target_node(new_base_node)
205
+ end
206
+
207
+ def rhs_can_be_default_value?(node)
208
+ !rhs_is_block_control?(node)
209
+ end
210
+
211
+ def rhs_is_block_control?(node)
212
+ block_control?(conterpart_rhs_of(node))
213
+ end
214
+
215
+ def new_code_default_nil(expression)
216
+ "ENV.fetch(#{expression.source}, nil)"
217
+ end
218
+
219
+ def new_code_default_rhs_single_line(node, expression)
220
+ parent = node.parent
221
+ if parent.rhs.basic_literal?
222
+ "ENV.fetch(#{expression.source}, #{parent.rhs.source})"
223
+ else
224
+ "ENV.fetch(#{expression.source}) { #{parent.rhs.source} }"
225
+ end
226
+ end
227
+
228
+ def new_code_default_rhs_multiline(node, expression)
229
+ env_indent = indent(node.parent)
230
+ default = node.parent.rhs.source.split("\n").map do |line|
231
+ "#{env_indent}#{line}"
232
+ end.join("\n")
233
+ <<~NEW_CODE.chomp
234
+ ENV.fetch(#{expression.source}) do
235
+ #{configured_indentation}#{default}
236
+ #{env_indent}end
237
+ NEW_CODE
238
+ end
239
+
240
+ def new_code_default_rhs(node, expression)
241
+ if node.parent.rhs.single_line?
242
+ new_code_default_rhs_single_line(node, expression)
243
+ else
244
+ new_code_default_rhs_multiline(node, expression)
245
+ end
246
+ end
247
+
248
+ def default_rhs(node, expression)
249
+ if left_end_of_or_chains?(node)
250
+ default_rhs_in_same_or(node, expression)
251
+ else
252
+ default_rhs_in_outer_or(node, expression)
253
+ end
254
+ end
255
+
256
+ # Adds an offense and sets `nil` to the default value of `ENV.fetch`.
257
+ # `ENV['X']` --> `ENV.fetch('X', nil)`
258
+ def default_nil(node, expression)
259
+ message = format(MSG_DEFAULT_NIL, key: expression.source)
260
+
261
+ add_offense(node, message: message) do |corrector|
262
+ corrector.replace(node, new_code_default_nil(expression))
263
+ end
264
+ end
265
+
266
+ # Adds an offense and makes the RHS the default value of `ENV.fetch`.
267
+ # `ENV['X'] || y` --> `ENV.fetch('X') { y }`
268
+ def default_rhs_in_same_or(node, expression)
269
+ template = message_template_for(node.parent.rhs)
270
+ message = format(template,
271
+ key: expression.source,
272
+ default: first_line_of(node.parent.rhs.source))
273
+
274
+ add_offense(node, message: message) do |corrector|
275
+ corrector.replace(node.parent, new_code_default_rhs(node, expression))
276
+ end
277
+ end
278
+
279
+ # Adds an offense and makes the RHS the default value of `ENV.fetch`.
280
+ # `z || ENV['X'] || y` --> `z || ENV.fetch('X') { y }`
281
+ def default_rhs_in_outer_or(node, expression)
282
+ parent = node.parent
283
+ grand_parent = parent.parent
284
+
285
+ template = message_template_for(grand_parent.rhs)
286
+ message = format(template,
287
+ key: expression.source,
288
+ default: first_line_of(grand_parent.rhs.source))
289
+
290
+ add_offense(node, message: message) do |corrector|
291
+ lhs_code = parent.lhs.source
292
+ rhs_code = new_code_default_rhs(parent, expression)
293
+ corrector.replace(grand_parent, "#{lhs_code} || #{rhs_code}")
294
+ end
295
+ end
296
+
297
+ def message_template_for(rhs)
298
+ if rhs.multiline?
299
+ MSG_DEFAULT_RHS_MULTILINE_BLOCK
300
+ elsif rhs.basic_literal?
301
+ MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH
302
+ else
303
+ MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK
304
+ end
305
+ end
306
+
307
+ def configured_indentation
308
+ ' ' * (config.for_cop('Layout/IndentationWidth')['Width'] || 2)
309
+ end
310
+
311
+ def first_line_of(source)
312
+ source.split("\n").first
72
313
  end
73
314
  end
74
315
  end
@@ -13,7 +13,7 @@ module RuboCop
13
13
  #
14
14
  # @safety
15
15
  # Auto-correction is unsafe because changing the order of method invocations
16
- # may change the behaviour of the code. For example:
16
+ # may change the behavior of the code. For example:
17
17
  #
18
18
  # [source,ruby]
19
19
  # ----
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # ----
28
28
  #
29
29
  # In this example, `method_that_relies_on_global_state` will be moved before
30
- # `method_that_modifies_global_state`, which changes the behaviour of the program.
30
+ # `method_that_modifies_global_state`, which changes the behavior of the program.
31
31
  #
32
32
  # @example
33
33
  # # bad
@@ -29,11 +29,8 @@ module RuboCop
29
29
  #
30
30
  class MapToHash < Base
31
31
  extend AutoCorrector
32
- extend TargetRubyVersion
33
32
  include RangeHelp
34
33
 
35
- minimum_target_ruby_version 2.6
36
-
37
34
  MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
38
35
  RESTRICT_ON_SEND = %i[to_h].freeze
39
36
 
@@ -119,7 +119,7 @@ module RuboCop
119
119
  arguments = node.arguments.reverse
120
120
  mixins = ["#{node.method_name} #{arguments.first.source}"]
121
121
 
122
- arguments[1..-1].inject(mixins) do |replacement, arg|
122
+ arguments[1..].inject(mixins) do |replacement, arg|
123
123
  replacement << "#{indent(node)}#{node.method_name} #{arg.source}"
124
124
  end.join("\n")
125
125
  end
@@ -73,7 +73,11 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def enforce_single_line_ternary_operator?(node)
76
- SINGLE_LINE_TYPES.include?(node.parent.type)
76
+ SINGLE_LINE_TYPES.include?(node.parent.type) && !use_assignment_method?(node.parent)
77
+ end
78
+
79
+ def use_assignment_method?(node)
80
+ node.send_type? && node.assignment_method?
77
81
  end
78
82
  end
79
83
  end
@@ -185,7 +185,7 @@ module RuboCop
185
185
  end
186
186
 
187
187
  def end_followed_by_whitespace_only?(source_buffer, end_pos)
188
- /\A\s*$/.match?(source_buffer.source[end_pos..-1])
188
+ /\A\s*$/.match?(source_buffer.source[end_pos..])
189
189
  end
190
190
 
191
191
  def reindentable_lines(node)
@@ -8,7 +8,7 @@ module RuboCop
8
8
  #
9
9
  # @safety
10
10
  # This cop is unsafe because changing a method signature will
11
- # implicitly change behaviour.
11
+ # implicitly change behavior.
12
12
  #
13
13
  # @example
14
14
  # # bad
@@ -9,7 +9,7 @@ module RuboCop
9
9
  #
10
10
  # @safety
11
11
  # This cop is unsafe because changing a method signature will
12
- # implicitly change behaviour.
12
+ # implicitly change behavior.
13
13
  #
14
14
  # @example
15
15
  # # bad
@@ -116,7 +116,7 @@ module RuboCop
116
116
  def wrong_quotes?(node)
117
117
  return super if hash_key?(node)
118
118
 
119
- super(node.source[1..-1])
119
+ super(node.source[1..])
120
120
  end
121
121
  end
122
122
  end
@@ -80,7 +80,7 @@ module RuboCop
80
80
  return node.source if message_nodes.size > 1
81
81
 
82
82
  argument = message_nodes.first.source
83
- exception_class = exception_node.const_name || exception_node.receiver.source
83
+ exception_class = exception_node.receiver&.source || exception_node.source
84
84
 
85
85
  if node.parent && requires_parens?(node.parent)
86
86
  "#{node.method_name}(#{exception_class}.new(#{argument}))"
@@ -73,13 +73,12 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def offense?(node)
76
- condition, if_branch, else_branch = *node
76
+ _condition, _if_branch, else_branch = *node
77
77
 
78
78
  return false if use_if_branch?(else_branch) || use_hash_key_assignment?(else_branch)
79
79
 
80
- condition == if_branch && !node.elsif? && (
81
- node.ternary? || !else_branch.instance_of?(AST::Node) || else_branch.single_line?
82
- )
80
+ synonymous_condition_and_branch?(node) && !node.elsif? &&
81
+ (node.ternary? || !else_branch.instance_of?(AST::Node) || else_branch.single_line?)
83
82
  end
84
83
 
85
84
  def use_if_branch?(else_branch)
@@ -90,16 +89,87 @@ module RuboCop
90
89
  else_branch&.send_type? && else_branch&.method?(:[]=)
91
90
  end
92
91
 
92
+ def synonymous_condition_and_branch?(node)
93
+ condition, if_branch, _else_branch = *node
94
+ # e.g.
95
+ # if var
96
+ # var
97
+ # else
98
+ # 'foo'
99
+ # end
100
+ return true if condition == if_branch
101
+
102
+ # e.g.
103
+ # if foo
104
+ # @value = foo
105
+ # else
106
+ # @value = another_value?
107
+ # end
108
+ return true if branches_have_assignment?(node) && condition == if_branch.expression
109
+
110
+ # e.g.
111
+ # if foo
112
+ # test.value = foo
113
+ # else
114
+ # test.value = another_value?
115
+ # end
116
+ branches_have_method?(node) && condition == if_branch.first_argument
117
+ end
118
+
119
+ def branches_have_assignment?(node)
120
+ _condition, if_branch, else_branch = *node
121
+
122
+ return false unless if_branch && else_branch
123
+
124
+ asgn_type?(if_branch) && (if_branch_variable_name = if_branch.name) &&
125
+ asgn_type?(else_branch) && (else_branch_variable_name = else_branch.name) &&
126
+ if_branch_variable_name == else_branch_variable_name
127
+ end
128
+
129
+ def asgn_type?(node)
130
+ node.lvasgn_type? || node.ivasgn_type? || node.cvasgn_type? || node.gvasgn_type?
131
+ end
132
+
133
+ def branches_have_method?(node)
134
+ _condition, if_branch, else_branch = *node
135
+
136
+ return false unless if_branch && else_branch
137
+
138
+ if_branch.send_type? && if_branch.arguments.count == 1 &&
139
+ else_branch.send_type? && else_branch.arguments.count == 1 &&
140
+ if_branch.method?(else_branch.method_name)
141
+ end
142
+
93
143
  def else_source(else_branch)
94
- if require_parentheses?(else_branch)
144
+ if branches_have_method?(else_branch.parent)
145
+ else_source_if_has_method(else_branch)
146
+ elsif require_parentheses?(else_branch)
95
147
  "(#{else_branch.source})"
96
148
  elsif without_argument_parentheses_method?(else_branch)
97
149
  "#{else_branch.method_name}(#{else_branch.arguments.map(&:source).join(', ')})"
150
+ elsif branches_have_assignment?(else_branch.parent)
151
+ else_source_if_has_assignment(else_branch)
98
152
  else
99
153
  else_branch.source
100
154
  end
101
155
  end
102
156
 
157
+ def else_source_if_has_method(else_branch)
158
+ if require_parentheses?(else_branch.first_argument)
159
+ "(#{else_branch.first_argument.source})"
160
+ else
161
+ else_branch.first_argument.source
162
+ end
163
+ end
164
+
165
+ def else_source_if_has_assignment(else_branch)
166
+ if require_parentheses?(else_branch.expression)
167
+ "(#{else_branch.expression.source})"
168
+ else
169
+ else_branch.expression.source
170
+ end
171
+ end
172
+
103
173
  def make_ternary_form(node)
104
174
  _condition, if_branch, else_branch = *node
105
175
  ternary_form = [if_branch.source, else_source(else_branch)].join(' || ')
@@ -127,8 +197,8 @@ module RuboCop
127
197
  end
128
198
 
129
199
  def without_argument_parentheses_method?(node)
130
- node.send_type? &&
131
- !node.arguments.empty? && !node.parenthesized? && !node.operator_method?
200
+ node.send_type? && !node.arguments.empty? &&
201
+ !node.parenthesized? && !node.operator_method? && !node.assignment_method?
132
202
  end
133
203
  end
134
204
  end
@@ -99,7 +99,7 @@ module RuboCop
99
99
  end
100
100
 
101
101
  def backslash_b?(elem)
102
- # \b's behaviour is different inside and outside of a character class, matching word
102
+ # \b's behavior is different inside and outside of a character class, matching word
103
103
  # boundaries outside but backspace (0x08) when inside.
104
104
  elem == '\b'
105
105
  end
@@ -59,7 +59,7 @@ module RuboCop
59
59
  def allowed_escape?(node, char, within_character_class)
60
60
  # Strictly speaking a few single-letter metachars are currently
61
61
  # unnecessary to "escape", e.g. i, E, F, but enumerating them is
62
- # rather difficult, and their behaviour could change over time with
62
+ # rather difficult, and their behavior could change over time with
63
63
  # different versions of Ruby so that e.g. /\i/ != /i/
64
64
  return true if /[[:alnum:]]/.match?(char)
65
65
  return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char)
@@ -67,8 +67,7 @@ module RuboCop
67
67
  alias on_gvasgn on_lvasgn
68
68
 
69
69
  def on_send(node)
70
- # TODO: Remove `Symbol#to_s` after supporting only Ruby >= 2.7.
71
- return unless node.method_name.to_s.end_with?('=')
70
+ return unless node.assignment_method?
72
71
  return unless redundant_assignment?(node)
73
72
 
74
73
  message = format(MSG, method_name: node.first_argument.method_name)
@@ -23,7 +23,7 @@ module RuboCop
23
23
  #
24
24
  # @safety
25
25
  # Autocorrection is unsafe because if a value is `false`, the resulting
26
- # code will have different behaviour or raise an error.
26
+ # code will have different behavior or raise an error.
27
27
  #
28
28
  # [source,ruby]
29
29
  # ----
@@ -44,6 +44,7 @@ module RuboCop
44
44
 
45
45
  expression = single_argument_dig?(node)
46
46
  return unless expression
47
+ return if expression.forwarded_args_type?
47
48
 
48
49
  receiver = node.receiver.source
49
50
  argument = expression.source
@@ -27,9 +27,6 @@ module RuboCop
27
27
  # items[1..]
28
28
  class SlicingWithRange < Base
29
29
  extend AutoCorrector
30
- extend TargetRubyVersion
31
-
32
- minimum_target_ruby_version 2.6
33
30
 
34
31
  MSG = 'Prefer ary[n..] over ary[n..-1].'
35
32
  RESTRICT_ON_SEND = %i[[]].freeze
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # @safety
9
9
  # This cop is unsafe because it cannot be guaranteed that the receiver
10
10
  # is actually a string. If another class has a `split` method with
11
- # different behaviour, it would be registered as a false positive.
11
+ # different behavior, it would be registered as a false positive.
12
12
  #
13
13
  # @example
14
14
  # # bad
@@ -167,8 +167,8 @@ module RuboCop
167
167
  allowed_methods.map(&:to_sym) + [:initialize]
168
168
  end
169
169
 
170
- def dsl_writer?(method_name)
171
- !method_name.to_s.end_with?('=')
170
+ def dsl_writer?(node)
171
+ !node.assignment_method?
172
172
  end
173
173
 
174
174
  def trivial_reader?(node)
@@ -180,8 +180,7 @@ module RuboCop
180
180
  end
181
181
 
182
182
  def trivial_writer?(node)
183
- looks_like_trivial_writer?(node) &&
184
- !allowed_method_name?(node) && !allowed_writer?(node.method_name)
183
+ looks_like_trivial_writer?(node) && !allowed_method_name?(node) && !allowed_writer?(node)
185
184
  end
186
185
 
187
186
  # @!method looks_like_trivial_writer?(node)
@@ -195,8 +194,8 @@ module RuboCop
195
194
  (exact_name_match? && !names_match?(node))
196
195
  end
197
196
 
198
- def allowed_writer?(method_name)
199
- allow_dsl_writers? && dsl_writer?(method_name)
197
+ def allowed_writer?(node)
198
+ allow_dsl_writers? && dsl_writer?(node)
200
199
  end
201
200
 
202
201
  def allowed_reader?(node)
@@ -206,11 +205,11 @@ module RuboCop
206
205
  def names_match?(node)
207
206
  ivar_name, = *node.body
208
207
 
209
- node.method_name.to_s.sub(/[=?]$/, '') == ivar_name[1..-1]
208
+ node.method_name.to_s.sub(/[=?]$/, '') == ivar_name[1..]
210
209
  end
211
210
 
212
211
  def trivial_accessor_kind(node)
213
- if trivial_writer?(node) && !dsl_writer?(node.method_name)
212
+ if trivial_writer?(node) && !dsl_writer?(node)
214
213
  'writer'
215
214
  elsif trivial_reader?(node)
216
215
  'reader'
@@ -246,7 +246,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
246
246
  end
247
247
 
248
248
  def table_of_content_for_department(department)
249
- type_title = department[0].upcase + department[1..-1]
249
+ type_title = department[0].upcase + department[1..]
250
250
  filename = "#{department_to_basename(department)}.adoc"
251
251
  content = +"=== Department xref:#{filename}[#{type_title}]\n\n"
252
252
  cops_of_department(department).each do |cop|