rubocop 1.28.2 → 1.29.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/config/default.yml +36 -21
  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_inside_reference_brackets.rb +1 -1
  16. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +1 -1
  17. data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
  18. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  19. data/lib/rubocop/cop/lint/loop.rb +1 -1
  20. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +5 -5
  21. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -1
  22. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  23. data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
  24. data/lib/rubocop/cop/lint/return_in_void_context.rb +5 -17
  25. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  26. data/lib/rubocop/cop/mixin/duplication.rb +1 -1
  27. data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
  28. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  29. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  30. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  31. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  32. data/lib/rubocop/cop/naming/inclusive_language.rb +3 -2
  33. data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
  34. data/lib/rubocop/cop/naming/variable_name.rb +9 -0
  35. data/lib/rubocop/cop/naming/variable_number.rb +10 -0
  36. data/lib/rubocop/cop/security/yaml_load.rb +1 -1
  37. data/lib/rubocop/cop/style/alias.rb +3 -3
  38. data/lib/rubocop/cop/style/and_or.rb +1 -1
  39. data/lib/rubocop/cop/style/bisected_attr_accessor/macro.rb +1 -1
  40. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  41. data/lib/rubocop/cop/style/character_literal.rb +1 -1
  42. data/lib/rubocop/cop/style/collection_compact.rb +3 -3
  43. data/lib/rubocop/cop/style/date_time.rb +1 -1
  44. data/lib/rubocop/cop/style/double_negation.rb +28 -2
  45. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -1
  46. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  47. data/lib/rubocop/cop/style/env_home.rb +56 -0
  48. data/lib/rubocop/cop/style/fetch_env_var.rb +240 -11
  49. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -2
  50. data/lib/rubocop/cop/style/mixin_grouping.rb +1 -1
  51. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +5 -1
  52. data/lib/rubocop/cop/style/next.rb +1 -1
  53. data/lib/rubocop/cop/style/optional_arguments.rb +1 -1
  54. data/lib/rubocop/cop/style/optional_boolean_parameter.rb +1 -1
  55. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  56. data/lib/rubocop/cop/style/raise_args.rb +4 -1
  57. data/lib/rubocop/cop/style/redundant_condition.rb +108 -11
  58. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  59. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  60. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  61. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  62. data/lib/rubocop/cop/style/string_chars.rb +1 -1
  63. data/lib/rubocop/cop/style/trivial_accessors.rb +7 -8
  64. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  65. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  66. data/lib/rubocop/formatter/html_formatter.rb +2 -9
  67. data/lib/rubocop/formatter/markdown_formatter.rb +76 -0
  68. data/lib/rubocop/magic_comment.rb +4 -3
  69. data/lib/rubocop/options.rb +4 -3
  70. data/lib/rubocop/result_cache.rb +1 -1
  71. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  72. data/lib/rubocop/rspec/parallel_formatter.rb +1 -1
  73. data/lib/rubocop/rspec/shared_contexts.rb +1 -1
  74. data/lib/rubocop/runner.rb +1 -1
  75. data/lib/rubocop/string_interpreter.rb +4 -4
  76. data/lib/rubocop/target_ruby.rb +7 -1
  77. data/lib/rubocop/version.rb +1 -1
  78. data/lib/rubocop.rb +3 -1
  79. metadata +16 -7
  80. 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,41 +42,84 @@ 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?
56
100
 
57
- node.parent.if_type? || (node.parent.send_type? && node.parent.prefix_bang?)
101
+ if_node = node.ancestors.find(&:if_type?)
102
+ return true if if_node&.condition == node
103
+
104
+ node.parent.send_type? && (node.parent.prefix_bang? || node.parent.comparison_method?)
105
+ end
106
+
107
+ def offensive?(node)
108
+ !(allowed_var?(node) || allowable_use?(node))
109
+ end
110
+
111
+ def default_to_rhs?(node)
112
+ operand_of_or?(node) && !right_end_of_or_chains?(node) && rhs_can_be_default_value?(node)
58
113
  end
59
114
 
60
115
  # Check if the node is a receiver and receives a message with dot syntax.
61
116
  def message_chained_with_dot?(node)
62
117
  return false if node.root?
63
118
 
64
- node.parent.send_type? && node.parent.children.first == node && node.parent.dot?
119
+ parent = node.parent
120
+ return false if !parent.call_type? || parent.children.first != node
121
+
122
+ parent.dot? || parent.safe_navigation?
65
123
  end
66
124
 
67
125
  # The following are allowed cases:
@@ -84,6 +142,177 @@ module RuboCop
84
142
  lhs, _method, _rhs = *parent
85
143
  node == lhs
86
144
  end
