rubocop 1.31.1 → 1.33.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/default.yml +68 -16
  4. data/config/obsoletion.yml +23 -1
  5. data/lib/rubocop/cache_config.rb +29 -0
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +2 -2
  7. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config.rb +1 -1
  10. data/lib/rubocop/config_finder.rb +68 -0
  11. data/lib/rubocop/config_loader.rb +5 -45
  12. data/lib/rubocop/config_loader_resolver.rb +1 -1
  13. data/lib/rubocop/config_obsoletion/changed_parameter.rb +5 -0
  14. data/lib/rubocop/config_obsoletion/parameter_rule.rb +4 -0
  15. data/lib/rubocop/config_obsoletion.rb +7 -2
  16. data/lib/rubocop/cop/base.rb +1 -1
  17. data/lib/rubocop/cop/generator.rb +4 -0
  18. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +60 -0
  19. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  20. data/lib/rubocop/cop/layout/block_end_newline.rb +32 -5
  21. data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -1
  22. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +4 -3
  23. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +3 -3
  24. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +57 -13
  25. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  26. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +4 -1
  27. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +45 -0
  28. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +26 -6
  29. data/lib/rubocop/cop/lint/debugger.rb +11 -1
  30. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -4
  31. data/lib/rubocop/cop/lint/empty_conditional_body.rb +60 -1
  32. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +60 -25
  33. data/lib/rubocop/cop/lint/number_conversion.rb +28 -6
  34. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -0
  35. data/lib/rubocop/cop/lint/require_range_parentheses.rb +57 -0
  36. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +35 -1
  37. data/lib/rubocop/cop/metrics/abc_size.rb +3 -1
  38. data/lib/rubocop/cop/metrics/block_length.rb +6 -6
  39. data/lib/rubocop/cop/metrics/method_length.rb +8 -7
  40. data/lib/rubocop/cop/mixin/allowed_methods.rb +15 -1
  41. data/lib/rubocop/cop/mixin/allowed_pattern.rb +9 -1
  42. data/lib/rubocop/cop/mixin/check_line_breakable.rb +4 -0
  43. data/lib/rubocop/cop/mixin/comments_help.rb +5 -1
  44. data/lib/rubocop/cop/mixin/def_node.rb +2 -7
  45. data/lib/rubocop/cop/mixin/method_complexity.rb +4 -9
  46. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +6 -13
  47. data/lib/rubocop/cop/mixin/percent_array.rb +60 -1
  48. data/lib/rubocop/cop/naming/predicate_name.rb +30 -1
  49. data/lib/rubocop/cop/style/block_delimiters.rb +26 -7
  50. data/lib/rubocop/cop/style/class_and_module_children.rb +4 -4
  51. data/lib/rubocop/cop/style/class_equality_comparison.rb +50 -3
  52. data/lib/rubocop/cop/style/empty_else.rb +37 -0
  53. data/lib/rubocop/cop/style/empty_heredoc.rb +73 -0
  54. data/lib/rubocop/cop/style/fetch_env_var.rb +10 -177
  55. data/lib/rubocop/cop/style/format_string_token.rb +25 -6
  56. data/lib/rubocop/cop/style/hash_except.rb +0 -4
  57. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +2 -0
  58. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -1
  59. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -7
  60. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +19 -2
  61. data/lib/rubocop/cop/style/module_function.rb +2 -2
  62. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +9 -0
  63. data/lib/rubocop/cop/style/numeric_predicate.rb +43 -9
  64. data/lib/rubocop/cop/style/redundant_condition.rb +19 -4
  65. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
  66. data/lib/rubocop/cop/style/redundant_sort.rb +21 -6
  67. data/lib/rubocop/cop/style/semicolon.rb +27 -3
  68. data/lib/rubocop/cop/style/symbol_array.rb +2 -3
  69. data/lib/rubocop/cop/style/symbol_proc.rb +35 -7
  70. data/lib/rubocop/cop/style/trivial_accessors.rb +3 -0
  71. data/lib/rubocop/cop/style/word_array.rb +2 -3
  72. data/lib/rubocop/options.rb +3 -6
  73. data/lib/rubocop/rake_task.rb +5 -1
  74. data/lib/rubocop/result_cache.rb +22 -20
  75. data/lib/rubocop/rspec/shared_contexts.rb +14 -14
  76. data/lib/rubocop/rspec/support.rb +14 -0
  77. data/lib/rubocop/runner.rb +4 -0
  78. data/lib/rubocop/server/cache.rb +33 -1
  79. data/lib/rubocop/server/cli.rb +19 -2
  80. data/lib/rubocop/server/client_command/base.rb +1 -1
  81. data/lib/rubocop/version.rb +1 -1
  82. data/lib/rubocop.rb +4 -2
  83. metadata +12 -7
  84. data/lib/rubocop/cop/mixin/ignored_methods.rb +0 -52
