rubocop 1.68.0 → 1.69.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +41 -6
  4. data/lib/rubocop/cop/base.rb +1 -1
  5. data/lib/rubocop/cop/bundler/gem_filename.rb +0 -1
  6. data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +0 -1
  7. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +1 -1
  8. data/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +1 -2
  9. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +0 -2
  10. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +2 -4
  11. data/lib/rubocop/cop/internal_affairs/numblock_handler.rb +1 -1
  12. data/lib/rubocop/cop/internal_affairs/operator_keyword.rb +46 -0
  13. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +0 -2
  14. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  15. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -2
  16. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  17. data/lib/rubocop/cop/layout/begin_end_alignment.rb +0 -1
  18. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  19. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  20. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +2 -3
  21. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +3 -4
  22. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +3 -1
  23. data/lib/rubocop/cop/layout/indentation_width.rb +7 -7
  24. data/lib/rubocop/cop/layout/leading_comment_space.rb +15 -0
  25. data/lib/rubocop/cop/layout/line_length.rb +118 -4
  26. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  27. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +1 -1
  28. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -3
  29. data/lib/rubocop/cop/layout/parameter_alignment.rb +3 -4
  30. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -35
  31. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -2
  32. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  33. data/lib/rubocop/cop/layout/space_around_operators.rb +16 -17
  34. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +6 -0
  35. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +4 -0
  36. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +0 -1
  37. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +11 -12
  38. data/lib/rubocop/cop/lint/circular_argument_reference.rb +2 -0
  39. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +1 -1
  40. data/lib/rubocop/cop/lint/empty_ensure.rb +1 -1
  41. data/lib/rubocop/cop/lint/empty_file.rb +0 -2
  42. data/lib/rubocop/cop/lint/ensure_return.rb +1 -1
  43. data/lib/rubocop/cop/lint/float_comparison.rb +14 -6
  44. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -3
  45. data/lib/rubocop/cop/lint/hash_new_with_keyword_arguments_as_default.rb +55 -0
  46. data/lib/rubocop/cop/lint/interpolation_check.rb +9 -0
  47. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +3 -0
  48. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +1 -1
  49. data/lib/rubocop/cop/lint/mixed_case_range.rb +2 -5
  50. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  51. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +2 -2
  52. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +1 -1
  53. data/lib/rubocop/cop/lint/number_conversion.rb +0 -1
  54. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -2
  55. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +106 -0
  56. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -2
  57. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +1 -1
  58. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +1 -1
  59. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +12 -7
  60. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -7
  61. data/lib/rubocop/cop/lint/regexp_as_condition.rb +0 -1
  62. data/lib/rubocop/cop/lint/rescue_type.rb +3 -7
  63. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -0
  64. data/lib/rubocop/cop/lint/self_assignment.rb +8 -10
  65. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  66. data/lib/rubocop/cop/lint/unused_method_argument.rb +18 -2
  67. data/lib/rubocop/cop/lint/useless_defined.rb +55 -0
  68. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -1
  69. data/lib/rubocop/cop/lint/useless_setter_call.rb +14 -25
  70. data/lib/rubocop/cop/lint/void.rb +3 -2
  71. data/lib/rubocop/cop/metrics/class_length.rb +7 -7
  72. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -1
  73. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -2
  74. data/lib/rubocop/cop/mixin/check_assignment.rb +4 -12
  75. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +49 -0
  76. data/lib/rubocop/cop/mixin/dig_help.rb +27 -0
  77. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +5 -9
  78. data/lib/rubocop/cop/mixin/range_help.rb +0 -1
  79. data/lib/rubocop/cop/mixin/target_ruby_version.rb +17 -1
  80. data/lib/rubocop/cop/naming/constant_name.rb +6 -7
  81. data/lib/rubocop/cop/naming/file_name.rb +0 -2
  82. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +11 -12
  83. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +3 -11
  84. data/lib/rubocop/cop/naming/variable_name.rb +3 -4
  85. data/lib/rubocop/cop/naming/variable_number.rb +2 -3
  86. data/lib/rubocop/cop/style/access_modifier_declarations.rb +53 -24
  87. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +1 -1
  88. data/lib/rubocop/cop/style/array_intersect.rb +5 -4
  89. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -1
  90. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  91. data/lib/rubocop/cop/style/case_like_if.rb +8 -11
  92. data/lib/rubocop/cop/style/commented_keyword.rb +11 -1
  93. data/lib/rubocop/cop/style/conditional_assignment.rb +19 -21
  94. data/lib/rubocop/cop/style/constant_visibility.rb +3 -12
  95. data/lib/rubocop/cop/style/dig_chain.rb +90 -0
  96. data/lib/rubocop/cop/style/file_null.rb +73 -0
  97. data/lib/rubocop/cop/style/file_touch.rb +75 -0
  98. data/lib/rubocop/cop/style/for.rb +0 -1
  99. data/lib/rubocop/cop/style/global_vars.rb +1 -3
  100. data/lib/rubocop/cop/style/guard_clause.rb +1 -1
  101. data/lib/rubocop/cop/style/hash_conversion.rb +1 -2
  102. data/lib/rubocop/cop/style/if_inside_else.rb +0 -1
  103. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -2
  104. data/lib/rubocop/cop/style/if_with_semicolon.rb +14 -5
  105. data/lib/rubocop/cop/style/inverse_methods.rb +0 -1
  106. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +2 -2
  107. data/lib/rubocop/cop/style/lambda_call.rb +0 -1
  108. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  109. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +7 -11
  110. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +33 -3
  111. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  112. data/lib/rubocop/cop/style/mutable_constant.rb +4 -5
  113. data/lib/rubocop/cop/style/negated_if_else_condition.rb +6 -4
  114. data/lib/rubocop/cop/style/nested_ternary_operator.rb +5 -4
  115. data/lib/rubocop/cop/style/not.rb +1 -1
  116. data/lib/rubocop/cop/style/one_line_conditional.rb +25 -4
  117. data/lib/rubocop/cop/style/operator_method_call.rb +5 -6
  118. data/lib/rubocop/cop/style/or_assignment.rb +3 -6
  119. data/lib/rubocop/cop/style/parallel_assignment.rb +8 -13
  120. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  121. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -1
  122. data/lib/rubocop/cop/style/redundant_condition.rb +36 -21
  123. data/lib/rubocop/cop/style/redundant_line_continuation.rb +7 -6
  124. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  125. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -0
  126. data/lib/rubocop/cop/style/redundant_return.rb +2 -2
  127. data/lib/rubocop/cop/style/redundant_self.rb +7 -14
  128. data/lib/rubocop/cop/style/redundant_self_assignment.rb +7 -5
  129. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +4 -4
  130. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  131. data/lib/rubocop/cop/style/rescue_modifier.rb +2 -3
  132. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  133. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -1
  134. data/lib/rubocop/cop/style/self_assignment.rb +11 -17
  135. data/lib/rubocop/cop/style/signal_exception.rb +2 -3
  136. data/lib/rubocop/cop/style/single_argument_dig.rb +9 -5
  137. data/lib/rubocop/cop/style/single_line_do_end_block.rb +13 -3
  138. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -3
  139. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  140. data/lib/rubocop/cop/style/string_concatenation.rb +0 -1
  141. data/lib/rubocop/cop/style/swap_values.rb +4 -15
  142. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +4 -4
  143. data/lib/rubocop/cop/style/variable_interpolation.rb +1 -2
  144. data/lib/rubocop/cop/variable_force.rb +4 -10
  145. data/lib/rubocop/cops_documentation_generator.rb +9 -1
  146. data/lib/rubocop/version.rb +1 -1
  147. data/lib/rubocop.rb +8 -0
  148. metadata +17 -8