145
+
146
+ def left_end_of_or_chains?(node)
147
+ return false unless operand_of_or?(node)
148
+
149
+ node.parent.lhs == node
150
+ end
151
+
152
+ def right_end_of_or_chains?(node)
153
+ !(left_end_of_or_chains?(node) || node.parent&.parent&.or_type?)
154
+ end
155
+
156
+ # Returns the node and expression of the rightmost `ENV[]` in `||` chains.
157
+ # e.g.,
158
+ # `ENV['X'] || y || z || ENV['A'] || b`
159
+ # ^^^^^^^^ Matches this one
160
+ def rightmost_offense_in_or_chains(base_node)
161
+ or_nodes = [base_node.parent]
162
+
163
+ while (grand_parent = or_nodes.last&.parent)&.or_type?
164
+ or_nodes << grand_parent
165
+ end
166
+
167
+ # Finds the rightmost `ENV[]` in `||` chains.
168
+ or_node = or_nodes.reverse.find do |n|
169
+ env_with_bracket?(n.rhs)
170
+ end
171
+
172
+ or_node ? or_node.rhs : base_node
173
+ end
174
+
175
+ def no_env_with_bracket_in_descendants?(node)
176
+ !env_with_bracket_in_descendants?(node)
177
+ end
178
+
179
+ def conterpart_rhs_of(node)
180
+ left_end_of_or_chains?(node) ? node.parent.rhs : node.parent.parent.rhs
181
+ end
182
+
183
+ # Looks ahead to the `ENV[]` that must be corrected first, avoiding a cross correction.
184
+ # ```
185
+ # ENV['X'] || y.map do |a|
186
+ # a.map do |b|
187
+ # ENV['Z'] + b
188
+ # ^^^^^^^^ This must be corrected first.
189
+ # end
190
+ # end
191
+ # ```
192
+ def lookahead_target_node(base_node)
193
+ return base_node unless operand_of_or?(base_node)
194
+
195
+ candidate_node = rightmost_offense_in_or_chains(base_node)
196
+ return candidate_node if right_end_of_or_chains?(candidate_node)
197
+
198
+ counterpart_rhs = conterpart_rhs_of(candidate_node)
199
+ return candidate_node if no_env_with_bracket_in_descendants?(counterpart_rhs)
200
+
201
+ new_base_node = counterpart_rhs.each_descendant.find do |d|
202
+ env_with_bracket?(d) && offensive?(d)
203
+ end
204
+ return candidate_node unless new_base_node
205
+
206
+ lookahead_target_node(new_base_node)
207
+ end
208
+
209
+ def rhs_can_be_default_value?(node)
210
+ !rhs_is_block_control?(node)
211
+ end
212
+
213
+ def rhs_is_block_control?(node)
214
+ block_control?(conterpart_rhs_of(node))
215
+ end
216
+
217
+ def new_code_default_nil(expression)
218
+ "ENV.fetch(#{expression.source}, nil)"
219
+ end
220
+
221
+ def new_code_default_rhs_single_line(node, expression)
222
+ parent = node.parent
223
+ if parent.rhs.basic_literal?
224
+ "ENV.fetch(#{expression.source}, #{parent.rhs.source})"
225
+ else
226
+ "ENV.fetch(#{expression.source}) { #{parent.rhs.source} }"
227
+ end
228
+ end
229
+
230
+ def new_code_default_rhs_multiline(node, expression)
231
+ env_indent = indent(node.parent)
232
+ default = node.parent.rhs.source.split("\n").map do |line|
233
+ "#{env_indent}#{line}"
234
+ end.join("\n")
235
+ <<~NEW_CODE.chomp
236
+ ENV.fetch(#{expression.source}) do
237
+ #{configured_indentation}#{default}
238
+ #{env_indent}end
239
+ NEW_CODE
240
+ end
241
+
242
+ def new_code_default_rhs(node, expression)
243
+ if node.parent.rhs.single_line?
244
+ new_code_default_rhs_single_line(node, expression)
245
+ else
246
+ new_code_default_rhs_multiline(node, expression)
247
+ end
248
+ end
249
+
250
+ def default_rhs(node, expression)
251
+ if left_end_of_or_chains?(node)
252
+ default_rhs_in_same_or(node, expression)
253
+ else
254
+ default_rhs_in_outer_or(node, expression)
255
+ end
256
+ end
257
+
258
+ # Adds an offense and sets `nil` to the default value of `ENV.fetch`.
259
+ # `ENV['X']` --> `ENV.fetch('X', nil)`
260
+ def default_nil(node, expression)
261
+ message = format(MSG_DEFAULT_NIL, key: expression.source)
262
+
263
+ add_offense(node, message: message) do |corrector|
264
+ corrector.replace(node, new_code_default_nil(expression))
265
+ end
266
+ end
267
+
268
+ # Adds an offense and makes the RHS the default value of `ENV.fetch`.
269
+ # `ENV['X'] || y` --> `ENV.fetch('X') { y }`
270
+ def default_rhs_in_same_or(node, expression)
271
+ template = message_template_for(node.parent.rhs)
272
+ message = format(template,
273
+ key: expression.source,
274
+ default: first_line_of(node.parent.rhs.source))
275
+
276
+ add_offense(node, message: message) do |corrector|
277
+ corrector.replace(node.parent, new_code_default_rhs(node, expression))
278
+ end
279
+ end
280
+
281
+ # Adds an offense and makes the RHS the default value of `ENV.fetch`.
282
+ # `z || ENV['X'] || y` --> `z || ENV.fetch('X') { y }`
283
+ def default_rhs_in_outer_or(node, expression)
284
+ parent = node.parent
285
+ grand_parent = parent.parent
286
+
287
+ template = message_template_for(grand_parent.rhs)
288
+ message = format(template,
289
+ key: expression.source,
290
+ default: first_line_of(grand_parent.rhs.source))
291
+
292
+ add_offense(node, message: message) do |corrector|
293
+ lhs_code = parent.lhs.source
294
+ rhs_code = new_code_default_rhs(parent, expression)
295
+ corrector.replace(grand_parent, "#{lhs_code} || #{rhs_code}")
296
+ end
297
+ end
298
+
299
+ def message_template_for(rhs)
300
+ if rhs.multiline?
301
+ MSG_DEFAULT_RHS_MULTILINE_BLOCK
302
+ elsif rhs.basic_literal?
303
+ MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH
304
+ else
305
+ MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK
306
+ end
307
+ end
308
+
309
+ def configured_indentation
310
+ ' ' * (config.for_cop('Layout/IndentationWidth')['Width'] || 2)
311
+ end
312
+
313
+ def first_line_of(source)
314
+ source.split("\n").first
315
+ end
87
316
  end
88
317
  end
89
318
  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
@@ -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}))"
@@ -91,6 +91,9 @@ module RuboCop
91
91
 
