rubocop 1.28.2 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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