rubocop 1.66.1 → 1.68.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +55 -6
  4. data/config/internal_affairs.yml +11 -0
  5. data/lib/rubocop/cached_data.rb +12 -4
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
  7. data/lib/rubocop/cli/command/execute_runner.rb +1 -1
  8. data/lib/rubocop/cli/command/lsp.rb +2 -2
  9. data/lib/rubocop/cli/command/version.rb +2 -2
  10. data/lib/rubocop/config_loader_resolver.rb +3 -3
  11. data/lib/rubocop/config_validator.rb +2 -1
  12. data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
  13. data/lib/rubocop/cop/base.rb +6 -2
  14. data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
  15. data/lib/rubocop/cop/cop.rb +8 -0
  16. data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
  17. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  18. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
  19. data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
  20. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
  21. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
  22. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
  23. data/lib/rubocop/cop/internal_affairs.rb +16 -0
  24. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  25. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  26. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  27. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
  28. data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
  29. data/lib/rubocop/cop/layout/leading_comment_space.rb +56 -1
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
  31. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
  32. data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
  33. data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
  34. data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
  35. data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
  36. data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
  37. data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
  38. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  39. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  40. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
  41. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
  42. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
  43. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
  44. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
  45. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
  46. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
  47. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +107 -41
  48. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  49. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
  50. data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
  51. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
  52. data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
  53. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
  54. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
  55. data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  57. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  58. data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
  59. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  60. data/lib/rubocop/cop/offense.rb +4 -5
  61. data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
  62. data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
  63. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
  64. data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
  65. data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
  66. data/lib/rubocop/cop/style/block_delimiters.rb +31 -3
  67. data/lib/rubocop/cop/style/collection_compact.rb +10 -10
  68. data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
  69. data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
  70. data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
  71. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  72. data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
  73. data/lib/rubocop/cop/style/endless_method.rb +1 -14
  74. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  75. data/lib/rubocop/cop/style/guard_clause.rb +15 -2
  76. data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
  77. data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
  78. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  79. data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -3
  80. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
  81. data/lib/rubocop/cop/style/lambda.rb +1 -1
  82. data/lib/rubocop/cop/style/map_into_array.rb +59 -8
  83. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
  84. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  85. data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
  86. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  87. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  88. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
  89. data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
  90. data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
  91. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  92. data/lib/rubocop/cop/style/redundant_line_continuation.rb +23 -5
  93. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -11
  94. data/lib/rubocop/cop/style/require_order.rb +1 -1
  95. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
  96. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
  97. data/lib/rubocop/cop/style/safe_navigation.rb +104 -50
  98. data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
  99. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
  100. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  101. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  102. data/lib/rubocop/cop/style/ternary_parentheses.rb +26 -5
  103. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  104. data/lib/rubocop/cop/team.rb +8 -1
  105. data/lib/rubocop/cop/util.rb +1 -1
  106. data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
  107. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  108. data/lib/rubocop/cop/variable_force/variable.rb +5 -1
  109. data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
  110. data/lib/rubocop/cops_documentation_generator.rb +81 -40
  111. data/lib/rubocop/file_finder.rb +9 -4
  112. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  113. data/lib/rubocop/lsp/runtime.rb +2 -0
  114. data/lib/rubocop/lsp/server.rb +0 -1
  115. data/lib/rubocop/rspec/expect_offense.rb +1 -0
  116. data/lib/rubocop/runner.rb +17 -6
  117. data/lib/rubocop/server/cache.rb +6 -1
  118. data/lib/rubocop/server/core.rb +1 -0
  119. data/lib/rubocop/target_ruby.rb +13 -13
  120. data/lib/rubocop/version.rb +28 -7
  121. data/lib/rubocop/yaml_duplication_checker.rb +20 -27
  122. data/lib/rubocop.rb +10 -0
  123. metadata +13 -4
@@ -3,13 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Layout
6
- # Checks for indentation that doesn't use the specified number
7
- # of spaces.
6
+ # Checks for indentation that doesn't use the specified number of spaces.
7
+ # The indentation width can be configured using the `Width` setting. The default width is 2.
8
8
  #
9
- # See also the IndentationConsistency cop which is the companion to this
10
- # one.
9
+ # See also the `Layout/IndentationConsistency` cop which is the companion to this one.
11
10
  #
12
- # @example
11
+ # @example Width: 2 (default)
13
12
  # # bad