92
92
  def check_compact(node)
93
93
  if node.arguments.size > 1
94
+ exception = node.first_argument
95
+ return if exception.send_type? && exception.first_argument&.hash_type?
96
+
94
97
  add_offense(node, message: format(COMPACT_MSG, method: node.method_name)) do |corrector|
95
98
  replacement = correction_exploded_to_compact(node)
96
99
 
@@ -44,9 +44,9 @@ module RuboCop
44
44
  message = message(node)
45
45
 
46
46
  add_offense(range_of_offense(node), message: message) do |corrector|
47
- if node.ternary?
47
+ if node.ternary? && !branches_have_method?(node)
48
48
  correct_ternary(corrector, node)
49
- elsif node.modifier_form? || !node.else_branch
49
+ elsif redudant_condition?(node)
50
50
  corrector.replace(node, node.if_branch.source)
51
51
  else
52
52
  corrected = make_ternary_form(node)
@@ -59,7 +59,7 @@ module RuboCop
59
59
  private
60
60
 
61
61
  def message(node)
62
- if node.modifier_form? || !node.else_branch
62
+ if redudant_condition?(node)
63
63
  REDUNDANT_CONDITION
64
64
  else
65
65
  MSG
@@ -68,18 +68,22 @@ module RuboCop
68
68
 
69
69
  def range_of_offense(node)
70
70
  return node.loc.expression unless node.ternary?
71
+ return node.loc.expression if node.ternary? && branches_have_method?(node)
71
72
 
72
73
  range_between(node.loc.question.begin_pos, node.loc.colon.end_pos)
73
74
  end
74
75
 
75
76
  def offense?(node)
76
- condition, if_branch, else_branch = *node
77
+ _condition, _if_branch, else_branch = *node
77
78
 
78
79
  return false if use_if_branch?(else_branch) || use_hash_key_assignment?(else_branch)
79
80
 
80
- condition == if_branch && !node.elsif? && (
81
- node.ternary? || !else_branch.instance_of?(AST::Node) || else_branch.single_line?
82
- )
81
+ synonymous_condition_and_branch?(node) && !node.elsif? &&
82
+ (node.ternary? || !else_branch.instance_of?(AST::Node) || else_branch.single_line?)
83
+ end
84
+
85
+ def redudant_condition?(node)
86
+ node.modifier_form? || !node.else_branch
83
87
  end
84
88
 
85
89
  def use_if_branch?(else_branch)
@@ -90,19 +94,108 @@ module RuboCop
90
94
  else_branch&.send_type? && else_branch&.method?(:[]=)
91
95
  end
92
96
 