@@ -33,24 +33,20 @@ module RuboCop
33
33
  branch.begin_type? ? Array(branch).last : branch
34
34
  end
35
35
 
36
- # rubocop:disable Metrics/AbcSize
37
36
  def lhs(node)
38
37
  case node.type
39
38
  when :send
40
39
  lhs_for_send(node)
41
- when :op_asgn
42
- "#{node.children[0].source} #{node.children[1]}= "
43
- when :and_asgn, :or_asgn
44
- "#{node.children[0].source} #{node.loc.operator.source} "
40
+ when :op_asgn, :and_asgn, :or_asgn
41
+ "#{node.assignment_node.source} #{node.operator}= "
45
42
  when :casgn
46
43
  lhs_for_casgn(node)
47
44
  when *ConditionalAssignment::VARIABLE_ASSIGNMENT_TYPES
48
- "#{node.children[0]} = "
45
+ "#{node.name} = "
49
46
  else
50
47
  node.source
51
48
  end
52
49
  end
53
- # rubocop:enable Metrics/AbcSize
54
50
 
55
51
  def indent(cop, source)
56
52
  conf = cop.config.for_cop(END_ALIGNMENT)
@@ -94,11 +90,12 @@ module RuboCop
94
90
  end
95
91
 
96
92
  def lhs_for_casgn(node)