@@ -44,13 +44,26 @@ module RuboCop
44
44
  no_acceptable_style! if brackets_required
45
45
 
46
46
  bracketed_array = build_bracketed_array(node)
47
- message = format(self.class::ARRAY_MSG, prefer: bracketed_array)
47
+ message = build_message_for_bracketed_array(bracketed_array)
48
48
 
49
49
  add_offense(node, message: message) do |corrector|
50
50
  corrector.replace(node, bracketed_array)
51
51
  end
52
52
  end
53
53
 
54
+ # @param [String] preferred_array_code
55
+ # @return [String]
56
+ def build_message_for_bracketed_array(preferred_array_code)
57
+ format(
58
+ self.class::ARRAY_MSG,
59
+ prefer: if preferred_array_code.include?("\n")
60
+ 'an array literal `[...]`'
61
+ else
62
+ "`#{preferred_array_code}`"
63
+ end
64
+ )
65
+ end
66
+
54
67
  def check_bracketed_array(node, literal_prefix)
55
68
  return if allowed_bracket_array?(node)
56
69
 
@@ -63,6 +76,52 @@ module RuboCop
63
76
  percent_literal_corrector.correct(corrector, node, literal_prefix)
64
77
  end
65
78
  end
79
+
80
+ # @param [RuboCop::AST::ArrayNode] node
81
+ # @param [Array<String>] elements
82
+ # @return [String]
83
+ def build_bracketed_array_with_appropriate_whitespace(elements:, node:)
84
+ [
85
+ '[',
86
+ whitespace_leading(node),
87
+ elements.join(",#{whitespace_between(node)}"),
88
+ whitespace_trailing(node),
89
+ ']'
90
+ ].join
91
+ end
92
+
93
+ # Provides whitespace between elements for building a bracketed array.
94
+ # %w[ a b c ]
95
+ # ^^^
96
+ # @param [RuboCop::AST::ArrayNode] node
97
+ # @return [String]
98
+ def whitespace_between(node)
99
+ if node.children.length >= 2
100
+ node.source[
101
+ node.children[0].loc.expression.end_pos...node.children[1].loc.expression.begin_pos
102
+ ]
103
+ else
104
+ ' '
105
+ end
106
+ end
107
+
108
+ # Provides leading whitespace for building a bracketed array.
109
+ # %w[ a b c ]
110
+ # ^^
111
+ # @param [RuboCop::AST::ArrayNode] node
112
+ # @return [String]
113
+ def whitespace_leading(node)
114
+ node.source[node.loc.begin.end_pos...node.children[0].loc.expression.begin_pos]
115
+ end
116
+
117
+ # Provides trailing whitespace for building a bracketed array.
118
+ # %w[ a b c ]
119
+ # ^^^^
120
+ # @param [RuboCop::AST::ArrayNode] node
121
+ # @return [String]
122
+ def whitespace_trailing(node)
123
+ node.source[node.children[-1].loc.expression.end_pos...node.loc.end.begin_pos]
124
+ end
66
125
  end
67
126
  end
68
127
  end
