rubocop 1.39.0 → 1.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +47 -9
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop/comment_config.rb +5 -0
  6. data/lib/rubocop/config.rb +5 -4
  7. data/lib/rubocop/config_loader.rb +5 -5
  8. data/lib/rubocop/config_loader_resolver.rb +1 -1
  9. data/lib/rubocop/cop/base.rb +2 -9
  10. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +22 -6
  11. data/lib/rubocop/cop/internal_affairs/lambda_or_proc.rb +46 -0
  12. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  13. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  14. data/lib/rubocop/cop/lint/assignment_in_condition.rb +11 -1
  15. data/lib/rubocop/cop/lint/deprecated_constants.rb +8 -1
  16. data/lib/rubocop/cop/lint/empty_block.rb +1 -5
  17. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  18. data/lib/rubocop/cop/lint/interpolation_check.rb +4 -3
  19. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -0
  20. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +13 -3
  21. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +15 -6
  22. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +5 -4
  23. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +4 -3
  24. data/lib/rubocop/cop/lint/void.rb +6 -6
  25. data/lib/rubocop/cop/metrics/block_length.rb +9 -4
  26. data/lib/rubocop/cop/metrics/class_length.rb +9 -4
  27. data/lib/rubocop/cop/metrics/method_length.rb +9 -4
  28. data/lib/rubocop/cop/metrics/module_length.rb +9 -4
  29. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +5 -2
  30. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +24 -5
  31. data/lib/rubocop/cop/mixin/statement_modifier.rb +15 -1
  32. data/lib/rubocop/cop/registry.rb +23 -11
  33. data/lib/rubocop/cop/style/array_intersect.rb +111 -0
  34. data/lib/rubocop/cop/style/guard_clause.rb +32 -5
  35. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -1
  36. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  37. data/lib/rubocop/cop/style/redundant_argument.rb +3 -0
  38. data/lib/rubocop/cop/style/redundant_constant_base.rb +72 -0
  39. data/lib/rubocop/cop/style/redundant_return.rb +7 -0
  40. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  41. data/lib/rubocop/cop/style/require_order.rb +88 -0
  42. data/lib/rubocop/cop/style/safe_navigation.rb +35 -6
  43. data/lib/rubocop/cop/style/select_by_regexp.rb +8 -4
  44. data/lib/rubocop/cop/style/string_literals.rb +1 -5
  45. data/lib/rubocop/cop/style/symbol_proc.rb +2 -4
  46. data/lib/rubocop/cop/team.rb +1 -1
  47. data/lib/rubocop/cop/util.rb +1 -1
  48. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  49. data/lib/rubocop/cop/variable_force.rb +20 -29
  50. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  51. data/lib/rubocop/formatter/html_formatter.rb +1 -1
  52. data/lib/rubocop/formatter.rb +3 -1
  53. data/lib/rubocop/optimized_patterns.rb +38 -0
  54. data/lib/rubocop/options.rb +9 -1
  55. data/lib/rubocop/path_util.rb +14 -2
  56. data/lib/rubocop/result_cache.rb +1 -1
  57. data/lib/rubocop/rspec/cop_helper.rb +4 -1
  58. data/lib/rubocop/rspec/support.rb +2 -2
  59. data/lib/rubocop/server/core.rb +1 -1
  60. data/lib/rubocop/target_ruby.rb +1 -1
  61. data/lib/rubocop/version.rb +1 -1
  62. data/lib/rubocop.rb +14 -6
  63. metadata +8 -3