97
- namespace = node.children[0]
98
- if namespace.nil? || namespace.cbase_type?
99
- "#{namespace&.source}#{node.children[1]} = "
93
+ if node.namespace.nil?
94
+ "#{node.name} = "
95
+ elsif node.namespace.cbase_type?
96
+ "::#{node.name} = "
100
97
  else
101
- "#{namespace.source}::#{node.children[1]} = "
98
+ "#{node.namespace.const_name}::#{node.name} = "
102
99
  end
103
100
  end
104
101
 
@@ -210,7 +207,6 @@ module RuboCop
210
207
  class ConditionalAssignment < Base
211
208
  include ConditionalAssignmentHelper
212
209
  include ConfigurableEnforcedStyle
213
- include IgnoredNode
214
210
  extend AutoCorrector
215
211
 
216
212
  MSG = 'Use the return of the conditional for variable assignment and comparison.'
@@ -317,12 +313,14 @@ module RuboCop
317
313
  end
318
314
 
319
315
  def assignment_node(node)
320
- *_variable, assignment = *node
316
+ assignment = node.send_type? ? node.last_argument : node.expression
321
317
 
322
318
  # ignore pseudo-assignments without rhs in for nodes
323
319
  return if node.parent&.for_type?
324
320
 
325
- assignment, = *assignment if assignment.begin_type? && assignment.children.one?
321
+ if assignment.begin_type? && assignment.children.one?
322
+ assignment = assignment.children.first
323
+ end
326
324
 
327
325
  assignment
328
326
  end
@@ -338,7 +336,7 @@ module RuboCop
338
336
  end
339
337
 
340
338
  def move_assignment_inside_condition(corrector, node)
341
- *_assignment, condition = *node
339
+ condition = node.send_type? ? node.last_argument : node.expression
342
340
 
343
341
  if ternary_condition?(condition)
344
342
  TernaryCorrector.move_assignment_inside_condition(corrector, node)
@@ -459,7 +457,7 @@ module RuboCop
459
457
  end
460
458
 
461
459
  def assignment(node)
462
- *_, condition = *node
460
+ condition = node.send_type? ? node.last_argument : node.expression
463
461
 
464
462
  node.source_range.begin.join(condition.source_range.begin)
465
463
  end
@@ -506,7 +504,7 @@ module RuboCop
506
504
  end
507
505
 
508
506
  def move_assignment_inside_condition(corrector, node)
509
- *_var, rhs = *node
507
+ rhs = node.send_type? ? node.last_argument : node.expression
510
508
  if_branch, else_branch = extract_branches(node)