@@ -3,7 +3,30 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Naming
6
- # Makes sure that predicates are named properly.
6
+ # Checks that predicate methods names end with a question mark and
7
+ # do not start with a forbidden prefix.
8
+ #
9
+ # A method is determined to be a predicate method if its name starts
10
+ # with one of the prefixes defined in the `NamePrefix` configuration.
11
+ # You can change what prefixes are considered by changing this option.
12
+ # Any method name that starts with one of these prefixes is required by
13
+ # the cop to end with a `?`. Other methods can be allowed by adding to
14
+ # the `AllowedMethods` configuration.
15
+ #
16
+ # NOTE: The `is_a?` method is allowed by default.
17
+ #
18
+ # If `ForbiddenPrefixes` is set, methods that start with the configured
19
+ # prefixes will not be allowed and will be removed by autocorrection.
20
+ #
21
+ # In other words, if `ForbiddenPrefixes` is empty, a method named `is_foo`
22
+ # will register an offense only due to the lack of question mark (and will be
23
+ # autocorrected to `is_foo?`). If `ForbiddenPrefixes` contains `is_`,
24
+ # `is_foo` will register an offense both because the ? is missing and because of
25
+ # the `is_` prefix, and will be corrected to `foo?`.
26
+ #
27
+ # NOTE: `ForbiddenPrefixes` is only applied to prefixes in `NamePrefix`;
28
+ # a prefix in the former but not the latter will not be considered by
29
+ # this cop.
7
30
  #
8
31
  # @example
9
32
  # # bad
@@ -27,6 +50,12 @@ module RuboCop
27
50
  # # good
28
51
  # def value?
29
52
  # end
53
+ #
54
+ # @example AllowedMethods: ['is_a?'] (default)
55
+ # # good
56
+ # def is_a?(value)
57
+ # end
58
+ #
30
59
  class PredicateName < Base
31
60
  include AllowedMethods
32
61
 
@@ -10,7 +10,7 @@ module RuboCop
10
10
  # Methods that can be either procedural or functional and cannot be
11
11
  # categorised from their usage alone is ignored.
12
12
  # `lambda`, `proc`, and `it` are their defaults.
13
- # Additional methods can be added to the `IgnoredMethods`.
13
+ # Additional methods can be added to the `AllowedMethods`.
14
14
  #
15
15
  # @example EnforcedStyle: line_count_based (default)
16
16
  # # bad - single line block
@@ -66,7 +66,7 @@ module RuboCop
66
66
  # x
67
67
  # }.inspect
68
68
  #
69
- # # The AllowBracesOnProceduralOneLiners option is ignored unless the
69
+ # # The AllowBracesOnProceduralOneLiners option is allowed unless the
70
70
  # # EnforcedStyle is set to `semantic`. If so:
71
71
  #
72
72
  # # If the AllowBracesOnProceduralOneLiners option is unspecified, or
@@ -116,7 +116,7 @@ module RuboCop
116
116
  #
117
117
  # # Methods listed in the BracesRequiredMethods list, such as 'sig'
118
118
  # # in this example, will require `{...}` braces. This option takes
119
- # # precedence over all other configurations except IgnoredMethods.
119
+ # # precedence over all other configurations except AllowedMethods.
120
120
  #
121
121
  # # bad
122
122
  # sig do
@@ -138,7 +138,7 @@ module RuboCop
138
138
  # puts foo
139
139
  # end
140
140
  #
141
- # @example IgnoredMethods: ['lambda', 'proc', 'it' ] (default)
141
+ # @example AllowedMethods: ['lambda', 'proc', 'it' ] (default)
142
142
  #
143
143
  # # good
144
144
  # foo = lambda do |x|
@@ -149,9 +149,26 @@ module RuboCop
149
149
  # x * 100
150
150
  # end
151
151
  #
152
+ # @example AllowedPatterns: [] (default)
153
+ #
154
+ # # bad
155
+ # things.map { |thing|
156
+ # something = thing.some_method
157
+ # process(something)
158
+ # }
159
+ #
160
+ # @example AllowedPatterns: [/map/]
161
+ #
162
+ # # good
163
+ # things.map { |thing|
164
+ # something = thing.some_method
165
+ # process(something)
166
+ # }
167
+ #
152
168
  class BlockDelimiters < Base