@@ -48,16 +48,17 @@ module RuboCop
48
48
  (send
49
49
  (const _ _) {:#{SEND_METHODS.join(' :')}}
50
50
  ({sym str} $#mixin_method?)
51
- $(const _ _))
51
+ $(const _ _)+)
52
52
  PATTERN
53
53
 
54
54
  def on_send(node)
55
- send_with_mixin_argument?(node) do |method, module_name|
56
- message = message(method, module_name.source, bad_location(node).source)
55
+ send_with_mixin_argument?(node) do |method, module_names|
56
+ module_names_source = module_names.map(&:source).join(', ')
57
+ message = message(method, module_names_source, bad_location(node).source)
57
58
 
58
59
  bad_location = bad_location(node)
59
60
  add_offense(bad_location, message: message) do |corrector|
60
- corrector.replace(bad_location, "#{method} #{module_name.source}")
61
+ corrector.replace(bad_location, "#{method} #{module_names_source}")
61
62
  end
62
63
  end
63
64
  end
@@ -73,10 +73,11 @@ module RuboCop
73
73
  outer_local_variable_node =
74
74
  find_conditional_node_from_ascendant(outer_local_variable.declaration_node)
75
75
  return true unless outer_local_variable_node
76
+ return false unless outer_local_variable_node.conditional?
77
+ return true if variable_node == outer_local_variable_node
76
78
 
77
- outer_local_variable_node.conditional? &&
78
- (variable_node == outer_local_variable_node ||
79
- variable_node == outer_local_variable_node.else_branch)
79
+ outer_local_variable_node.if_type? &&
80
+ variable_node == outer_local_variable_node.else_branch
80
81
  end
81
82
 
82
83
  def variable_node(variable)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for operators, variables, literals, and nonmutating
6
+ # Checks for operators, variables, literals, lambda, proc and nonmutating
7
7
  # methods used in void context.
8
8
  #
9
9
  # @example CheckForMethodsWithNoSideEffects: false (default)
@@ -45,7 +45,7 @@ module RuboCop
45
45
  VAR_MSG = 'Variable `%<var>s` used in void context.'
46
46
  LIT_MSG = 'Literal `%<lit>s` used in void context.'
47
47
  SELF_MSG = '`self` used in void context.'
48
- DEFINED_MSG = '`%<defined>s` used in void context.'
48
+ EXPRESSION_MSG = '`%<expression>s` used in void context.'
49
49
  NONMUTATING_MSG = 'Method `#%<method>s` used in void context. Did you mean `#%<method>s!`?'
50
50
 
51
51
  BINARY_OPERATORS = %i[* / % + - == === != < > <= >= <=>].freeze
@@ -87,7 +87,7 @@ module RuboCop
87
87
  check_literal(expr)
88
88
  check_var(expr)
89
89
  check_self(expr)
90
- check_defined(expr)
90
+ check_void_expression(expr)
91
91
  return unless cop_config['CheckForMethodsWithNoSideEffects']
92
92
 
93
93
  check_nonmutating(expr)
@@ -117,10 +117,10 @@ module RuboCop
117
117
  add_offense(node, message: SELF_MSG)
118
118
  end
119
119
 
120
- def check_defined(node)
121
- return unless node.defined_type?
120
+ def check_void_expression(node)
121
+ return unless node.defined_type? || node.lambda_or_proc?
122
122
 
123
- add_offense(node, message: format(DEFINED_MSG, defined: node.source))
123
+ add_offense(node, message: format(EXPRESSION_MSG, expression: node.source))
124
124
  end
125
125
 
126
126
  def check_nonmutating(node)
@@ -8,8 +8,8 @@ module RuboCop
8
8
  # The maximum allowed length is configurable.
9
9
  # The cop can be configured to ignore blocks passed to certain methods.
10
10
  #
11
- # You can set literals you want to fold with `CountAsOne`.
12
- # Available are: 'array', 'hash', and 'heredoc'. Each literal
11
+ # You can set constructs you want to fold with `CountAsOne`.
12
+ # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct
13
13
  # will be counted as one line regardless of its actual size.
14
14
  #
15
15
  #
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # for backwards compatibility. Please use `AllowedMethods` and `AllowedPatterns`
18
18
  # instead. By default, there are no methods to allowed.
19
19
  #
20
- # @example CountAsOne: ['array', 'heredoc']
20
+ # @example CountAsOne: ['array', 'heredoc', 'method_call']
21
21
  #
22
22
  # something do
23
23
  # array = [ # +1
@@ -33,7 +33,12 @@ module RuboCop
33
33
  # Heredoc
34
34
  # content.
35
35
  # HEREDOC
36
- # end # 5 points
36
+ #
37
+ # foo( # +1
38
+ # 1,
39
+ # 2
40
+ # )
41
+ # end # 6 points
37
42
  #
38
43
  # NOTE: This cop does not apply for `Struct` definitions.
39
44
  class BlockLength < Base
@@ -7,11 +7,11 @@ module RuboCop
7
7
  # Comment lines can optionally be ignored.
8
8
  # The maximum allowed length is configurable.
9
9
  #
10
- # You can set literals you want to fold with `CountAsOne`.
11
- # Available are: 'array', 'hash', and 'heredoc'. Each literal
10
+ # You can set constructs you want to fold with `CountAsOne`.
11
+ # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct
12
12
  # will be counted as one line regardless of its actual size.
13
13
  #
14
- # @example CountAsOne: ['array', 'heredoc']
14
+ # @example CountAsOne: ['array', 'heredoc', 'method_call']
15
15
  #
16
16
  # class Foo
17
17
  # ARRAY = [ # +1
@@ -27,7 +27,12 @@ module RuboCop
27
27
  # Heredoc
28
28
  # content.
29
29
  # HEREDOC
30
- # end # 5 points
30
+ #
31
+ # foo( # +1
32
+ # 1,
33
+ # 2
34
+ # )
35
+ # end # 6 points
31
36
  #
32
37
  #
33
38
  # NOTE: This cop also applies for `Struct` definitions.
@@ -7,8 +7,8 @@ module RuboCop
7
7
  # Comment lines can optionally be allowed.
8
8
  # The maximum allowed length is configurable.
9
9
  #
10
- # You can set literals you want to fold with `CountAsOne`.
11
- # Available are: 'array', 'hash', and 'heredoc'. Each literal
10
+ # You can set constructs you want to fold with `CountAsOne`.
11
+ # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct
12
12
  # will be counted as one line regardless of its actual size.
13
13
  #
14
14
  # NOTE: The `ExcludedMethods` and `IgnoredMethods` configuration is
@@ -16,7 +16,7 @@ module RuboCop
16
16
  # Please use `AllowedMethods` and `AllowedPatterns` instead.
17
17
  # By default, there are no methods to allowed.
18
18
  #
19
- # @example CountAsOne: ['array', 'heredoc']
19
+ # @example CountAsOne: ['array', 'heredoc', 'method_call']
20
20
  #
21
21
  # def m
22
22
  # array = [ # +1
@@ -32,7 +32,12 @@ module RuboCop
32
32
  # Heredoc
33
33
  # content.
34
34
  # HEREDOC
35
- # end # 5 points
35
+ #
36
+ # foo( # +1
37
+ # 1,
38
+ # 2
39
+ # )
40
+ # end # 6 points
36
41
  #
37
42
  class MethodLength < Base
38
43
  include CodeLength
@@ -7,11 +7,11 @@ module RuboCop
7
7
  # Comment lines can optionally be ignored.
8
8
  # The maximum allowed length is configurable.
9
9
  #
10
- # You can set literals you want to fold with `CountAsOne`.
11
- # Available are: 'array', 'hash', and 'heredoc'. Each literal
10
+ # You can set constructs you want to fold with `CountAsOne`.
11
+ # Available are: 'array', 'hash', 'heredoc', and 'method_call'. Each construct
12
12
  # will be counted as one line regardless of its actual size.
13
13
  #
14
- # @example CountAsOne: ['array', 'heredoc']
14
+ # @example CountAsOne: ['array', 'heredoc', 'method_call']
15
15
  #
16
16
  # module M
17
17
  # ARRAY = [ # +1
@@ -27,7 +27,12 @@ module RuboCop
27
27
  # Heredoc
28
28
  # content.
29
29
  # HEREDOC
30
- # end # 5 points
30
+ #
31
+ # foo( # +1
32
+ # 1,
33
+ # 2
34
+ # )
35
+ # end # 6 points
31
36
  #
32
37
  class ModuleLength < Base
33
38
  include CodeLength
@@ -9,7 +9,7 @@ module RuboCop
9
9
  extend NodePattern::Macros
10
10
  include Util
11
11
 
12
- FOLDABLE_TYPES = %i[array hash heredoc].freeze
12
+ FOLDABLE_TYPES = %i[array hash heredoc send csend].freeze
13
13
  CLASSLIKE_TYPES = %i[class module].freeze
14
14
  private_constant :FOLDABLE_TYPES, :CLASSLIKE_TYPES
15
15
 
@@ -39,7 +39,7 @@ module RuboCop
39
39
 
40
40
  private
41
41
 
42
- def build_foldable_checks(types)
42
+ def build_foldable_checks(types) # rubocop:disable Metrics/MethodLength
43
43
  types.map do |type|
44
44
  case type
45
45
  when :array
@@ -48,6 +48,8 @@ module RuboCop
48
48
  ->(node) { node.hash_type? }
49
49
  when :heredoc
50
50
  ->(node) { heredoc_node?(node) }
51
+ when :method_call
52
+ ->(node) { node.call_type? }
51
53
  else
52
54
  raise ArgumentError, "Unknown foldable type: #{type.inspect}. " \
53
55
  "Valid foldable types are: #{FOLDABLE_TYPES.join(', ')}."
@@ -57,6 +59,7 @@ module RuboCop
57
59
 
58
60
  def normalize_foldable_types(types)
59
61
  types.concat(%i[str dstr]) if types.delete(:heredoc)
62
+ types.concat(%i[send csend]) if types.delete(:method_call)
60
63
  types
61
64
  end
62
65
 
@@ -47,6 +47,12 @@ module RuboCop
47
47
 
48
48
  def register_offense(node, message, replacement)
49
49
  add_offense(node.value, message: message) do |corrector|
50
+ if (def_node = def_node_that_require_parentheses(node))
51
+ white_spaces = range_between(def_node.loc.selector.end_pos,
52
+ def_node.first_argument.source_range.begin_pos)
53
+ corrector.replace(white_spaces, '(')
54
+ corrector.insert_after(def_node.arguments.last, ')')
55
+ end
50
56
  corrector.replace(node, replacement)
51
57
  end
52
58
  end
@@ -76,12 +82,25 @@ module RuboCop
76
82
  end
77
83
 
78
84
  def require_hash_value_for_around_hash_literal?(node)
79
- return false unless (ancestor = node.parent.parent)
80
- return false if ancestor.send_type? && ancestor.method?(:[])
85
+ return false unless (send_node = find_ancestor_send_node(node))
86
+
87
+ !node.parent.braces? && !use_element_of_hash_literal_as_receiver?(send_node, node.parent) &&
88
+ use_modifier_form_without_parenthesized_method_call?(send_node)
89
+ end
90
+
91
+ def def_node_that_require_parentheses(node)
92
+ return unless (send_node = find_ancestor_send_node(node))
93
+ return unless without_parentheses_call_expr_follows?(send_node)
94
+
95
+ def_node = node.each_ancestor(:send, :csend).first
96
+
97
+ def_node unless def_node && def_node.arguments.empty?
98
+ end
99
+
100
+ def find_ancestor_send_node(node)
101
+ ancestor = node.parent.parent
81
102
 
82
- !node.parent.braces? && !use_element_of_hash_literal_as_receiver?(ancestor, node.parent) &&
83
- (use_modifier_form_without_parenthesized_method_call?(ancestor) ||
84
- without_parentheses_call_expr_follows?(ancestor))
103
+ ancestor if ancestor&.call_type? && !ancestor&.method?(:[])
85
104
  end
86
105
 
87
106
  def use_element_of_hash_literal_as_receiver?(ancestor, parent)
@@ -48,11 +48,25 @@ module RuboCop
48
48
  end
49
49
 
50
50
  def to_modifier_form(node)
51
- expression = [node.body.source, node.keyword, node.condition.source].compact.join(' ')
51
+ body = if_body_source(node.body)
52
+ expression = [body, node.keyword, node.condition.source].compact.join(' ')
52
53
  parenthesized = parenthesize?(node) ? "(#{expression})" : expression
53
54
  [parenthesized, first_line_comment(node)].compact.join(' ')
54
55
  end
55
56
 
57
+ def if_body_source(if_body)
58
+ if if_body.call_type? &&
59
+ if_body.last_argument&.hash_type? && if_body.last_argument.pairs.last.value_omission?
60
+ "#{method_source(if_body)}(#{if_body.arguments.map(&:source).join(', ')})"
61
+ else
62
+ if_body.source
63
+ end
64
+ end
65
+
66
+ def method_source(if_body)
67
+ range_between(if_body.loc.expression.begin_pos, if_body.loc.selector.end_pos).source
68
+ end
69
+
56
70
  def first_line_comment(node)
57
71
  comment = processed_source.find_comment { |c| same_line?(c, node) }
58
72
  return unless comment
@@ -72,27 +72,27 @@ module RuboCop
72
72
  #
73
73
  # @example gives back a correctly qualified cop name
74
74
  #
75
- # cops = RuboCop::Cop::Cop.all
76
- # cops.
77
- # qualified_cop_name('Layout/EndOfLine') # => 'Layout/EndOfLine'
75
+ # registry = RuboCop::Cop::Registry
76
+ # registry.qualified_cop_name('Layout/EndOfLine', '') # => 'Layout/EndOfLine'
78
77
  #
79
78
  # @example fixes incorrect namespaces
80
79
  #
81
- # cops = RuboCop::Cop::Cop.all
82
- # cops.qualified_cop_name('Lint/EndOfLine') # => 'Layout/EndOfLine'
80
+ # registry = RuboCop::Cop::Registry
81
+ # registry.qualified_cop_name('Lint/EndOfLine', '') # => 'Layout/EndOfLine'
83
82
  #
84
83
  # @example namespaces bare cop identifiers
85
84
  #
86
- # cops = RuboCop::Cop::Cop.all
87
- # cops.qualified_cop_name('EndOfLine') # => 'Layout/EndOfLine'
85
+ # registry = RuboCop::Cop::Registry
86
+ # registry.qualified_cop_name('EndOfLine', '') # => 'Layout/EndOfLine'
88
87
  #
89
88
  # @example passes back unrecognized cop names
90
89
  #
91
- # cops = RuboCop::Cop::Cop.all
92
- # cops.qualified_cop_name('NotACop') # => 'NotACop'
90
+ # registry = RuboCop::Cop::Registry
91
+ # registry.qualified_cop_name('NotACop', '') # => 'NotACop'
93
92
  #
94
93
  # @param name [String] Cop name extracted from config
95
94
  # @param path [String, nil] Path of file that `name` was extracted from
95
+ # @param warn [Boolean] Print a warning if no department given for `name`
96
96
  #
97
97
  # @raise [AmbiguousCopName]
98
98
  # if a bare identifier with two possible namespaces is provided
@@ -158,7 +158,7 @@ module RuboCop
158
158
  end
159
159
 
160
160
  def enabled?(cop, config)
161
- return true if options.fetch(:only, []).include?(cop.cop_name)
161
+ return true if options[:only]&.include?(cop.cop_name)
162
162
 
163
163
  cfg = config.for_cop(cop)
164
164
 
@@ -182,8 +182,12 @@ module RuboCop
182
182
  cops.map(&:cop_name)
183
183
  end
184
184
 
185
+ def cops_for_department(department)
186
+ cops.select { |cop| cop.department == department.to_sym }
187
+ end
188
+
185
189
  def names_for_department(department)
186
- cops.select { |cop| cop.department == department.to_sym }.map(&:cop_name)
190
+ cops_for_department(department).map(&:cop_name)
187
191
  end
188
192
 
189
193
  def ==(other)
@@ -211,6 +215,14 @@ module RuboCop
211
215
  to_h[cop_name].first
212
216
  end
213
217
 
218
+ # When a cop name is given returns a single-element array with the cop class.
219
+ # When a department name is given returns an array with all the cop classes
220
+ # for that department.
221
+ def find_cops_by_directive(directive)
222
+ cop = find_by_cop_name(directive)
223
+ cop ? [cop] : cops_for_department(directive)
224
+ end
225
+
214
226
  def freeze
215
227
  clear_enrollment_queue
216
228
  unqualified_cop_names # build cache
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # In Ruby 3.1, `Array#intersect?` has been added.
7
+ #
8
+ # This cop identifies places where `(array1 & array2).any?`
9
+ # can be replaced by `array1.intersect?(array2)`.
10
+ #
11
+ # The `array1.intersect?(array2)` method is faster than
12
+ # `(array1 & array2).any?` and is more readable.
13
+ #
14
+ # @safety
15
+ # This cop cannot guarantee that array1 and array2 are
16
+ # actually arrays while method `intersect?` is for arrays only.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # (array1 & array2).any?
21
+ # (array1 & array2).empty?
22
+ #
23
+ # # good
24
+ # array1.intersect?(array2)
25
+ # !array1.intersect?(array2)
26
+ #
27
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
28
+ # # good
29
+ # (array1 & array2).present?
30
+ # (array1 & array2).blank?
31
+ #
32
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
33
+ # # bad
34
+ # (array1 & array2).present?
35
+ # (array1 & array2).blank?
36
+ #
37
+ # # good
38
+ # array1.intersect?(array2)
39
+ # !array1.intersect?(array2)
40
+ class ArrayIntersect < Base
41
+ extend AutoCorrector
42
+ extend TargetRubyVersion
43
+
44
+ minimum_target_ruby_version 3.1
45
+
46
+ # @!method regular_bad_intersection_check?(node)
47
+ def_node_matcher :regular_bad_intersection_check?, <<~PATTERN
48
+ (send
49
+ (begin
50
+ (send $(...) :& $(...))
51
+ ) ${:any? :empty?}
52
+ )
53
+ PATTERN
54
+
55
+ # @!method active_support_bad_intersection_check?(node)
56
+ def_node_matcher :active_support_bad_intersection_check?, <<~PATTERN
57
+ (send
58
+ (begin
59
+ (send $(...) :& $(...))
60
+ ) ${:present? :any? :blank? :empty?}
61
+ )
62
+ PATTERN
63
+
64
+ MSG = 'Use `%<negated>s%<receiver>s.intersect?(%<argument>s)` ' \
65
+ 'instead of `(%<receiver>s & %<argument>s).%<method_name>s`.'
66
+ STRAIGHT_METHODS = %i[present? any?].freeze
67
+ NEGATED_METHODS = %i[blank? empty?].freeze
68
+ RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
69
+
70
+ def on_send(node)
71
+ return unless (receiver, argument, method_name = bad_intersection_check?(node))
72
+
73
+ message = message(receiver.source, argument.source, method_name)
74
+
75
+ add_offense(node, message: message) do |corrector|
76
+ if straight?(method_name)
77
+ corrector.replace(node, "#{receiver.source}.intersect?(#{argument.source})")
78
+ else
79
+ corrector.replace(node, "!#{receiver.source}.intersect?(#{argument.source})")
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def bad_intersection_check?(node)
87
+ if active_support_extensions_enabled?
88
+ active_support_bad_intersection_check?(node)
89
+ else
90
+ regular_bad_intersection_check?(node)
91
+ end
92
+ end
93
+
94
+ def straight?(method_name)
95
+ STRAIGHT_METHODS.include?(method_name.to_sym)
96
+ end
97
+
98
+ def message(receiver, argument, method_name)
99
+ negated = straight?(method_name) ? '' : '!'
100
+ format(
101
+ MSG,
102
+ negated: negated,
103
+ receiver: receiver,
104
+ argument: argument,
105
+ method_name: method_name
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -94,6 +94,7 @@ module RuboCop
94
94
  #
95
95
  class GuardClause < Base
96
96
  extend AutoCorrector
97
+ include RangeHelp
97
98
  include MinBodyLength
98
99
  include StatementModifier
99
100
 
@@ -179,13 +180,35 @@ module RuboCop
179
180
  end
180
181
  end
181
182
 
183
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
182
184
  def autocorrect(corrector, node, condition, replacement, guard)
183
185
  corrector.replace(node.loc.keyword.join(condition.loc.expression), replacement)
184
- corrector.remove(node.loc.end)
185
- return unless node.else?
186
186
 
187
- corrector.remove(node.loc.else)
188
- corrector.remove(branch_to_remove(node, guard))
187
+ if_branch = node.if_branch
188
+ else_branch = node.else_branch
189
+
190
+ if if_branch&.send_type? && if_branch.last_argument&.heredoc?
191
+ autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
192
+ elsif else_branch&.send_type? && else_branch.last_argument&.heredoc?
193
+ autocorrect_heredoc_argument(corrector, node, else_branch, if_branch, guard)
194
+ else
195
+ corrector.remove(node.loc.end)
196
+ return unless node.else?
197
+
198
+ corrector.remove(node.loc.else)
199
+ corrector.remove(branch_to_remove(node, guard))
200
+ end
201
+ end
202
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
203
+
204
+ def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
205
+ remove_whole_lines(corrector, leave_branch.source_range)
206
+ remove_whole_lines(corrector, node.loc.else)
207
+ remove_whole_lines(corrector, node.loc.end)
208
+ remove_whole_lines(corrector, branch_to_remove(node, guard).source_range)
209
+ corrector.insert_after(
210
+ heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
211
+ )
189
212
  end
190
213
 
191
214
  def branch_to_remove(node, guard)
@@ -222,7 +245,7 @@ module RuboCop
222
245
  end
223
246
 
224
247
  def accepted_if?(node, ending)
225
- return true if node.modifier_form? || node.ternary?
248
+ return true if node.modifier_form? || node.ternary? || node.elsif_conditional?
226
249
 
227
250
  if ending
228
251
  node.else?
@@ -231,6 +254,10 @@ module RuboCop
231
254
  end
232
255
  end
233
256
 
257
+ def remove_whole_lines(corrector, range)
258
+ corrector.remove(range_by_whole_lines(range, include_final_newline: true))
259
+ end
260
+
234
261
  def allowed_consecutive_conditionals?
235
262
  cop_config.fetch('AllowConsecutiveConditionals', false)
236
263
  end
@@ -39,9 +39,10 @@ module RuboCop
39
39
  def autocorrect(node)
40
40
  return correct_elsif(node) if node.else_branch.if_type?
41
41
 
42
+ then_code = node.if_branch ? node.if_branch.source : 'nil'
42
43
  else_code = node.else_branch ? node.else_branch.source : 'nil'
43
44
 
44
- "#{node.condition.source} ? #{node.if_branch.source} : #{else_code}"
45
+ "#{node.condition.source} ? #{then_code} : #{else_code}"
45
46
  end
46
47
 
47
48
  def correct_elsif(node)
@@ -44,7 +44,7 @@ module RuboCop
44
44
  PATTERN
45
45
 
46
46
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
47
- return unless node.lambda? || node.proc?
47
+ return unless node.lambda_or_proc?
48
48
  return unless nil_return?(node.body)
49
49
 
50
50
  message = format(MSG, type: node.lambda? ? 'lambda' : 'proc')
@@ -13,6 +13,7 @@ module RuboCop
13
13
  # ----
14
14
  # Methods:
15
15
  # join: ''
16
+ # sum: 0
16
17
  # split: ' '
17
18
  # chomp: "\n"
18
19
  # chomp!: "\n"
@@ -33,6 +34,7 @@ module RuboCop
33
34
  # # bad
34
35
  # array.join('')
35
36
  # [1, 2, 3].join("")
37
+ # array.sum(0)
36
38
  # string.split(" ")
37
39
  # "first\nsecond".split(" ")
38
40
  # string.chomp("\n")
@@ -42,6 +44,7 @@ module RuboCop
42
44
  # # good
43
45
  # array.join
44
46
  # [1, 2, 3].join
47
+ # array.sum
45
48
  # string.split
46
49
  # "first second".split
47
50
  # string.chomp