511
509
  assignment = assignment(node)
512
510
 
@@ -537,8 +535,8 @@ module RuboCop
537
535
  end
538
536
 
539
537
  def extract_branches(node)
540
- *_var, rhs = *node
541
- condition, = *rhs if rhs.begin_type? && rhs.children.one?
538
+ rhs = node.send_type? ? node.last_argument : node.expression
539
+ condition = rhs.children.first if rhs.begin_type? && rhs.children.one?
542
540
  _condition, if_branch, else_branch = *(condition || rhs)
543
541
 
544
542
  [if_branch, else_branch]
@@ -567,7 +565,7 @@ module RuboCop
567
565
 
568
566
  def move_assignment_inside_condition(corrector, node)
569
567
  column = node.source_range.column
570
- *_var, condition = *node
568
+ condition = node.send_type? ? node.last_argument : node.expression
571
569
  assignment = assignment(node)
572
570
 
573
571
  corrector.remove(assignment)
@@ -618,7 +616,7 @@ module RuboCop
618
616
 
619
617
  def move_assignment_inside_condition(corrector, node)
620
618
  column = node.source_range.column
621
- *_var, condition = *node
619
+ condition = node.send_type? ? node.last_argument : node.expression
622
620
  assignment = assignment(node)
623
621
 
624
622
  corrector.remove(assignment)
@@ -53,8 +53,7 @@ module RuboCop
53
53
  return if visibility_declaration?(node)
54
54
  return if ignore_modules? && module?(node)
55
55
 
56
- message = message(node)
57
- add_offense(node, message: message)
56
+ add_offense(node, message: format(MSG, constant_name: node.name))
58
57
  end
59
58
 
60
59
  private
@@ -64,13 +63,7 @@ module RuboCop
64
63
  end
65
64
 
66
65
  def module?(node)
67
- node.children.last.class_constructor?
68
- end
69
-
70
- def message(node)
71
- _namespace, constant_name, _value = *node
72
-
73
- format(MSG, constant_name: constant_name)
66
+ node.expression.class_constructor?
74
67
  end
75
68
 
76
69
  def class_or_module_scope?(node)
@@ -85,10 +78,8 @@ module RuboCop
85
78
  end
86
79
 
87
80
  def visibility_declaration?(node)
88
- _namespace, constant_name, _value = *node
89
-
90
81
  node.parent.each_child_node(:send).any? do |child|
91
- visibility_declaration_for?(child, constant_name)
82
+ visibility_declaration_for?(child, node.name)
92
83
  end
93
84
  end