153
169
  include ConfigurableEnforcedStyle
154
- include IgnoredMethods
170
+ include AllowedMethods
171
+ include AllowedPattern
155
172
  include RangeHelp
156
173
  extend AutoCorrector
157
174
 
@@ -330,12 +347,14 @@ module RuboCop
330
347
  end
331
348
 
332
349
  def special_method?(method_name)
333
- ignored_method?(method_name) || braces_required_method?(method_name)
350
+ allowed_method?(method_name) ||
351
+ matches_allowed_pattern?(method_name) ||
352
+ braces_required_method?(method_name)
334
353
  end
335
354
 
336
355
  def special_method_proper_block_style?(node)
337
356
  method_name = node.method_name
338
- return true if ignored_method?(method_name)
357
+ return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
339
358
  return node.braces? if braces_required_method?(method_name)
340
359
  end
341
360
 
@@ -117,10 +117,10 @@ module RuboCop
117
117
  end
118
118
 
119
119
  def remove_end(corrector, body)
120
- range = range_between(
121
- body.loc.end.begin_pos - leading_spaces(body).size,
122
- body.loc.end.end_pos + 1
123
- )
120
+ remove_begin_pos = body.loc.end.begin_pos - leading_spaces(body).size
121
+ adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
122
+ range = range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)
123
+
124
124
  corrector.remove(range)
125
125
  end
126
126
 
@@ -5,6 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces the use of `Object#instance_of?` instead of class comparison
7
7
  # for equality.
8
+ # `==`, `equal?`, and `eql?` methods are allowed by default.
9
+ # These are customizable with `AllowedMethods` option.
8
10
  #
9
11
  # @example
10
12
  # # bad
@@ -16,9 +18,50 @@ module RuboCop
16
18
  # # good
17
19
  # var.instance_of?(Date)
18
20
  #
21
+ # @example AllowedMethods: [] (default)
22
+ # # good
23
+ # var.instance_of?(Date)
24
+ #
25
+ # # bad
26
+ # var.class == Date
27
+ # var.class.equal?(Date)
28
+ # var.class.eql?(Date)
29
+ # var.class.name == 'Date'
30
+ #
31
+ # @example AllowedMethods: [`==`]
32
+ # # good
33
+ # var.instance_of?(Date)
34
+ # var.class == Date
35
+ # var.class.name == 'Date'
36
+ #
37
+ # # bad
38
+ # var.class.equal?(Date)
39
+ # var.class.eql?(Date)
40
+ #
41
+ # @example AllowedPatterns: [] (default)
42
+ # # good
43
+ # var.instance_of?(Date)
44
+ #
45
+ # # bad
46
+ # var.class == Date
47
+ # var.class.equal?(Date)
48
+ # var.class.eql?(Date)
49
+ # var.class.name == 'Date'
50
+ #
51
+ # @example AllowedPatterns: [`/eq/`]
52
+ # # good
53
+ # var.instance_of?(Date)
54
+ # var.class.equal?(Date)
55
+ # var.class.eql?(Date)
56
+ #
57
+ # # bad
58
+ # var.class == Date
59
+ # var.class.name == 'Date'
60
+ #
19
61
  class ClassEqualityComparison < Base
20
62
  include RangeHelp
21
- include IgnoredMethods
63
+ include AllowedMethods
64
+ include AllowedPattern
22
65
  extend AutoCorrector
23
66
 
24
67
  MSG = 'Use `instance_of?(%<class_name>s)` instead of comparing classes.'
@@ -34,7 +77,9 @@ module RuboCop
34
77
 
35
78
  def on_send(node)
36
79
  def_node = node.each_ancestor(:def, :defs).first
37
- return if def_node && ignored_method?(def_node.method_name)
80
+ return if def_node &&
81
+ (allowed_method?(def_node.method_name) ||
82
+ matches_allowed_pattern?(def_node.method_name))
38
83
 
39
84
  class_comparison_candidate?(node) do |receiver_node, class_node|