14
13
  # class A
15
14
  # def test
@@ -49,18 +49,57 @@ module RuboCop
49
49
  # #ruby=2.7.0
50
50
  # #ruby-gemset=myproject
51
51
  #
52
+ # @example AllowRBSInlineAnnotation: false (default)
53
+ #
54
+ # # bad
55
+ #
56
+ # include Enumerable #[Integer]
57
+ #
58
+ # attr_reader :name #: String
59
+ # attr_reader :age #: Integer?
60
+ #
61
+ # @example AllowRBSInlineAnnotation: true
62
+ #
63
+ # # good
64
+ #
65
+ # include Enumerable #[Integer]
66
+ #
67
+ # attr_reader :name #: String
68
+ # attr_reader :age #: Integer?
69
+ #
70
+ # @example AllowSteepAnnotation: false (default)
71
+ #
72
+ # # bad
73
+ # [1, 2, 3].each_with_object([]) do |n, list| #$ Array[Integer]
74
+ # list << n
75
+ # end
76
+ #
77
+ # name = 'John' #: String
78
+ #
79
+ # @example AllowSteepAnnotation: true
80
+ #
81
+ # # good
82
+ #
83
+ # [1, 2, 3].each_with_object([]) do |n, list| #$ Array[Integer]
84
+ # list << n
85
+ # end
86
+ #
87
+ # name = 'John' #: String
88
+ #
52
89
  class LeadingCommentSpace < Base
53
90
  include RangeHelp
54
91
  extend AutoCorrector
55
92
 
56
93
  MSG = 'Missing space after `#`.'
57
94
 
58
- def on_new_investigation
95
+ def on_new_investigation # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
59
96
  processed_source.comments.each do |comment|