94
85
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Check for chained `dig` calls that can be collapsed into a single `dig`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe because it cannot be guaranteed that the receiver
10
+ # is an `Enumerable` or does not have a nonstandard implementation
11
+ # of `dig`.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # x.dig(:foo).dig(:bar).dig(:baz)
16
+ # x.dig(:foo, :bar).dig(:baz)
17
+ # x.dig(:foo, :bar)&.dig(:baz)
18
+ #
19
+ # # good
20
+ # x.dig(:foo, :bar, :baz)
21
+ #
22
+ # # good - `dig`s cannot be combined
23
+ # x.dig(:foo).bar.dig(:baz)
24
+ #
25
+ class DigChain < Base
26
+ extend AutoCorrector
27
+ include RangeHelp
28
+ include CommentsHelp
29
+ include DigHelp
30
+
31
+ MSG = 'Use `%<replacement>s` instead of chaining.'
32
+ RESTRICT_ON_SEND = %i[dig].freeze
33
+
34
+ def on_send(node)
35
+ return if ignored_node?(node)
36
+ return unless node.loc.dot
37
+ return unless dig?(node)
38
+
39
+ range, arguments = inspect_chain(node)
40
+ return if invalid_arguments?(arguments)
41
+ return unless range
42
+
43
+ register_offense(node, range, arguments)
44
+ end
45
+ alias on_csend on_send
46
+
47
+ private
48
+
49
+ # Walk up the method chain while the receiver is `dig` with arguments.
50
+ def inspect_chain(node)
51
+ arguments = node.arguments.dup
52
+ end_pos = node.source_range.end_pos
53
+
54
+ while dig?((node = node.receiver))
55
+ begin_pos = node.loc.dot ? node.loc.dot.begin_pos + 1 : 0
56
+ arguments.unshift(*node.arguments)
57
+ ignore_node(node)
58
+ end
59
+
60
+ return unless begin_pos
61
+
62
+ [range_between(begin_pos, end_pos), arguments]
63
+ end
64
+
65
+ def invalid_arguments?(arguments)
66
+ # If any of the arguments are arguments forwarding (`...`), it can only be the
67
+ # first argument, or else the resulting code will have a syntax error.
68
+
69
+ return false unless arguments&.any?
70
+
71
+ forwarded_args_index = arguments.index(&:forwarded_args_type?)
72
+ forwarded_args_index && forwarded_args_index < (arguments.size - 1)
73
+ end
74
+
75
+ def register_offense(node, range, arguments)
76
+ arguments = arguments.map(&:source).join(', ')
77
+ replacement = "dig(#{arguments})"
78
+
79
+ add_offense(range, message: format(MSG, replacement: replacement)) do |corrector|
80
+ corrector.replace(range, replacement)
81
+
82
+ comments_in_range(node).reverse_each do |comment|
83
+ corrector.insert_before(node, "#{comment.source}\n")
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Use `File::NULL` instead of hardcoding the null device (`/dev/null` on Unix-like
7
+ # OSes, `NUL` or `NUL:` on Windows), so that code is platform independent.
8
+ # Only looks for full string matches, substrings within a longer string are not
9
+ # considered.
10
+ #
11
+ # NOTE: Uses inside arrays and hashes are ignored.
12
+ #
13
+ # @safety
14
+ # It is possible for a string value to be changed if code is being run
15
+ # on multiple platforms and was previously hardcoded to a specific null device.
16
+ #
17
+ # For example, the following string will change on Windows when changed to
18
+ # `File::NULL`:
19
+ #
20
+ # [source,ruby]
21
+ # ----
22
+ # path = "/dev/null"
23
+ # ----
24
+ #
25
+ # @example
26
+ # # bad
27
+ # '/dev/null'
28
+ # 'NUL'
29
+ # 'NUL:'
30
+ #
31
+ # # good
32
+ # File::NULL
33
+ #
34
+ # # ok - inside an array
35
+ # null_devices = %w[/dev/null nul]
36
+ #
37
+ # # ok - inside a hash
38
+ # { unix: "/dev/null", windows: "nul" }
39
+ class FileNull < Base
40
+ extend AutoCorrector
41
+
42
+ REGEXP = %r{\A(/dev/null|NUL:?)\z}i.freeze
43
+ MSG = 'Use `File::NULL` instead of `%<source>s`.'
44
+
45
+ def on_str(node)
46
+ value = node.value
47
+
48
+ return if invalid_string?(value)
49
+ return if acceptable?(node)
50
+ return unless REGEXP.match?(value)
51
+
52
+ add_offense(node, message: format(MSG, source: value)) do |corrector|
53
+ corrector.replace(node, 'File::NULL')
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def invalid_string?(value)
60
+ value.empty? || !value.valid_encoding?
61
+ end
62
+
63
+ def acceptable?(node)
64
+ # Using a hardcoded null device is acceptable when inside an array or
65
+ # inside a hash to ensure behavior doesn't change.
66
+ return false unless node.parent
67
+
68
+ node.parent.type?(:array, :pair)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for usage of `File.open` in append mode with empty block.
7
+ #
8
+ # Such a usage only creates a new file, but it doesn't update
9
+ # timestamps for an existing file, which might have been the intention.
10
+ #
11
+ # For example, for an existing file `foo.txt`:
12
+ #
13
+ # ruby -e "puts File.mtime('foo.txt')"
14
+ # # 2024-11-26 12:17:23 +0100
15
+ #
16
+ # ruby -e "File.open('foo.txt', 'a') {}"
17
+ #
18
+ # ruby -e "puts File.mtime('foo.txt')"
19
+ # # 2024-11-26 12:17:23 +0100 -> unchanged
20
+ #
21
+ # If the intention was to update timestamps, `FileUtils.touch('foo.txt')`
22
+ # should be used instead.
23
+ #
24
+ # @safety
25
+ # Autocorrection is unsafe for this cop because unlike `File.open`,
26
+ # `FileUtils.touch` updates an existing file's timestamps.
27
+ #
28
+ # @example
29
+ # # bad
30
+ # File.open(filename, 'a') {}
31
+ # File.open(filename, 'a+') {}
32
+ #
33
+ # # good
34
+ # FileUtils.touch(filename)
35
+ #
36
+ class FileTouch < Base
37
+ extend AutoCorrector
38
+
39
+ MSG = 'Use `FileUtils.touch(%<argument>s)` instead of `File.open` in ' \
40
+ 'append mode with empty block.'
41
+
42
+ RESTRICT_ON_SEND = %i[open].freeze
43
+
44
+ APPEND_FILE_MODES = %w[a a+ ab a+b at a+t].to_set.freeze
45
+
46
+ # @!method file_open?(node)
47
+ def_node_matcher :file_open?, <<~PATTERN
48
+ (send
49
+ (const {nil? cbase} :File) :open
50
+ $(...)
51
+ (str %APPEND_FILE_MODES))
52
+ PATTERN
53
+
54
+ def on_send(node)
55
+ filename = file_open?(node)
56
+ parent = node.parent
57
+
58
+ return unless filename
59
+ return unless parent && empty_block?(parent)
60
+
61
+ message = format(MSG, argument: filename.source)
62
+ add_offense(parent, message: message) do |corrector|
63
+ corrector.replace(parent, "FileUtils.touch(#{filename.source})")
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def empty_block?(node)
70
+ node.block_type? && !node.body
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -44,7 +44,6 @@ module RuboCop
44
44
  #
45
45
  class For < Base
46
46
  include ConfigurableEnforcedStyle
47
- include RangeHelp
48
47
  extend AutoCorrector
49
48
 
50
49
  EACH_LENGTH = 'each'.length
@@ -70,9 +70,7 @@ module RuboCop
70
70
  end
71
71
 
72
72
  def check(node)
73
- global_var, = *node
74
-
75
- add_offense(node.loc.name) unless allowed_var?(global_var)
73
+ add_offense(node.loc.name) unless allowed_var?(node.name)
76
74
  end
77
75
  end
78
76
  end
@@ -264,7 +264,7 @@ module RuboCop
264
264
 
265
265
  def and_or_guard_clause?(guard_clause)
266
266
  parent = guard_clause.parent
267
- parent.and_type? || parent.or_type?
267
+ parent.operator_keyword?
268
268
  end
269
269
 
270
270
  def too_long_for_single_line?(node, example)
@@ -111,8 +111,7 @@ module RuboCop
111
111
  end
112
112
 
113
113
  def requires_parens?(node)
114
- (node.call_type? && node.arguments.any? && !node.parenthesized?) ||
115
- node.or_type? || node.and_type?
114
+ (node.call_type? && node.arguments.any? && !node.parenthesized?) || node.operator_keyword?
116
115
  end
117
116
 
118
117
  def multi_argument(node)
@@ -59,7 +59,6 @@ module RuboCop
59
59
  # end
60
60
  #
61
61
  class IfInsideElse < Base
62
- include IgnoredNode
63
62
  include RangeHelp
64
63
  extend AutoCorrector
65
64
 
@@ -155,8 +155,7 @@ module RuboCop
155
155
  end
156
156
 
157
157
  def require_parentheses?(condition)
158
- condition.and_type? || condition.or_type? ||
159
- (condition.send_type? && condition.comparison_method?)
158
+ condition.operator_keyword? || (condition.send_type? && condition.comparison_method?)
160
159
  end
161
160
  end
162
161
  end
@@ -23,6 +23,7 @@ module RuboCop
23
23
 
24
24
  def on_normal_if_unless(node)
25
25
  return if node.parent&.if_type?
26
+ return if part_of_ignored_node?(node)
26
27
 
27
28
  beginning = node.loc.begin
28
29
  return unless beginning&.is?(';')
@@ -32,13 +33,14 @@ module RuboCop
32
33
  add_offense(node, message: message) do |corrector|
33
34
  autocorrect(corrector, node)
34
35
  end
36
+
37
+ ignore_node(node)
35
38
  end
36
39
 
37
40
  private
38
41
 
39
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
40
42
  def message(node)
41
- template = if node.if_branch&.begin_type?
43
+ template = if require_newline?(node)
42
44
  MSG_NEWLINE
43
45
  elsif node.else_branch&.if_type? || node.else_branch&.begin_type? ||
44
46
  use_block_in_branches?(node)
@@ -49,20 +51,27 @@ module RuboCop
49
51
 
50
52
  format(template, expr: node.condition.source)
51
53
  end
52
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
53
54
 
54
55
  def autocorrect(corrector, node)
55
- if node.branches.compact.any?(&:begin_type?) || use_block_in_branches?(node)
56
+ if require_newline?(node) || use_block_in_branches?(node)
56
57
  corrector.replace(node.loc.begin, "\n")
57
58
  else
58
59
  corrector.replace(node, replacement(node))
59
60
  end
60
61
  end
61
62
 
63
+ def require_newline?(node)
64
+ node.branches.compact.any?(&:begin_type?) || use_return_with_argument?(node)
65
+ end
66
+
62
67
  def use_block_in_branches?(node)
63
68
  node.branches.compact.any? { |branch| branch.block_type? || branch.numblock_type? }
64
69
  end
65
70
 
71
+ def use_return_with_argument?(node)
72
+ node.if_branch&.return_type? && node.if_branch&.arguments&.any?
73
+ end
74
+
66
75
  def replacement(node)
67
76
  return correct_elsif(node) if node.else_branch&.if_type?
68
77
 
@@ -111,7 +120,7 @@ module RuboCop
111
120
  end
112
121
 
113
122
  def require_argument_parentheses?(node)
114
- return false unless node.call_type?
123
+ return false if !node.call_type? || node.arithmetic_operation?
115
124
 
116
125
  !node.parenthesized? && node.arguments.any? && !node.method?(:[]) && !node.method?(:[]=)
117
126
  end
@@ -41,7 +41,6 @@ module RuboCop
41
41
  # f != 1
42
42
  # end
43
43
  class InverseMethods < Base
44
- include IgnoredNode
45
44
  include RangeHelp
46
45
  extend AutoCorrector
47
46
 
@@ -6,8 +6,8 @@ module RuboCop
6
6
  # When passing an existing hash as keyword arguments, provide additional arguments
7
7
  # directly rather than using `merge`.
8
8
  #
9
- # Providing arguments directly is more performant, than using `merge`, and
10
- # also leads to a shorter and simpler code.
9
+ # Providing arguments directly is more performant than using `merge`, and
10
+ # also leads to shorter and simpler code.
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -20,7 +20,6 @@ module RuboCop
20
20
  # lambda.(x, y)
21
21
  class LambdaCall < Base
22
22
  include ConfigurableEnforcedStyle
23
- include IgnoredNode
24
23
  extend AutoCorrector
25
24
 
26
25
  MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
@@ -203,7 +203,7 @@ module RuboCop
203
203
  end
204
204
 
205
205
  def logical_operator?(node)
206
- (node.and_type? || node.or_type?) && node.logical_operator?
206
+ node.operator_keyword? && node.logical_operator?
207
207
  end
208
208
 
209
209
  def hash_literal?(node)
@@ -71,9 +71,11 @@ module RuboCop
71
71
  return false if node.receiver
72
72
 
73
73
  any_assignment?(node) do |asgn_node|
74
- next variable_in_mass_assignment?(node.method_name, asgn_node) if asgn_node.masgn_type?
75
-
76
- asgn_node.loc.name.source == node.method_name.to_s
74
+ if asgn_node.masgn_type?
75
+ variable_in_mass_assignment?(node.method_name, asgn_node)
76
+ else
77
+ asgn_node.loc.name.source == node.method_name.to_s
78
+ end
77
79
  end
78
80
  end
79
81
 
@@ -98,20 +100,14 @@ module RuboCop
98
100
  # `obj.method ||= value` parses as (or-asgn (send ...) ...)
99
101
  # which IS an `asgn_node`. Similarly, `obj.method += value` parses
100
102
  # as (op-asgn (send ...) ...), which is also an `asgn_node`.
101
- if asgn_node.shorthand_asgn?
102
- asgn_node, _value = *asgn_node
103
- next if asgn_node.send_type?
104
- end
103
+ next if asgn_node.shorthand_asgn? && asgn_node.lhs.send_type?
105
104
 
106
105
  yield asgn_node
107
106
  end
108
107
  end
109
108
 
110
109
  def variable_in_mass_assignment?(variable_name, node)
111
- mlhs_node, _mrhs_node = *node
112
- var_nodes = *mlhs_node
113
-
114
- var_nodes.any? { |n| n.to_a.first == variable_name }
110
+ node.assignments.any? { |n| n.name == variable_name }
115
111
  end
116
112
 
117
113
  def offense_range(node)
@@ -6,19 +6,49 @@ module RuboCop
6
6
  # Checks for the presence of `method_missing` without also
7
7
  # defining `respond_to_missing?`.
8
8
  #
9
+ # Not defining `respond_to_missing?` will cause metaprogramming
10
+ # methods like `respond_to?` to behave unexpectedly:
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # class StringDelegator
15
+ # def initialize(string)
16
+ # @string = string
17
+ # end
18
+ #
19
+ # def method_missing(name, *args)
20
+ # @string.send(name, *args)
21
+ # end
22
+ # end
23
+ #
24
+ # delegator = StringDelegator.new("foo")
25
+ # # Claims to not respond to `upcase`.
26
+ # delegator.respond_to?(:upcase) # => false
27
+ # # But you can call it.
28
+ # delegator.upcase # => FOO
29
+ # ----
30
+ #
9
31
  # @example
10
32
  # # bad
11
33
  # def method_missing(name, *args)
12
- # # ...
34
+ # if @delegate.respond_to?(name)
35
+ # @delegate.send(name, *args)
36
+ # else
37
+ # super
38
+ # end
13
39
  # end
14
40
  #
15
41
  # # good
16
42
  # def respond_to_missing?(name, include_private)
17
- # # ...
43
+ # @delegate.respond_to?(name) || super
18
44
  # end
19
45
  #
20
46
  # def method_missing(name, *args)
21
- # # ...
47
+ # if @delegate.respond_to?(name)
48
+ # @delegate.send(name, *args)
49
+ # else
50
+ # super
51
+ # end
22
52
  # end
23
53
  #
24
54
  class MissingRespondToMissing < Base
@@ -39,7 +39,7 @@ module RuboCop
39
39
  BRACES_MSG = 'Wrap multiline memoization blocks in `(` and `)`.'
40
40
 
41
41
  def on_or_asgn(node)
42
- _lhs, rhs = *node
42
+ rhs = node.expression
43
43
 
44
44
  return unless bad_rhs?(rhs)
45
45