97
+ def use_hash_key_access?(node)
98
+ node.send_type? && node.method?(:[])
99
+ end
100
+
101
+ def synonymous_condition_and_branch?(node)
102
+ condition, if_branch, _else_branch = *node
103
+ # e.g.
104
+ # if var
105
+ # var
106
+ # else
107
+ # 'foo'
108
+ # end
109
+ return true if condition == if_branch
110
+
111
+ # e.g.
112
+ # if foo
113
+ # @value = foo
114
+ # else
115
+ # @value = another_value?
116
+ # end
117
+ return true if branches_have_assignment?(node) && condition == if_branch.expression
118
+
119
+ # e.g.
120
+ # if foo
121
+ # test.value = foo
122
+ # else
123
+ # test.value = another_value?
124
+ # end
125
+ branches_have_method?(node) && condition == if_branch.first_argument &&
126
+ !use_hash_key_access?(if_branch)
127
+ end
128
+
129
+ def branches_have_assignment?(node)
130
+ _condition, if_branch, else_branch = *node
131
+
132
+ return false unless if_branch && else_branch
133
+
134
+ asgn_type?(if_branch) && (if_branch_variable_name = if_branch.name) &&
135
+ asgn_type?(else_branch) && (else_branch_variable_name = else_branch.name) &&
136
+ if_branch_variable_name == else_branch_variable_name
137
+ end
138
+
139
+ def asgn_type?(node)
140
+ node.lvasgn_type? || node.ivasgn_type? || node.cvasgn_type? || node.gvasgn_type?
141
+ end
142
+
143
+ def branches_have_method?(node)
144
+ _condition, if_branch, else_branch = *node
145
+
146
+ return false unless if_branch && else_branch
147
+
148
+ if_branch.send_type? && if_branch.arguments.count == 1 &&
149
+ else_branch.send_type? && else_branch.arguments.count == 1 &&
150
+ if_branch.method?(else_branch.method_name)
151
+ end
152
+
153
+ def if_source(if_branch)
154
+ if branches_have_method?(if_branch.parent) && if_branch.parenthesized?
155
+ if_branch.source.delete_suffix(')')
156
+ else
157
+ if_branch.source
158
+ end
159
+ end
160
+
93
161
  def else_source(else_branch)
94
- if require_parentheses?(else_branch)
162
+ if branches_have_method?(else_branch.parent)
163
+ else_source_if_has_method(else_branch)
164
+ elsif require_parentheses?(else_branch)
95
165
  "(#{else_branch.source})"
96
166
  elsif without_argument_parentheses_method?(else_branch)
97
167
  "#{else_branch.method_name}(#{else_branch.arguments.map(&:source).join(', ')})"
168
+ elsif branches_have_assignment?(else_branch.parent)
169
+ else_source_if_has_assignment(else_branch)
98
170
  else
99
171
  else_branch.source
100
172
  end
101
173
  end
102
174
 
175
+ def else_source_if_has_method(else_branch)
176
+ if require_parentheses?(else_branch.first_argument)
177
+ "(#{else_branch.first_argument.source})"
178
+ elsif require_braces?(else_branch.first_argument)
179
+ "{ #{else_branch.first_argument.source} }"
180
+ else
181
+ else_branch.first_argument.source
182
+ end
183
+ end
184
+
185
+ def else_source_if_has_assignment(else_branch)
186
+ if require_parentheses?(else_branch.expression)
187
+ "(#{else_branch.expression.source})"
188
+ elsif require_braces?(else_branch.expression)
189
+ "{ #{else_branch.expression.source} }"
190
+ else
191
+ else_branch.expression.source
192
+ end
193
+ end
194
+
103
195
  def make_ternary_form(node)
104
196
  _condition, if_branch, else_branch = *node
105
- ternary_form = [if_branch.source, else_source(else_branch)].join(' || ')
197
+ ternary_form = [if_source(if_branch), else_source(else_branch)].join(' || ')
198
+ ternary_form += ')' if branches_have_method?(node) && if_branch.parenthesized?
106
199
 
107
200
  if node.parent&.send_type?
108
201
  "(#{ternary_form})"
@@ -126,9 +219,13 @@ module RuboCop
126
219
  (node.respond_to?(:semantic_operator?) && node.semantic_operator?)
127
220
  end
128
221
 
222
+ def require_braces?(node)
223
+ node.hash_type? && !node.braces?
224
+ end
225
+
129
226
  def without_argument_parentheses_method?(node)
130
- node.send_type? &&
131
- !node.arguments.empty? && !node.parenthesized? && !node.operator_method?
227
+ node.send_type? && !node.arguments.empty? &&
228
+ !node.parenthesized? && !node.operator_method? && !node.assignment_method?
132
229
  end
133
230
  end
134
231
  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
  # ----
@@ -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'