60
97
  next unless /\A(?!#\+\+|#--)(#+[^#\s=])/.match?(comment.text)
61
98
  next if comment.loc.line == 1 && allowed_on_first_line?(comment)
62
99
  next if doxygen_comment_style?(comment)
63
100
  next if gemfile_ruby_comment?(comment)
101
+ next if rbs_inline_annotation?(comment)
102
+ next if steep_annotation?(comment)
64
103
 
65
104
  add_offense(comment) do |corrector|
66
105
  expr = comment.source_range
@@ -115,6 +154,22 @@ module RuboCop
115
154
  def gemfile_ruby_comment?(comment)
116
155
  allow_gemfile_ruby_comment? && ruby_comment_in_gemfile?(comment)
117
156
  end
157
+
158
+ def allow_rbs_inline_annotation?
159
+ cop_config['AllowRBSInlineAnnotation']
160
+ end
161
+
162
+ def rbs_inline_annotation?(comment)
163
+ allow_rbs_inline_annotation? && comment.text.start_with?(/#:|#\[.+\]/)
164
+ end
165
+
166
+ def allow_steep_annotation?
167
+ cop_config['AllowSteepAnnotation']
168
+ end
169
+
170
+ def steep_annotation?(comment)
171
+ allow_steep_annotation? && comment.text.start_with?(/#[$:]/)
172
+ end
118
173
  end
119
174
  end
120
175
  end
@@ -33,12 +33,12 @@ module RuboCop
33
33
  private
34
34
 
35
35
  def offense_range(node, begin_pos)
36
- if reference_variable_with_brackets?(node)
37
- receiver_end_pos = node.receiver.source_range.end_pos
38
- selector_begin_pos = node.loc.selector.begin_pos
39
- return if receiver_end_pos >= selector_begin_pos
40
- return if dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
36
+ receiver_end_pos = node.receiver.source_range.end_pos
37
+ selector_begin_pos = node.loc.selector.begin_pos
38
+ return if receiver_end_pos >= selector_begin_pos
39
+ return if dot_before_brackets?(node, receiver_end_pos, selector_begin_pos)
41
40
 
41
+ if reference_variable_with_brackets?(node)
42
42
  range_between(receiver_end_pos, selector_begin_pos)
43
43
  elsif node.method?(:[]=)
44
44
  offense_range_for_assignment(node, begin_pos)
@@ -82,6 +82,10 @@ module RuboCop
82
82
  include RangeHelp
83
83
  extend AutoCorrector
84
84
 
85
+ def self.autocorrect_incompatible_with
86
+ [Style::BlockDelimiters]
87
+ end
88
+
85
89
  def on_block(node)
86
90
  return if node.keywords?
87
91
 
@@ -57,6 +57,7 @@ module RuboCop
57
57
  # # good
58
58
  # (a.foo)..(b.bar)
59
59
  class AmbiguousRange < Base
60
+ include RationalLiteral
60
61
  extend AutoCorrector
61
62
 
62
63
  MSG = 'Wrap complex range boundaries with parentheses to avoid ambiguity.'
@@ -79,12 +80,14 @@ module RuboCop
79
80
  yield range.end if range.end
80
81
  end
81
82
 
83
+ # rubocop:disable Metrics/CyclomaticComplexity
82
84
  def acceptable?(node)
83
85
  node.begin_type? ||
84
- node.literal? ||
86
+ node.literal? || rational_literal?(node) ||
85
87
  node.variable? || node.const_type? || node.self_type? ||
86
88
  (node.call_type? && acceptable_call?(node))
87
89
  end
90
+ # rubocop:enable Metrics/CyclomaticComplexity
88
91
 
89
92
  def acceptable_call?(node)
90
93
  return true if node.unary_operation?
@@ -17,8 +17,7 @@ module RuboCop
17
17
  class BigDecimalNew < Base
18
18
  extend AutoCorrector
19
19
 
20
- MSG = '`%<double_colon>sBigDecimal.new()` is deprecated. ' \
21
- 'Use `%<double_colon>sBigDecimal()` instead.'
20
+ MSG = '`BigDecimal.new()` is deprecated. Use `BigDecimal()` instead.'
22
21
  RESTRICT_ON_SEND = %i[new].freeze
23
22
 
24
23
  # @!method big_decimal_new(node)
@@ -28,13 +27,11 @@ module RuboCop
28
27
  PATTERN
29
28
 
30
29
  def on_send(node)
31
- big_decimal_new(node) do |captured_value|
32
- double_colon = captured_value ? '::' : ''
33
- message = format(MSG, double_colon: double_colon)
34
-
35
- add_offense(node.loc.selector, message: message) do |corrector|
30
+ big_decimal_new(node) do |cbase|
31
+ add_offense(node.loc.selector) do |corrector|
36
32
  corrector.remove(node.loc.selector)
37
33
  corrector.remove(node.loc.dot)
34
+ corrector.remove(cbase) if cbase
38
35
  end
39
36
  end
40
37
  end
@@ -36,7 +36,7 @@ module RuboCop
36
36
  return unless boolean_symbol?(node)
37
37
 
38
38
  parent = node.parent
39
- return if parent&.array_type? && parent&.percent_literal?(:symbol)
39
+ return if parent&.array_type? && parent.percent_literal?(:symbol)
40
40
 
41
41
  add_offense(node, message: format(MSG, boolean: node.value)) do |corrector|
42
42
  autocorrect(corrector, node)
@@ -15,6 +15,9 @@ module RuboCop
15
15
  # With `IgnoreConstantBranches: true`, branches are not registered
16
16
  # as offenses if they return a constant value.
17
17
  #
18
+ # With `IgnoreDuplicateElseBranch: true`, in conditionals with multiple branches,
19
+ # duplicate 'else' branches are not registered as offenses.
20
+ #
18
21
  # @example
19
22
  # # bad
20
23
  # if foo
@@ -83,21 +86,37 @@ module RuboCop
83
86
  # else MEDIUM_SIZE
84
87
  # end
85
88
  #
89
+ # @example IgnoreDuplicateElseBranch: true
90
+ # # good
91
+ # if foo
92
+ # do_foo
93
+ # elsif bar
94
+ # do_bar
95
+ # else
96
+ # do_foo
97
+ # end
98
+ #
86
99
  class DuplicateBranch < Base
87
100
  MSG = 'Duplicate branch body detected.'
88
101
 
89
102
  def on_branching_statement(node)
90
- branches(node).each_with_object(Set.new) do |branch, previous|
91
- next unless consider_branch?(branch)
103
+ branches = branches(node)
104
+ branches.each_with_object(Set.new) do |branch, previous|
105
+ next unless consider_branch?(branches, branch)
92
106
 
93
107
  add_offense(offense_range(branch)) unless previous.add?(branch)
94
108
  end
95
109
  end
96
- alias on_if on_branching_statement
97
110
  alias on_case on_branching_statement
98
111
  alias on_case_match on_branching_statement
99
112
  alias on_rescue on_branching_statement
100
113
 
114
+ def on_if(node)
115
+ # Ignore 'elsif' nodes, because we don't want to check them separately whether
116
+ # the 'else' branch is duplicated. We want to check only on the outermost conditional.
117
+ on_branching_statement(node) unless node.elsif?
118
+ end
119
+
101
120
  private
102
121
 
103
122
  def offense_range(duplicate_branch)
@@ -118,10 +137,14 @@ module RuboCop
118
137
  node.branches.compact
119
138
  end
120
139
 
121
- def consider_branch?(branch)
140
+ def consider_branch?(branches, branch)
122
141
  return false if ignore_literal_branches? && literal_branch?(branch)
123
142
  return false if ignore_constant_branches? && const_branch?(branch)
124
143
 
144
+ if ignore_duplicate_else_branches? && duplicate_else_branch?(branches, branch)
145
+ return false
146
+ end
147
+
125
148
  true
126
149
  end
127
150
 
@@ -133,6 +156,10 @@ module RuboCop
133
156
  cop_config.fetch('IgnoreConstantBranches', false)
134
157
  end
135
158
 
159
+ def ignore_duplicate_else_branches?
160
+ cop_config.fetch('IgnoreDuplicateElseBranch', false)
161
+ end
162
+
136
163
  def literal_branch?(branch) # rubocop:disable Metrics/CyclomaticComplexity
137
164
  return false if !branch.literal? || branch.xstr_type?
138
165
  return true if branch.basic_literal?
@@ -147,6 +174,14 @@ module RuboCop
147
174
  def const_branch?(branch)
148
175
  branch.const_type?
149
176
  end
177
+
178
+ def duplicate_else_branch?(branches, branch)
179
+ return false unless (parent = branch.parent)
180
+
181
+ branches.size > 2 &&
182
+ branch.equal?(branches.last) &&
183
+ parent.respond_to?(:else?) && parent.else?
184
+ end
150
185
  end
151
186
  end
152
187
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for duplicate literal, constant, or variable elements in Set.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # Set[:foo, :bar, :foo]
12
+ #
13
+ # # good
14
+ # Set[:foo, :bar]
15
+ #
16
+ # # bad
17
+ # Set.new([:foo, :bar, :foo])
18
+ #
19
+ # # good
20
+ # Set.new([:foo, :bar])
21
+ #
22
+ # # bad
23
+ # [:foo, :bar, :foo].to_set
24
+ #
25
+ # # good
26
+ # [:foo, :bar].to_set
27
+ #
28
+ class DuplicateSetElement < Base
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Remove the duplicate element in Set.'
32
+ RESTRICT_ON_SEND = %i[\[\] new to_set].freeze
33
+
34
+ # @!method set_init_elements(node)
35
+ def_node_matcher :set_init_elements, <<~PATTERN
36
+ {
37
+ (send (const {nil? cbase} :Set) :[] $...)
38
+ (send (const {nil? cbase} :Set) :new (array $...))
39
+ (call (array $...) :to_set)
40
+ }
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ return unless (set_elements = set_init_elements(node))
45
+
46
+ seen_elements = Set[]
47
+
48
+ set_elements.each_with_index do |set_element, index|
49
+ # NOTE: Skip due to the possibility of corner cases where Set elements
50
+ # may have changing return values if they are not literals, constants, or variables.
51
+ next if !set_element.literal? && !set_element.const_type? && !set_element.variable?
52
+
53
+ if seen_elements.include?(set_element)
54
+ register_offense(set_element, set_elements[index - 1])
55
+ else
56
+ seen_elements << set_element
57
+ end
58
+ end
59
+ end
60
+ alias on_csend on_send
61
+
62
+ private
63
+
64
+ def register_offense(current_element, prev_element)
65
+ add_offense(current_element) do |corrector|
66
+ range = prev_element.source_range.end.join(current_element.source_range.end)
67
+
68
+ corrector.remove(range)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -40,9 +40,6 @@ module RuboCop
40
40
  # cleanup
41
41
  # end
42
42
  class EnsureReturn < Base
43
- extend AutoCorrector
44
- include RangeHelp
45
-
46
43
  MSG = 'Do not return from an `ensure` block.'
47
44
 
48
45
  def on_ensure(node)
@@ -91,7 +91,7 @@ module RuboCop
91
91
  next if !argument || argument.hash_type?
92
92
 
93
93
  add_offense(
94
- argument.source_range, message: message(i, argument.source)
94
+ argument, message: message(i, argument.source)
95
95
  ) do |corrector|
96
96
  autocorrect(corrector, node)
97
97
  end
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # # good - using BigDecimal
19
19
  # x.to_d == 0.1.to_d
20
20
  #
21
- # # good - comparing against zero
21
+ # # good - comparing against zero
22
22
  # x == 0.0
23
23
  # x != 0.0
24
24
  #
@@ -28,7 +28,7 @@ module RuboCop
28
28
  FOR_METHOD = ' Or, if they were intended to be separate method ' \
29
29
  'arguments, separate them with a comma.'
30
30
 
31
- # rubocop:disable Metrics/AbcSize
31
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
32
32
  def on_dstr(node)
33
33
  each_bad_cons(node) do |lhs_node, rhs_node|
34
34
  range = lhs_node.source_range.join(rhs_node.source_range)
@@ -40,13 +40,19 @@ module RuboCop
40
40
  end
41
41
 
42
42
  add_offense(range, message: message) do |corrector|
43
- range = lhs_node.source_range.end.join(rhs_node.source_range.begin)
43
+ if lhs_node.value == ''
44
+ corrector.remove(lhs_node)
45
+ elsif rhs_node.value == ''
46
+ corrector.remove(rhs_node)
47
+ else
48
+ range = lhs_node.source_range.end.join(rhs_node.source_range.begin)
44
49
 
45
- corrector.replace(range, ' + ')
50
+ corrector.replace(range, ' + ')
51
+ end
46
52
  end
47
53
  end
48
54
  end
49
- # rubocop:enable Metrics/AbcSize
55
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
50
56
 
51
57
  private
52
58
 
@@ -29,25 +29,16 @@ module RuboCop
29
29
 
30
30
  MSG = '`it` calls without arguments will refer to the first block param in Ruby 3.4; ' \
31
31
  'use `it()` or `self.it`.'
32
+ RESTRICT_ON_SEND = %i[it].freeze
32
33
 
33
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
34
- return unless (body = node.body)
35
- return unless node.arguments.empty_and_without_delimiters?
34
+ def on_send(node)
35
+ return unless (block_node = node.each_ancestor(:block).first)
36
+ return unless block_node.arguments.empty_and_without_delimiters?
36
37
 
37
- if body.send_type? && deprecated_it_method?(body)
38
- add_offense(body)
39
- else
40
- body.each_descendant(:send).each do |send_node|
41
- next unless deprecated_it_method?(send_node)
42
-
43
- add_offense(send_node)
44
- end
45
- end
38
+ add_offense(node) if deprecated_it_method?(node)
46
39
  end
47
40
 
48
41
  def deprecated_it_method?(node)
49
- return false unless node.method?(:it)
50
-
51
42
  !node.receiver && node.arguments.empty? && !node.parenthesized? && !node.block_literal?
52
43
  end
53
44
  end
@@ -30,6 +30,8 @@ module RuboCop
30
30
  # interpolation should not be removed if the expanded value
31
31
  # contains a space character.
32
32
  expanded_value = autocorrected_value(final_node)
33
+ expanded_value = handle_special_regexp_chars(begin_node, expanded_value)
34
+
33
35
  return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
34
36
 
35
37
  add_offense(final_node) do |corrector|
@@ -77,6 +79,27 @@ module RuboCop
77
79
  end
78
80
  # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
79
81
 
82
+ def handle_special_regexp_chars(begin_node, value)
83
+ parent_node = begin_node.parent
84
+
85
+ return value unless parent_node.regexp_type? && parent_node.slash_literal? && value['/']
86
+
87
+ # When a literal string containing a forward slash preceded by backslashes
88
+ # is interpolated inside a regexp, the number of resultant backslashes in the
89
+ # compiled Regexp is `(2(n+1) / 4)+1`, where `n` is the number of backslashes
90
+ # inside the interpolation.
91
+ # ie. 0-2 backslashes is compiled to 1, 3-6 is compiled to 3, etc.
92
+ # This maintains that same behavior in order to ensure the Regexp behavior
93
+ # does not change upon removing the interpolation.
94
+ value.gsub(%r{(\\*)/}) do
95
+ backslashes = Regexp.last_match[1]
96
+ backslash_count = backslashes.length
97
+ needed_backslashes = (2 * ((backslash_count + 1) / 4)) + 1
98
+
99
+ "#{'\\' * needed_backslashes}/"
100
+ end
101
+ end
102
+
80
103
  def autocorrected_value_for_string(node)
81
104
  if node.source.start_with?("'", '%q')
82
105
  node.children.last.inspect[1..-2]
@@ -150,7 +173,7 @@ module RuboCop
150
173
 
151
174
  def ends_heredoc_line?(node)
152
175
  grandparent = node.parent.parent
153
- return false unless grandparent&.dstr_type? && grandparent&.heredoc?
176
+ return false unless grandparent&.dstr_type? && grandparent.heredoc?
154
177
 
155
178
  line = processed_source.lines[node.last_line - 1]
156
179
  line.size == node.loc.last_column + 1
@@ -161,7 +184,7 @@ module RuboCop
161
184
  return false unless parent.dstr_type? || parent.dsym_type?
162
185
 
163
186
  grandparent = parent.parent
164
- grandparent&.array_type? && grandparent&.percent_literal?
187
+ grandparent&.array_type? && grandparent.percent_literal?
165
188
  end
166
189
  end
167
190
  end
@@ -134,6 +134,7 @@ module RuboCop
134
134
 
135
135
  corrector.replace(node.child_nodes.first.loc.name, 'FileUtils')
136
136
  corrector.replace(node.loc.selector, replacement_method(node))
137
+ corrector.insert_before(node.last_argument, 'mode: ') if require_mode_keyword?(node)
137
138
  end
138
139
 
139
140
  def replacement_method(node)
@@ -152,6 +153,12 @@ module RuboCop
152
153
  force_method_name?(node) || force_option?(node)
153
154
  end
154
155
 
156
+ def require_mode_keyword?(node)
157
+ return false unless node.receiver.const_name == 'Dir'
158
+
159
+ replacement_method(node) == 'mkdir_p' && node.arguments.length == 2
160
+ end
161
+
155
162
  def force_option?(node)
156
163
  node.arguments.any? { |arg| force?(arg) }
157
164
  end
@@ -37,9 +37,7 @@ module RuboCop
37
37
  private
38
38
 
39
39
  def valid_context?(node)
40
- unless node.arguments.one? && first_argument_starts_with_left_parenthesis?(node)
41
- return true
42
- end
40
+ return true unless node.arguments.one? && node.first_argument.parenthesized_call?
43
41
  return true if first_argument_block_type?(node.first_argument)
44
42
 
45
43
  node.operator_method? || node.setter_method? || chained_calls?(node) ||
@@ -51,11 +49,12 @@ module RuboCop
51
49
  end
52
50
 
53
51
  def valid_first_argument?(first_arg)
54
- first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg)
52
+ first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg) ||
53
+ compound_range?(first_arg)
55
54
  end
56
55
 
57
- def first_argument_starts_with_left_parenthesis?(node)
58
- node.first_argument.source.start_with?('(')
56
+ def compound_range?(first_arg)
57
+ first_arg.range_type? && first_arg.parenthesized_call?
59
58
  end
60
59
 
61
60
  def chained_calls?(node)
@@ -131,7 +131,7 @@ module RuboCop
131
131
  private
132
132
 
133
133
  def assume_receiver_instance_exists?(receiver)
134
- return true if receiver.const_type? && !receiver.source.match?(SNAKE_CASE)
134
+ return true if receiver.const_type? && !receiver.short_name.match?(SNAKE_CASE)
135
135
 
136
136
  receiver.literal? && !receiver.nil_type?
137
137
  end
@@ -38,6 +38,8 @@ module RuboCop
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
41
+ return unless require_safe_navigation?(node)
42
+
41
43
  bad_method?(node) do |safe_nav, method|
42
44
  return if nil_methods.include?(method) || PLUS_MINUS_METHODS.include?(node.method_name)
43
45
 
@@ -52,6 +54,13 @@ module RuboCop
52
54
 
53
55
  private
54
56
 
57
+ def require_safe_navigation?(node)
58
+ parent = node.parent
59
+ return true unless parent&.and_type?
60
+
61
+ parent.rhs != node || parent.lhs.receiver != parent.rhs.receiver
62
+ end
63
+
55
64
  # @param [Parser::Source::Range] offense_range
56
65
  # @param [RuboCop::AST::SendNode] send_node
57
66
  # @return [String]