rubocop 1.28.2 → 1.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/config/default.yml +21 -6
  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 +0 -3
  19. data/lib/rubocop/cop/lint/loop.rb +1 -1
  20. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  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/predicate_name.rb +2 -2
  33. data/lib/rubocop/cop/naming/variable_name.rb +9 -0
  34. data/lib/rubocop/cop/naming/variable_number.rb +10 -0
  35. data/lib/rubocop/cop/security/yaml_load.rb +1 -1
  36. data/lib/rubocop/cop/style/alias.rb +3 -3
  37. data/lib/rubocop/cop/style/and_or.rb +1 -1
  38. data/lib/rubocop/cop/style/bisected_attr_accessor/macro.rb +1 -1
  39. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  40. data/lib/rubocop/cop/style/character_literal.rb +1 -1
  41. data/lib/rubocop/cop/style/collection_compact.rb +3 -3
  42. data/lib/rubocop/cop/style/date_time.rb +1 -1
  43. data/lib/rubocop/cop/style/double_negation.rb +28 -2
  44. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -1
  45. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  46. data/lib/rubocop/cop/style/env_home.rb +56 -0
  47. data/lib/rubocop/cop/style/fetch_env_var.rb +238 -11
  48. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -2
  49. data/lib/rubocop/cop/style/map_to_hash.rb +0 -3
  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 +1 -1
  57. data/lib/rubocop/cop/style/redundant_condition.rb +77 -7
  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/slicing_with_range.rb +0 -3
  63. data/lib/rubocop/cop/style/string_chars.rb +1 -1
  64. data/lib/rubocop/cop/style/trivial_accessors.rb +7 -8
  65. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  66. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  67. data/lib/rubocop/formatter/html_formatter.rb +2 -9
  68. data/lib/rubocop/formatter/markdown_formatter.rb +76 -0
  69. data/lib/rubocop/magic_comment.rb +4 -3
  70. data/lib/rubocop/options.rb +4 -3
  71. data/lib/rubocop/result_cache.rb +1 -1
  72. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  73. data/lib/rubocop/rspec/parallel_formatter.rb +1 -1
  74. data/lib/rubocop/rspec/shared_contexts.rb +1 -5
  75. data/lib/rubocop/runner.rb +1 -1
  76. data/lib/rubocop/string_interpreter.rb +4 -4
  77. data/lib/rubocop/target_ruby.rb +8 -2
  78. data/lib/rubocop/version.rb +1 -1
  79. data/lib/rubocop.rb +3 -1
  80. metadata +17 -8
  81. data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +0 -45
