rubocop 1.28.2 → 1.29.1

Sign up to get free protection for your applications and to get access to all the features.
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'