40
85
  range = offense_range(receiver_node, node)
@@ -52,7 +97,9 @@ module RuboCop
52
97
  if node.children.first.method?(:name)
53
98
  return class_node.receiver.source if class_node.receiver
54
99
 
55
- class_node.source.delete('"').delete("'")
100
+ value = class_node.source.delete('"').delete("'")
101
+ value.prepend('::') if class_node.each_ancestor(:class, :module).any?
102
+ value
56
103
  else
57
104
  class_node.source
58
105
  end
@@ -89,6 +89,41 @@ module RuboCop
89
89
  # if condition
90
90
  # statement
91
91
  # end
92
+ #
93
+ # @example AllowComments: false (default)
94
+ #
95
+ # # bad
96
+ # if condition
97
+ # statement
98
+ # else
99
+ # # something comment
100
+ # nil
101
+ # end
102
+ #
103
+ # # bad
104
+ # if condition
105
+ # statement
106
+ # else
107
+ # # something comment
108
+ # end
109
+ #
110
+ # @example AllowComments: true
111
+ #
112
+ # # good
113
+ # if condition
114
+ # statement
115
+ # else
116
+ # # something comment
117
+ # nil
118
+ # end
119
+ #
120
+ # # good
121
+ # if condition
122
+ # statement
123
+ # else
124
+ # # something comment
125
+ # end
126
+ #
92
127
  class EmptyElse < Base
93
128
  include OnNormalIfUnless
94
129
  include ConfigurableEnforcedStyle
@@ -108,6 +143,8 @@ module RuboCop
108
143
  private
109
144
 
110
145
  def check(node)
146
+ return if cop_config['AllowComments'] && comment_in_else?(node.loc)
147
+
111
148
  empty_check(node) if empty_style?
112
149
  nil_check(node) if nil_style?
113
150
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for using empty heredoc to reduce redundancy.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # <<~EOS
12
+ # EOS
13
+ #
14
+ # <<-EOS
15
+ # EOS
16
+ #
17
+ # <<EOS
18
+ # EOS
19
+ #
20
+ # # good
21
+ # ''
22
+ #
23
+ # # bad
24
+ # do_something(<<~EOS)
25
+ # EOS
26
+ #
27
+ # do_something(<<-EOS)
28
+ # EOS
29
+ #
30
+ # do_something(<<EOS)
31
+ # EOS
32
+ #
33
+ # # good
34
+ # do_something('')
35
+ #
36
+ class EmptyHeredoc < Base
37
+ include Heredoc
38
+ include RangeHelp
39
+ extend AutoCorrector
40
+
41
+ MSG = 'Use an empty string literal instead of heredoc.'
42
+
43
+ def on_heredoc(node)
44
+ heredoc_body = node.loc.heredoc_body
45
+
46
+ return unless heredoc_body.source.empty?
47
+
48
+ add_offense(node) do |corrector|
49
+ heredoc_end = node.loc.heredoc_end
50
+
51
+ corrector.replace(node, preferred_string_literal)
52
+ corrector.remove(range_by_whole_lines(heredoc_body, include_final_newline: true))
53
+ corrector.remove(range_by_whole_lines(heredoc_end, include_final_newline: true))
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def preferred_string_literal
60
+ enforce_double_quotes? ? '""' : "''"
61
+ end
62
+
63
+ def enforce_double_quotes?
64
+ string_literals_config['EnforcedStyle'] == 'double_quotes'
65
+ end
66
+
67
+ def string_literals_config
68
+ config.for_cop('Style/StringLiterals')
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -9,32 +9,15 @@ 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
- #
15
12
  # @example
16
13
  # # bad
17
14
  # ENV['X']
18
- # ENV['X'] || 'string literal'
19
- # ENV['X'] || some_method
20
15
  # x = ENV['X']
21
16
  #
22
- # ENV['X'] || y.map do |a|
23
- # puts a * 2
24
- # end
25
- #
26
17
  # # good
27
18
  # ENV.fetch('X')