@@ -70,7 +70,7 @@ module RuboCop
70
70
  !(method_name.start_with?(prefix) && # cheap check to avoid allocating Regexp
71
71
  method_name.match?(/^#{prefix}[^0-9]/)) ||
72
72
  method_name == expected_name(method_name, prefix) ||
73
- method_name.end_with?('=') ||
73
+ method_name.end_with?('=') || # rubocop:todo InternalAffairs/MethodNameEndWith
74
74
  allowed_method?(method_name)
75
75
  end
76
76
 
@@ -80,7 +80,7 @@ module RuboCop
80
80
  else
81
81
  method_name.dup
82
82
  end
83
- new_name << '?' unless method_name.end_with?('?')
83
+ new_name << '?' unless method_name.end_with?('?') # rubocop:todo InternalAffairs/MethodNameEndWith
84
84
  new_name
85
85
  end
86
86
 
@@ -19,12 +19,21 @@ module RuboCop
19
19
  #
20
20
  # # good
21
21
  # fooBar = 1
22
+ #
23
+ # @example AllowedPatterns: ['_v\d+\z']
24
+ # # good
25
+ # :release_v1
22
26
  class VariableName < Base
23
27
  include AllowedIdentifiers
24
28
  include ConfigurableNaming
29
+ include AllowedPattern
25
30
 
26
31
  MSG = 'Use %<style>s for variable names.'
27
32
 
33
+ def valid_name?(node, name, given_style = style)
34
+ super || matches_allowed_pattern?(name)
35
+ end
36
+
28
37
  def on_lvasgn(node)
29
38
  name, = *node
30
39
  return unless name
@@ -96,12 +96,21 @@ module RuboCop
96
96
  # # good
97
97
  # expect(Open3).to receive(:capture3)
98
98
  #
99
+ # @example AllowedPatterns: ['_v\d+\z']
100
+ # # good
101
+ # :some_sym_v1
102
+ #
99
103
  class VariableNumber < Base
100
104
  include AllowedIdentifiers
101
105
  include ConfigurableNumbering
106
+ include AllowedPattern
102
107
 
103
108
  MSG = 'Use %<style>s for %<identifier_type>s numbers.'
104
109
 
110
+ def valid_name?(node, name, given_style = style)
111
+ super || matches_allowed_pattern?(name)
112
+ end
113
+
105
114
  def on_arg(node)
106
115
  @node = node
107
116
  name, = *node
@@ -112,6 +121,7 @@ module RuboCop
112
121
  alias on_lvasgn on_arg
113
122
  alias on_ivasgn on_arg
114
123
  alias on_cvasgn on_arg
124
+ alias on_gvasgn on_arg
115
125
 
116
126
  def on_def(node)
117
127
  @node = node
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # NOTE: Ruby 3.1+ (Psych 4) uses `Psych.load` as `Psych.safe_load` by default.
11
11
  #
12
12
  # @safety
13
- # The behaviour of the code might change depending on what was
13
+ # The behavior of the code might change depending on what was
14
14
  # in the YAML payload, since `YAML.safe_load` is more restrictive.
15
15
  #
16
16
  # @example
@@ -76,7 +76,7 @@ module RuboCop
76
76
 
77
77
  def add_offense_for_args(node, &block)
78
78
  existing_args = node.children.map(&:source).join(' ')
79
- preferred_args = node.children.map { |a| a.source[1..-1] }.join(' ')
79
+ preferred_args = node.children.map { |a| a.source[1..] }.join(' ')
80
80
  arg_ranges = node.children.map(&:source_range)
81
81
  msg = format(MSG_SYMBOL_ARGS, prefer: preferred_args, current: existing_args)
82
82
  add_offense(arg_ranges.reduce(&:join), message: msg, &block)
@@ -134,8 +134,8 @@ module RuboCop
134
134
  end
135
135
 
136
136
  def correct_alias_with_symbol_args(corrector, node)
137
- corrector.replace(node.new_identifier, node.new_identifier.source[1..-1])
138
- corrector.replace(node.old_identifier, node.old_identifier.source[1..-1])
137
+ corrector.replace(node.new_identifier, node.new_identifier.source[1..])
138
+ corrector.replace(node.old_identifier, node.old_identifier.source[1..])
139
139
  end
140
140
 
141
141
  # @!method identifier(node)
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # @safety
11
11
  # Auto-correction is unsafe because there is a different operator precedence
12
12
  # between logical operators (`&&` and `||`) and semantic operators (`and` and `or`),
13
- # and that might change the behaviour.
13
+ # and that might change the behavior.
14
14
  #
15
15
  # @example EnforcedStyle: always
16
16
  # # bad
@@ -18,7 +18,7 @@ module RuboCop
18
18
 
19
19
  def initialize(node)
20
20
  @node = node
21
- @attrs = node.arguments.map { |attr| [attr.source, attr] }.to_h
21
+ @attrs = node.arguments.to_h { |attr| [attr.source, attr] }
22
22
  @bisection = []
23
23
  end
24
24
 
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # @safety
10
10
  # This cop is unsafe. `case` statements use `===` for equality,
11
11
  # so if the original conditional used a different equality operator, the
12
- # behaviour may be different.
12
+ # behavior may be different.
13
13
  #
14
14
  # @example
15
15
  # # bad
@@ -33,7 +33,7 @@ module RuboCop
33
33
  end
34
34
 
35
35
  def autocorrect(corrector, node)
36
- string = node.source[1..-1]
36
+ string = node.source[1..]
37
37
 
38
38
  # special character like \n
39
39
  # or ' which needs to use "" or be escaped.
@@ -70,7 +70,7 @@ module RuboCop
70
70
  def on_send(node)
71
71
  return unless (range = offense_range(node))
72
72
 
73
- good = good_method_name(node.method_name)
73
+ good = good_method_name(node)
74
74
  message = format(MSG, good: good, bad: range.source)
75
75
 
76
76
  add_offense(range, message: message) { |corrector| corrector.replace(range, good) }
@@ -94,8 +94,8 @@ module RuboCop
94
94
  end
95
95
  end
96
96
 
97
- def good_method_name(method_name)
98
- if method_name.to_s.end_with?('!')
97
+ def good_method_name(node)
98
+ if node.bang_method?
99
99
  'compact!'
100
100
  else
101
101
  'compact'
@@ -11,7 +11,7 @@ module RuboCop
11
11
  #
12
12
  # @safety
13
13
  # Autocorrection is not safe, because `DateTime` and `Time` do not have
14
- # exactly the same behaviour, although in most cases the autocorrection
14
+ # exactly the same behavior, although in most cases the autocorrection
15
15
  # will be fine.
16
16
  #
17
17
  # @example
@@ -37,11 +37,27 @@ module RuboCop
37
37
  # !!return_value
38
38
  # end
39
39
  #
40
+ # define_method :foo? do
41
+ # !!return_value
42
+ # end
43
+ #
44
+ # define_singleton_method :foo? do
45
+ # !!return_value
46
+ # end
47
+ #
40
48
  # @example EnforcedStyle: forbidden
41
49
  # # bad
42
50
  # def foo?
43
51
  # !!return_value
44
52
  # end
53
+ #
54
+ # define_method :foo? do
55
+ # !!return_value
56
+ # end
57
+ #
58
+ # define_singleton_method :foo? do
59
+ # !!return_value
60
+ # end
45
61
  class DoubleNegation < Base
46
62
  include ConfigurableEnforcedStyle
47
63
  extend AutoCorrector
@@ -73,22 +89,32 @@ module RuboCop
73
89
  return false unless (def_node = find_def_node_from_ascendant(node))
74
90
 
75
91
  conditional_node = find_conditional_node_from_ascendant(node)
76
- last_child = find_last_child(def_node.body)
92
+ last_child = find_last_child(def_node.send_type? ? def_node : def_node.body)
77
93
 
78
94
  if conditional_node
79
95
  double_negative_condition_return_value?(node, last_child, conditional_node)
80
96
  else
81
- last_child.last_line == node.last_line
97
+ last_child.last_line <= node.last_line
82
98
  end
83
99
  end
84
100
 
85
101
  def find_def_node_from_ascendant(node)
86
102
  return unless (parent = node.parent)
87
103
  return parent if parent.def_type? || parent.defs_type?
104
+ return node.parent.child_nodes.first if define_mehod?(parent)
88
105
 
89
106
  find_def_node_from_ascendant(node.parent)
90
107
  end
91
108
 
109
+ def define_mehod?(node)
110
+ return false unless node.block_type?
111
+
112
+ child = node.child_nodes.first
113
+ return false unless child.send_type?
114
+
115
+ child.method?(:define_method) || child.method?(:define_singleton_method)
116
+ end
117
+
92
118
  def find_conditional_node_from_ascendant(node)
93
119
  return unless (parent = node.parent)
94
120
  return parent if parent.conditional?
@@ -69,7 +69,7 @@ module RuboCop
69
69
 
70
70
  keep_first_when_comment(case_range, corrector)
71
71
 
72
- when_nodes[1..-1].each do |when_node|
72
+ when_nodes[1..].each do |when_node|
73
73
  corrector.replace(when_node.loc.keyword, 'elsif')
74
74
  end
75
75
  end
@@ -119,7 +119,7 @@ module RuboCop
119
119
  # because the braces are interpreted as a block. We will have
120
120
  # to rewrite the arguments to wrap them in parenthesis.
121
121
  args = node.parent.arguments
122
- "(#{args[1..-1].map(&:source).unshift('{}').join(', ')})"
122
+ "(#{args[1..].map(&:source).unshift('{}').join(', ')})"
123
123
  else
124
124
  '{}'
125
125
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for consistent usage of `ENV['HOME']`. If `nil` is used as
7
+ # the second argument of `ENV.fetch`, it is treated as a bad case like `ENV[]`.
8
+ #
9
+ # @safety
10
+ # The cop is unsafe because the result when `nil` is assigned to `ENV['HOME']` changes:
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # ENV['HOME'] = nil
15
+ # ENV['HOME'] # => nil
16
+ # Dir.home # => '/home/foo'
17
+ # ----
18
+ #
19
+ # @example
20
+ #
21
+ # # bad
22
+ # ENV['HOME']
23
+ # ENV.fetch('HOME', nil)
24
+ #
25
+ # # good
26
+ # Dir.home
27
+ #
28
+ # # good
29
+ # ENV.fetch('HOME', default)
30
+ #
31
+ class EnvHome < Base
32
+ extend AutoCorrector
33
+
34
+ MSG = 'Use `Dir.home` instead.'
35
+ RESTRICT_ON_SEND = %i[[] fetch].freeze
36
+
37
+ # @!method env_home?(node)
38
+ def_node_matcher :env_home?, <<~PATTERN
39
+ (send
40
+ (const {cbase nil?} :ENV) {:[] :fetch}
41
+ (str "HOME")
42
+ ...)
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ return unless env_home?(node)
47
+ return if node.arguments.count == 2 && !node.arguments[1].nil_type?
48
+
49
+ add_offense(node) do |corrector|
50
+ corrector.replace(node, 'Dir.home')
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -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,82 @@ 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?
100
+ return true if node.parent.if_type?
56
101
 
57
- node.parent.if_type? || (node.parent.send_type? && node.parent.prefix_bang?)
102
+ node.parent.send_type? && (node.parent.prefix_bang? || node.parent.comparison_method?)
103
+ end
104
+
105
+ def offensive?(node)
106
+ !(allowed_var?(node) || allowable_use?(node))
107
+ end
108
+
109
+ def default_to_rhs?(node)
110
+ operand_of_or?(node) && !right_end_of_or_chains?(node) && rhs_can_be_default_value?(node)
58
111
  end
59
112
 
60
113
  # Check if the node is a receiver and receives a message with dot syntax.
61
114
  def message_chained_with_dot?(node)
62
115
  return false if node.root?
63
116
 
64
- node.parent.send_type? && node.parent.children.first == node && node.parent.dot?
117
+ parent = node.parent
118
+ return false if !parent.call_type? || parent.children.first != node
119
+
120
+ parent.dot? || parent.safe_navigation?
65
121
  end
66
122
 
67
123
  # The following are allowed cases:
@@ -84,6 +140,177 @@ module RuboCop
84
140
  lhs, _method, _rhs = *parent
85
141
  node == lhs
86
142
  end
143
+
144
+ def left_end_of_or_chains?(node)
145
+ return false unless operand_of_or?(node)
146
+
147
+ node.parent.lhs == node
148
+ end
149
+
150
+ def right_end_of_or_chains?(node)
151
+ !(left_end_of_or_chains?(node) || node.parent&.parent&.or_type?)
152
+ end
153
+
154
+ # Returns the node and expression of the rightmost `ENV[]` in `||` chains.
155
+ # e.g.,
156
+ # `ENV['X'] || y || z || ENV['A'] || b`
157
+ # ^^^^^^^^ Matches this one
158
+ def rightmost_offense_in_or_chains(base_node)
159
+ or_nodes = [base_node.parent]
160
+
161
+ while (grand_parent = or_nodes.last&.parent)&.or_type?
162
+ or_nodes << grand_parent
163
+ end
164
+
165
+ # Finds the rightmost `ENV[]` in `||` chains.
166
+ or_node = or_nodes.reverse.find do |n|
167
+ env_with_bracket?(n.rhs)
168
+ end
169
+
170
+ or_node ? or_node.rhs : base_node
171
+ end
172
+
173
+ def no_env_with_bracket_in_descendants?(node)
174
+ !env_with_bracket_in_descendants?(node)
175
+ end
176
+
177
+ def conterpart_rhs_of(node)
178
+ left_end_of_or_chains?(node) ? node.parent.rhs : node.parent.parent.rhs
179
+ end
180
+
181
+ # Looks ahead to the `ENV[]` that must be corrected first, avoiding a cross correction.
182
+ # ```
183
+ # ENV['X'] || y.map do |a|
184
+ # a.map do |b|
185
+ # ENV['Z'] + b
186
+ # ^^^^^^^^ This must be corrected first.
187
+ # end
188
+ # end
189
+ # ```
190
+ def lookahead_target_node(base_node)
191
+ return base_node unless operand_of_or?(base_node)
192
+
193
+ candidate_node = rightmost_offense_in_or_chains(base_node)
194
+ return candidate_node if right_end_of_or_chains?(candidate_node)
195
+
196
+ counterpart_rhs = conterpart_rhs_of(candidate_node)
197
+ return candidate_node if no_env_with_bracket_in_descendants?(counterpart_rhs)
198
+
199
+ new_base_node = counterpart_rhs.each_descendant.find do |d|
200
+ env_with_bracket?(d) && offensive?(d)
201
+ end
202
+ return candidate_node unless new_base_node
203
+
204
+ lookahead_target_node(new_base_node)
205
+ end
206
+
207
+ def rhs_can_be_default_value?(node)
208
+ !rhs_is_block_control?(node)
209
+ end
210
+
211
+ def rhs_is_block_control?(node)
212
+ block_control?(conterpart_rhs_of(node))
213
+ end
214
+
215
+ def new_code_default_nil(expression)
216
+ "ENV.fetch(#{expression.source}, nil)"
217
+ end
218
+
219
+ def new_code_default_rhs_single_line(node, expression)
220
+ parent = node.parent
221
+ if parent.rhs.basic_literal?
222
+ "ENV.fetch(#{expression.source}, #{parent.rhs.source})"
223
+ else
224
+ "ENV.fetch(#{expression.source}) { #{parent.rhs.source} }"
225
+ end
226
+ end
227
+
228
+ def new_code_default_rhs_multiline(node, expression)
229
+ env_indent = indent(node.parent)
230
+ default = node.parent.rhs.source.split("\n").map do |line|
231
+ "#{env_indent}#{line}"
232
+ end.join("\n")
233
+ <<~NEW_CODE.chomp
234
+ ENV.fetch(#{expression.source}) do
235
+ #{configured_indentation}#{default}
236
+ #{env_indent}end
237
+ NEW_CODE
238
+ end
239
+
240
+ def new_code_default_rhs(node, expression)
241
+ if node.parent.rhs.single_line?
242
+ new_code_default_rhs_single_line(node, expression)
243
+ else
244
+ new_code_default_rhs_multiline(node, expression)
245
+ end
246
+ end
247
+
248
+ def default_rhs(node, expression)
249
+ if left_end_of_or_chains?(node)
250
+ default_rhs_in_same_or(node, expression)
251
+ else
252
+ default_rhs_in_outer_or(node, expression)
253
+ end
254
+ end
255
+
256
+ # Adds an offense and sets `nil` to the default value of `ENV.fetch`.
257
+ # `ENV['X']` --> `ENV.fetch('X', nil)`
258
+ def default_nil(node, expression)
259
+ message = format(MSG_DEFAULT_NIL, key: expression.source)
260
+
261
+ add_offense(node, message: message) do |corrector|
262
+ corrector.replace(node, new_code_default_nil(expression))
263
+ end
264
+ end
265
+
266
+ # Adds an offense and makes the RHS the default value of `ENV.fetch`.
267
+ # `ENV['X'] || y` --> `ENV.fetch('X') { y }`
268
+ def default_rhs_in_same_or(node, expression)
269
+ template = message_template_for(node.parent.rhs)
270
+ message = format(template,
271
+ key: expression.source,
272
+ default: first_line_of(node.parent.rhs.source))
273
+
274
+ add_offense(node, message: message) do |corrector|
275
+ corrector.replace(node.parent, new_code_default_rhs(node, expression))
276
+ end
277
+ end
278
+
279
+ # Adds an offense and makes the RHS the default value of `ENV.fetch`.
280
+ # `z || ENV['X'] || y` --> `z || ENV.fetch('X') { y }`
281
+ def default_rhs_in_outer_or(node, expression)
282
+ parent = node.parent
283
+ grand_parent = parent.parent
284
+
285
+ template = message_template_for(grand_parent.rhs)
286
+ message = format(template,
287
+ key: expression.source,
288
+ default: first_line_of(grand_parent.rhs.source))
289
+
290
+ add_offense(node, message: message) do |corrector|
291
+ lhs_code = parent.lhs.source
292
+ rhs_code = new_code_default_rhs(parent, expression)
293
+ corrector.replace(grand_parent, "#{lhs_code} || #{rhs_code}")
294
+ end
295
+ end
296
+
297
+ def message_template_for(rhs)
298
+ if rhs.multiline?
299
+ MSG_DEFAULT_RHS_MULTILINE_BLOCK
300
+ elsif rhs.basic_literal?
301
+ MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH
302
+ else
303
+ MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK
304
+ end
305
+ end
306
+
307
+ def configured_indentation
308
+ ' ' * (config.for_cop('Layout/IndentationWidth')['Width'] || 2)
309
+ end
310
+
311
+ def first_line_of(source)
312
+ source.split("\n").first
313
+ end
87
314
  end
88
315
  end
89
316
  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
@@ -29,11 +29,8 @@ module RuboCop
29
29
  #
30
30
  class MapToHash < Base
31
31
  extend AutoCorrector
32
- extend TargetRubyVersion
33
32
  include RangeHelp
34
33
 
35
- minimum_target_ruby_version 2.6
36
-
37
34
  MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
38
35
  RESTRICT_ON_SEND = %i[to_h].freeze
39
36
 
@@ -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