28
- # ENV.fetch('X', 'string literal')
29
- # ENV.fetch('X') { some_method }
30
19
  # x = ENV.fetch('X')
31
20
  #
32
- # ENV.fetch('X') do
33
- # y.map do |a|
34
- # puts a * 2
35
- # end
36
- # end
37
- #
38
21
  # # also good
39
22
  # !ENV['X']
40
23
  # ENV['X'].some_method # (e.g. `.nil?`)
@@ -42,48 +25,20 @@ module RuboCop
42
25
  class FetchEnvVar < Base
43
26
  extend AutoCorrector
44
27
 
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
28
+ MSG = 'Use `ENV.fetch(%<key>s)` or `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
51
29
 
52
30
  # @!method env_with_bracket?(node)
53
31
  def_node_matcher :env_with_bracket?, <<~PATTERN
54
32
  (send (const nil? :ENV) :[] $_)
55
33
  PATTERN
56
34
 
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 offensive_nodes(node)
68
- def_node_search :offensive_nodes, <<~PATTERN
69
- [#env_with_bracket? #offensive?]
70
- PATTERN
71
-
72
35
  def on_send(node)
73
36
  env_with_bracket?(node) do |name_node|
74
37
  break unless offensive?(node)
75
38
 
76
- if operand_of_or?(node)
77
- target_node = offensive_nodes(or_chain_root(node)).to_a.last
78
- target_name_node = env_with_bracket?(target_node)
79
-
80
- if default_to_rhs?(target_node)
81
- default_rhs(target_node, target_name_node)
82
- else
83
- default_nil(target_node, target_name_node)
84
- end
85
- else
86
- default_nil(node, name_node)
39
+ message = format(MSG, key: name_node.source)
40
+ add_offense(node, message: message) do |corrector|
41
+ corrector.replace(node, new_code(name_node))
87
42
  end
88
43
  end
89
44
  end
@@ -130,17 +85,6 @@ module RuboCop
130
85
  !(allowed_var?(node) || allowable_use?(node))
131
86
  end
132
87
 
133
- def or_chain_root(node)
134
- while operand_of_or?(ancestor_or ||= node.parent)
135
- ancestor_or = ancestor_or.parent
136
- end
137
- ancestor_or
138
- end
139
-
140
- def default_to_rhs?(node)
141
- operand_of_or?(node) && !right_end_of_or_chains?(node) && rhs_can_be_default_value?(node)
142
- end
143
-
144
88
  # Check if the node is a receiver and receives a message with dot syntax.
145
89
  def message_chained_with_dot?(node)
146
90
  return false if node.root?
@@ -157,8 +101,9 @@ module RuboCop
157
101
  # it simply checks whether the variable is set.
158
102
  # - Receiving a message with dot syntax, e.g. `ENV['X'].nil?`.
159
103
  # - `ENV['key']` assigned by logical AND/OR assignment.
104
+ # - `ENV['key']` is the LHS of a `||`.
160
105
  def allowable_use?(node)
161
- used_as_flag?(node) || message_chained_with_dot?(node) || assigned?(node)
106
+ used_as_flag?(node) || message_chained_with_dot?(node) || assigned?(node) || or_lhs?(node)
162
107
  end
163
108
 
164
109
  # The following are allowed cases:
@@ -172,127 +117,15 @@ module RuboCop
172
117
  node == lhs
173
118
  end
174
119
 
175
- def left_end_of_or_chains?(node)
176
- return false unless operand_of_or?(node)
177
-
178
- node.parent.lhs == node
179
- end
180
-
181
- def right_end_of_or_chains?(node)
182
- !(left_end_of_or_chains?(node) || node.parent&.parent&.or_type?)
183
- end
184
-
185
- def conterpart_rhs_of(node)
186
- left_end_of_or_chains?(node) ? node.parent.rhs : node.parent.parent.rhs
187
- end
188
-
189
- def rhs_can_be_default_value?(node)
190
- !rhs_is_block_control?(node)
191
- end
120
+ def or_lhs?(node)
121
+ return false unless (parent = node.parent)&.or_type?
192
122
 
193
- def rhs_is_block_control?(node)
194
- block_control?(conterpart_rhs_of(node))
123
+ parent.lhs == node || parent.parent&.or_type?
195
124
  end
196
125
 
197
- def new_code_default_nil(name_node)
126
+ def new_code(name_node)
198
127
  "ENV.fetch(#{name_node.source}, nil)"
199
128
  end
200
-
201
- def new_code_default_rhs_single_line(node, name_node)
202
- parent = node.parent
203
- if parent.rhs.basic_literal?
204
- "ENV.fetch(#{name_node.source}, #{parent.rhs.source})"
205
- else
206
- "ENV.fetch(#{name_node.source}) { #{parent.rhs.source} }"
207
- end
208
- end
209
-
210
- def new_code_default_rhs_multiline(node, name_node)
211
- env_indent = indent(node.parent)
212
- default = node.parent.rhs.source.split("\n").map do |line|
213
- "#{env_indent}#{line}"
214
- end.join("\n")
215
- <<~NEW_CODE.chomp
216
- ENV.fetch(#{name_node.source}) do
217
- #{configured_indentation}#{default}
218
- #{env_indent}end
219
- NEW_CODE
220
- end
221
-
222
- def new_code_default_rhs(node, name_node)
223
- if node.parent.rhs.single_line?
224
- new_code_default_rhs_single_line(node, name_node)
225
- else
226
- new_code_default_rhs_multiline(node, name_node)
227
- end
228
- end
229
-
230
- def default_rhs(node, name_node)
231
- if left_end_of_or_chains?(node)
232
- default_rhs_in_same_or(node, name_node)
233
- else
234
- default_rhs_in_outer_or(node, name_node)
235
- end
236
- end
237
-
238
- # Adds an offense and sets `nil` to the default value of `ENV.fetch`.
239
- # `ENV['X']` --> `ENV.fetch('X', nil)`
240
- def default_nil(node, name_node)
241
- message = format(MSG_DEFAULT_NIL, key: name_node.source)
242
-
243
- add_offense(node, message: message) do |corrector|
244
- corrector.replace(node, new_code_default_nil(name_node))
245
- end
246
- end
247
-
248
- # Adds an offense and makes the RHS the default value of `ENV.fetch`.
249
- # `ENV['X'] || y` --> `ENV.fetch('X') { y }`
250
- def default_rhs_in_same_or(node, name_node)
251
- template = message_template_for(node.parent.rhs)
252
- message = format(template,
253
- key: name_node.source,
254
- default: first_line_of(node.parent.rhs.source))
255
-
256
- add_offense(node, message: message) do |corrector|
257
- corrector.replace(node.parent, new_code_default_rhs(node, name_node))
258
- end
259
- end
260
-
261
- # Adds an offense and makes the RHS the default value of `ENV.fetch`.
262
- # `z || ENV['X'] || y` --> `z || ENV.fetch('X') { y }`
263
- def default_rhs_in_outer_or(node, name_node)
264
- parent = node.parent
265
- grand_parent = parent.parent
266
-
267
- template = message_template_for(grand_parent.rhs)
268
- message = format(template,
269
- key: name_node.source,
270
- default: first_line_of(grand_parent.rhs.source))
271
-
272
- add_offense(node, message: message) do |corrector|
273
- lhs_code = parent.lhs.source
274
- rhs_code = new_code_default_rhs(parent, name_node)
275
- corrector.replace(grand_parent, "#{lhs_code} || #{rhs_code}")
276
- end
277
- end
278
-
279
- def message_template_for(rhs)
280
- if rhs.multiline?
281
- MSG_DEFAULT_RHS_MULTILINE_BLOCK
282
- elsif rhs.basic_literal?
283
- MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH
284
- else
285
- MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK
286
- end
287
- end
288
-
289
- def configured_indentation
290
- ' ' * (config.for_cop('Layout/IndentationWidth')['Width'] || 2)
291
- end
292
-
293
- def first_line_of(source)
294
- source.split("\n").first
295
- end
296
129
  end
297
130
  end
298
131
  end