rubocop 1.75.8 → 1.80.1

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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -16
  3. data/config/default.yml +107 -26
  4. data/config/obsoletion.yml +6 -3
  5. data/exe/rubocop +1 -8
  6. data/lib/rubocop/cli.rb +17 -1
  7. data/lib/rubocop/config_loader.rb +1 -38
  8. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  9. data/lib/rubocop/cop/correctors/alignment_corrector.rb +6 -3
  10. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  11. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  12. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  13. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
  14. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  15. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  16. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -1
  17. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  18. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
  19. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  20. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  21. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  22. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
  23. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
  25. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
  26. data/lib/rubocop/cop/layout/line_length.rb +26 -5
  27. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -0
  28. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  29. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  30. data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
  31. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
  32. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  33. data/lib/rubocop/cop/lint/duplicate_methods.rb +25 -4
  34. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  35. data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
  36. data/lib/rubocop/cop/lint/float_comparison.rb +4 -4
  37. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  38. data/lib/rubocop/cop/lint/literal_as_condition.rb +34 -28
  39. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -2
  40. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  41. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  42. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  43. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +4 -4
  44. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  45. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  46. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  47. data/lib/rubocop/cop/lint/self_assignment.rb +30 -4
  48. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  49. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  50. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  51. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  52. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  53. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  54. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  55. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  56. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  57. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  58. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  59. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  60. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  61. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  62. data/lib/rubocop/cop/naming/method_name.rb +127 -13
  63. data/lib/rubocop/cop/naming/predicate_method.rb +306 -0
  64. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  65. data/lib/rubocop/cop/security/eval.rb +2 -1
  66. data/lib/rubocop/cop/security/open.rb +1 -0
  67. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  68. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  69. data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
  70. data/lib/rubocop/cop/style/array_intersect.rb +98 -34
  71. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  72. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  73. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  74. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  75. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -2
  76. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  77. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  78. data/lib/rubocop/cop/style/exponential_notation.rb +3 -2
  79. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  80. data/lib/rubocop/cop/style/hash_conversion.rb +16 -8
  81. data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
  82. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  83. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  84. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  85. data/lib/rubocop/cop/style/it_block_parameter.rb +36 -15
  86. data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
  87. data/lib/rubocop/cop/style/map_to_set.rb +1 -3
  88. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +4 -6
  89. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +16 -0
  90. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  91. data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
  92. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  93. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  94. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  95. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -9
  96. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  97. data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
  98. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  99. data/lib/rubocop/cop/style/redundant_parentheses.rb +42 -6
  100. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  101. data/lib/rubocop/cop/style/safe_navigation.rb +38 -12
  102. data/lib/rubocop/cop/style/single_line_methods.rb +7 -4
  103. data/lib/rubocop/cop/style/sole_nested_conditional.rb +32 -2
  104. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  105. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  106. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  107. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  108. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  109. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  110. data/lib/rubocop/cop/variable_force.rb +25 -8
  111. data/lib/rubocop/cops_documentation_generator.rb +1 -0
  112. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  113. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  114. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  115. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  116. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  117. data/lib/rubocop/lsp/diagnostic.rb +4 -4
  118. data/lib/rubocop/lsp/routes.rb +35 -6
  119. data/lib/rubocop/pending_cops_reporter.rb +56 -0
  120. data/lib/rubocop/result_cache.rb +14 -12
  121. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  122. data/lib/rubocop/runner.rb +6 -4
  123. data/lib/rubocop/server/cache.rb +4 -2
  124. data/lib/rubocop/server/client_command/base.rb +10 -0
  125. data/lib/rubocop/server/client_command/exec.rb +2 -1
  126. data/lib/rubocop/server/client_command/start.rb +11 -1
  127. data/lib/rubocop/target_finder.rb +9 -9
  128. data/lib/rubocop/version.rb +1 -1
  129. data/lib/rubocop.rb +11 -1
  130. data/lib/ruby_lsp/rubocop/addon.rb +2 -2
  131. metadata +17 -7
@@ -5,12 +5,17 @@ module RuboCop
5
5
  module Style
6
6
  # In Ruby 3.1, `Array#intersect?` has been added.
7
7
  #
8
- # This cop identifies places where `(array1 & array2).any?`
9
- # or `(array1.intersection(array2)).any?` can be replaced by
10
- # `array1.intersect?(array2)`.
8
+ # This cop identifies places where:
11
9
  #
12
- # The `array1.intersect?(array2)` method is faster than
13
- # `(array1 & array2).any?` and is more readable.
10
+ # * `(array1 & array2).any?`
11
+ # * `(array1.intersection(array2)).any?`
12
+ # * `array1.any? { |elem| array2.member?(elem) }`
13
+ # * `(array1 & array2).count > 0`
14
+ # * `(array1 & array2).size > 0`
15
+ #
16
+ # can be replaced with `array1.intersect?(array2)`.
17
+ #
18
+ # `array1.intersect?(array2)` is faster and more readable.
14
19
  #
15
20
  # In cases like the following, compatibility is not ensured,
16
21
  # so it will not be detected when using block argument.
@@ -40,10 +45,27 @@ module RuboCop
40
45
  # array1.intersection(array2).empty?
41
46
  # array1.intersection(array2).none?
42
47
  #
48
+ # # bad
49
+ # array1.any? { |elem| array2.member?(elem) }
50
+ # array1.none? { |elem| array2.member?(elem) }
51
+ #
43
52
  # # good
44
53
  # array1.intersect?(array2)
45
54
  # !array1.intersect?(array2)
46
55
  #
56
+ # # bad
57
+ # (array1 & array2).count > 0
58
+ # (array1 & array2).count.positive?
59
+ # (array1 & array2).count != 0
60
+ #
61
+ # (array1 & array2).count == 0
62
+ # (array1 & array2).count.zero?
63
+ #
64
+ # # good
65
+ # array1.intersect?(array2)
66
+ #
67
+ # !array1.intersect?(array2)
68
+ #
47
69
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
48
70
  # # good
49
71
  # (array1 & array2).present?
@@ -66,9 +88,11 @@ module RuboCop
66
88
  PREDICATES = %i[any? empty? none?].to_set.freeze
67
89
  ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze
68
90
 
91
+ ARRAY_SIZE_METHODS = %i[count length size].to_set.freeze
92
+
69
93
  # @!method bad_intersection_check?(node, predicates)
70
94
  def_node_matcher :bad_intersection_check?, <<~PATTERN
71
- (call
95
+ $(call
72
96
  {
73
97
  (begin (send $_ :& $_))
74
98
  (call $_ :intersection $_)
@@ -77,53 +101,93 @@ module RuboCop
77
101
  )
78
102
  PATTERN
79
103
 
80
- MSG = 'Use `%<negated>s%<receiver>s%<dot>sintersect?(%<argument>s)` ' \
81
- 'instead of `%<existing>s`.'
82
- STRAIGHT_METHODS = %i[present? any?].freeze
83
- NEGATED_METHODS = %i[blank? empty? none?].freeze
104
+ # @!method intersection_size_check?(node, predicates)
105
+ def_node_matcher :intersection_size_check?, <<~PATTERN
106
+ (call
107
+ $(call
108
+ {
109
+ (begin (send $_ :& $_))
110
+ (call $_ :intersection $_)
111
+ }
112
+ %ARRAY_SIZE_METHODS
113
+ )
114
+ {$:> (int 0) | $:positive? | $:!= (int 0) | $:== (int 0) | $:zero?}
115
+ )
116
+ PATTERN
117
+
118
+ # @!method any_none_block_intersection(node)
119
+ def_node_matcher :any_none_block_intersection, <<~PATTERN
120
+ {
121
+ (block
122
+ (call $_receiver ${:any? :none?})
123
+ (args (arg _key))
124
+ (send $_argument :member? (lvar _key))
125
+ )
126
+ (numblock
127
+ (call $_receiver ${:any? :none?}) 1
128
+ (send $_argument :member? (lvar :_1))
129
+ )
130
+ (itblock
131
+ (call $_receiver ${:any? :none?}) :it
132
+ (send $_argument :member? (lvar :it))
133
+ )
134
+ }
135
+ PATTERN
136
+
137
+ MSG = 'Use `%<replacement>s` instead of `%<existing>s`.'
138
+ STRAIGHT_METHODS = %i[present? any? > positive? !=].freeze
139
+ NEGATED_METHODS = %i[blank? empty? none? == zero?].freeze
84
140
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
85
141
 
86
142
  def on_send(node)
87
143
  return if node.block_literal?
88
- return unless (receiver, argument, method_name = bad_intersection?(node))
89
-
90
- dot = node.loc.dot.source
91
- message = message(receiver.source, argument.source, method_name, dot, node.source)
144
+ return unless (dot_node, receiver, argument, method_name = bad_intersection?(node))
92
145
 
93
- add_offense(node, message: message) do |corrector|
94
- bang = straight?(method_name) ? '' : '!'
146
+ dot = dot_node.loc.dot.source
147
+ bang = straight?(method_name) ? '' : '!'
148
+ replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
95
149
 
96
- corrector.replace(node, "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})")
97
- end
150
+ register_offense(node, replacement)
98
151
  end
99
152
  alias on_csend on_send
100
153
 
154
+ def on_block(node)
155
+ return unless (receiver, method_name, argument = any_none_block_intersection(node))
156
+
157
+ dot = node.send_node.loc.dot.source
158
+ bang = method_name == :any? ? '' : '!'
159
+ replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
160
+
161
+ register_offense(node, replacement)
162
+ end
163
+ alias on_numblock on_block
164
+ alias on_itblock on_block
165
+
101
166
  private
102
167
 
103
168
  def bad_intersection?(node)
104
- predicates = if active_support_extensions_enabled?
105
- ACTIVE_SUPPORT_PREDICATES
106
- else
107
- PREDICATES
108
- end
169
+ bad_intersection_check?(node, bad_intersection_predicates) ||
170
+ intersection_size_check?(node)
171
+ end
109
172
 
110
- bad_intersection_check?(node, predicates)
173
+ def bad_intersection_predicates
174
+ if active_support_extensions_enabled?
175
+ ACTIVE_SUPPORT_PREDICATES
176
+ else
177
+ PREDICATES
178
+ end
111
179
  end
112
180
 
113
181
  def straight?(method_name)
114
182
  STRAIGHT_METHODS.include?(method_name.to_sym)
115
183
  end
116
184
 
117
- def message(receiver, argument, method_name, dot, existing)
118
- negated = straight?(method_name) ? '' : '!'
119
- format(
120
- MSG,
121
- negated: negated,
122
- receiver: receiver,
123
- argument: argument,
124
- dot: dot,
125
- existing: existing
126
- )
185
+ def register_offense(node, replacement)
186
+ message = format(MSG, replacement: replacement, existing: node.source)
187
+
188
+ add_offense(node, message: message) do |corrector|
189
+ corrector.replace(node, replacement)
190
+ end
127
191
  end
128
192
  end
129
193
  end
@@ -70,18 +70,25 @@ module RuboCop
70
70
  (send _ :& _))
71
71
  PATTERN
72
72
 
73
+ # rubocop:disable Metrics/AbcSize
73
74
  def on_send(node)
74
75
  return unless node.receiver&.begin_type?
75
76
  return unless (preferred_method = preferred_method(node))
76
77
 
77
78
  bit_operation = node.receiver.children.first
78
79
  lhs, _operator, rhs = *bit_operation
79
- preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
80
+
81
+ preferred = if preferred_method == 'allbits?' && lhs.source == node.first_argument.source
82
+ "#{rhs.source}.allbits?(#{lhs.source})"
83
+ else
84
+ "#{lhs.source}.#{preferred_method}(#{rhs.source})"
85
+ end
80
86
 
81
87
  add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
82
88
  corrector.replace(node, preferred)
83
89
  end
84
90
  end
91
+ # rubocop:enable Metrics/AbcSize
85
92
 
86
93
  private
87
94
 
@@ -4,7 +4,7 @@
4
4
  module RuboCop
5
5
  module Cop
6
6
  module Style
7
- # Check for uses of braces or do/end around single line or
7
+ # Checks for uses of braces or do/end around single line or
8
8
  # multi-line blocks.
9
9
  #
10
10
  # Methods that can be either procedural or functional and cannot be
@@ -269,7 +269,7 @@ module RuboCop
269
269
  end
270
270
 
271
271
  def regexp_with_named_captures?(node)
272
- node.regexp_type? && node.each_capture(named: true).count.positive?
272
+ node.regexp_type? && node.each_capture(named: true).any?
273
273
  end
274
274
  end
275
275
  end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer `Enumerable` predicate methods over expressions with `count`.
7
+ #
8
+ # The cop checks calls to `count` without arguments, or with a
9
+ # block. It doesn't register offenses for `count` with a positional
10
+ # argument because its behavior differs from predicate methods (`count`
11
+ # matches the argument using `==`, while `any?`, `none?` and `one?` use
12
+ # `===`).
13
+ #
14
+ # NOTE: This cop doesn't check `length` and `size` methods because they
15
+ # would yield false positives. For example, `String` implements `length`
16
+ # and `size`, but it doesn't include `Enumerable`.
17
+ #
18
+ # @safety
19
+ # The cop is unsafe because receiver might not include `Enumerable`, or
20
+ # it has nonstandard implementation of `count` or any replacement
21
+ # methods.
22
+ #
23
+ # It's also unsafe because for collections with falsey values, expressions
24
+ # with `count` without a block return a different result than methods `any?`,
25
+ # `none?` and `one?`:
26
+ #
27
+ # [source,ruby]
28
+ # ----
29
+ # [nil, false].count.positive?
30
+ # [nil].count == 1
31
+ # # => true
32
+ #
33
+ # [nil, false].any?
34
+ # [nil].one?
35
+ # # => false
36
+ #
37
+ # [nil].count == 0
38
+ # # => false
39
+ #
40
+ # [nil].none?
41
+ # # => true
42
+ # ----
43
+ #
44
+ # Autocorrection is unsafe when replacement methods don't iterate over
45
+ # every element in collection and the given block runs side effects:
46
+ #
47
+ # [source,ruby]
48
+ # ----
49
+ # x.count(&:method_with_side_effects).positive?
50
+ # # calls `method_with_side_effects` on every element
51
+ #
52
+ # x.any?(&:method_with_side_effects)
53
+ # # calls `method_with_side_effects` until first element returns a truthy value
54
+ # ----
55
+ #
56
+ # @example
57
+ #
58
+ # # bad
59
+ # x.count.positive?
60
+ # x.count > 0
61
+ # x.count != 0
62
+ #
63
+ # x.count(&:foo?).positive?
64
+ # x.count { |item| item.foo? }.positive?
65
+ #
66
+ # # good
67
+ # x.any?
68
+ #
69
+ # x.any?(&:foo?)
70
+ # x.any? { |item| item.foo? }
71
+ #
72
+ # # bad
73
+ # x.count.zero?
74
+ # x.count == 0
75
+ #
76
+ # # good
77
+ # x.none?
78
+ #
79
+ # # bad
80
+ # x.count == 1
81
+ # x.one?
82
+ #
83
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
84
+ #
85
+ # # good
86
+ # x.count > 1
87
+ #
88
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
89
+ #
90
+ # # bad
91
+ # x.count > 1
92
+ #
93
+ # # good
94
+ # x.many?
95
+ #
96
+ class CollectionQuerying < Base
97
+ include RangeHelp
98
+ extend AutoCorrector
99
+
100
+ MSG = 'Use `%<prefer>s` instead.'
101
+
102
+ RESTRICT_ON_SEND = %i[positive? > != zero? ==].freeze
103
+
104
+ REPLACEMENTS = {
105
+ [:positive?, nil] => :any?,
106
+ [:>, 0] => :any?,
107
+ [:!=, 0] => :any?,
108
+ [:zero?, nil] => :none?,
109
+ [:==, 0] => :none?,
110
+ [:==, 1] => :one?,
111
+ [:>, 1] => :many?
112
+ }.freeze
113
+
114
+ # @!method count_predicate(node)
115
+ def_node_matcher :count_predicate, <<~PATTERN
116
+ (send
117
+ {
118
+ (any_block $(call !nil? :count) _ _)
119
+ $(call !nil? :count (block-pass _)?)
120
+ }
121
+ {
122
+ :positive? |
123
+ :> (int 0) |
124
+ :!= (int 0) |
125
+ :zero? |
126
+ :== (int 0) |
127
+ :== (int 1) |
128
+ :> (int 1)
129
+ })
130
+ PATTERN
131
+
132
+ def on_send(node)
133
+ return unless (count_node = count_predicate(node))
134
+
135
+ replacement_method = replacement_method(node)
136
+
137
+ return unless replacement_supported?(replacement_method)
138
+
139
+ offense_range = count_node.loc.selector.join(node.source_range.end)
140
+ add_offense(offense_range,
141
+ message: format(MSG, prefer: replacement_method)) do |corrector|
142
+ corrector.replace(count_node.loc.selector, replacement_method)
143
+ corrector.remove(removal_range(node))
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def replacement_method(node)
150
+ REPLACEMENTS.fetch([node.method_name, node.first_argument&.value])
151
+ end
152
+
153
+ def replacement_supported?(method_name)
154
+ return true if active_support_extensions_enabled?
155
+
156
+ method_name != :many?
157
+ end
158
+
159
+ def removal_range(node)
160
+ range = (node.loc.dot || node.loc.selector).join(node.source_range.end)
161
+
162
+ range_with_surrounding_space(range, side: :left)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -111,7 +111,7 @@ module RuboCop
111
111
  end
112
112
  end
113
113
 
114
- # Check for `if` and `case` statements where each branch is used for
114
+ # Checks for `if` and `case` statements where each branch is used for
115
115
  # both the assignment and comparison of the same variable
116
116
  # when using the return of the condition can be used instead.
117
117
  #
@@ -451,7 +451,9 @@ module RuboCop
451
451
  corrector.remove_preceding(condition.loc.else, condition.loc.else.column - column)
452
452
  end
453
453
 
454
- return unless condition.loc.end && !same_line?(condition.loc.end, condition)
454
+ return unless condition.loc.end && !same_line?(
455
+ condition.branches.last.parent.else_branch, condition.loc.end
456
+ )
455
457
 
456
458
  corrector.remove_preceding(condition.loc.end, condition.loc.end.column - column)
457
459
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Check for chained `dig` calls that can be collapsed into a single `dig`.
6
+ # Checks for chained `dig` calls that can be collapsed into a single `dig`.
7
7
  #
8
8
  # @safety
9
9
  # This cop is unsafe because it cannot be guaranteed that the receiver
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for empty strings being assigned inside string interpolation.
7
+ #
8
+ # Empty strings are a meaningless outcome inside of string interpolation, so we remove them.
9
+ # Alternatively, when configured to do so, we prioritise using empty strings.
10
+ #
11
+ # While this cop would also apply to variables that are only going to be used as strings,
12
+ # RuboCop can't detect that, so we only check inside of string interpolation.
13
+ #
14
+ # @example EnforcedStyle: trailing_conditional (default)
15
+ # # bad
16
+ # "#{condition ? 'foo' : ''}"
17
+ #
18
+ # # good
19
+ # "#{'foo' if condition}"
20
+ #
21
+ # # bad
22
+ # "#{condition ? '' : 'foo'}"
23
+ #
24
+ # # good
25
+ # "#{'foo' unless condition}"
26
+ #
27
+ # @example EnforcedStyle: ternary
28
+ # # bad
29
+ # "#{'foo' if condition}"
30
+ #
31
+ # # good
32
+ # "#{condition ? 'foo' : ''}"
33
+ #
34
+ # # bad
35
+ # "#{'foo' unless condition}"
36
+ #
37
+ # # good
38
+ # "#{condition ? '' : 'foo'}"
39
+ #
40
+ class EmptyStringInsideInterpolation < Base
41
+ include ConfigurableEnforcedStyle
42
+ include Interpolation
43
+ extend AutoCorrector
44
+
45
+ MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
46
+ MSG_TERNARY = 'Do not return empty strings in string interpolation.'
47
+
48
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
49
+ def on_interpolation(node)
50
+ node.each_child_node(:if) do |child_node|
51
+ if style == :trailing_conditional
52
+ if empty_if_outcome?(child_node)
53
+ ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
54
+ end
55
+
56
+ if empty_else_outcome?(child_node)
57
+ ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
58
+ end
59
+ elsif style == :ternary
60
+ next unless child_node.modifier_form?
61
+
62
+ ternary_component = if child_node.unless?
63
+ "'' : #{child_node.if_branch.source}"
64
+ else
65
+ "#{child_node.if_branch.source} : ''"
66
+ end
67
+
68
+ add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
69
+ corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
75
+
76
+ private
77
+
78
+ def empty_if_outcome?(node)
79
+ empty_branch_outcome?(node.if_branch)
80
+ end
81
+
82
+ def empty_else_outcome?(node)
83
+ empty_branch_outcome?(node.else_branch)
84
+ end
85
+
86
+ def empty_branch_outcome?(branch)
87
+ return false unless branch
88
+
89
+ branch.nil_type? || (branch.str_type? && branch.value.empty?)
90
+ end
91
+
92
+ def ternary_style_autocorrect(node, outcome, condition)
93
+ add_offense(node, message: MSG_TERNARY) do |corrector|
94
+ corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -59,6 +59,7 @@ module RuboCop
59
59
  #
60
60
  class ExponentialNotation < Base
61
61
  include ConfigurableEnforcedStyle
62
+
62
63
  MESSAGES = {
63
64
  scientific: 'Use a mantissa >= 1 and < 10.',
64
65
  engineering: 'Use an exponent divisible by 3 and a mantissa >= 0.1 and < 1000.',
@@ -87,7 +88,7 @@ module RuboCop
87
88
  true
88
89
  end
89
90
 
90
- def integral(node)
91
+ def integral?(node)
91
92
  mantissa, = node.source.split('e')
92
93
  /^-?[1-9](\d*[1-9])?$/.match?(mantissa)
93
94
  end
@@ -101,7 +102,7 @@ module RuboCop
101
102
  when :engineering
102
103
  !engineering?(node)
103
104
  when :integral
104
- !integral(node)
105
+ !integral?(node)
105
106
  else
106
107
  false
107
108
  end
@@ -9,7 +9,20 @@ module RuboCop
9
9
  # On the other hand, `ENV.fetch` raises `KeyError` or returns the explicitly
10
10
  # specified default value.
11
11
  #
12
- # @example
12
+ # @example DefaultToNil: true (default)
13
+ # # bad
14
+ # ENV['X']
15
+ # x = ENV['X']
16
+ #
17
+ # # good
18
+ # ENV.fetch('X', nil)
19
+ # x = ENV.fetch('X', nil)
20
+ #
21
+ # # also good
22
+ # !ENV['X']
23
+ # ENV['X'].some_method # (e.g. `.nil?`)
24
+ #
25
+ # @example DefaultToNil: false
13
26
  # # bad
14
27
  # ENV['X']
15
28
  # x = ENV['X']
@@ -25,7 +38,8 @@ module RuboCop
25
38
  class FetchEnvVar < Base
26
39
  extend AutoCorrector
27
40
 
28
- MSG = 'Use `ENV.fetch(%<key>s)` or `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
41
+ MSG_WITH_NIL = 'Use `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
42
+ MSG_WITHOUT_NIL = 'Use `ENV.fetch(%<key>s)` instead of `ENV[%<key>s]`.'
29
43
  RESTRICT_ON_SEND = [:[]].freeze
30
44
 
31
45
  # @!method env_with_bracket?(node)
@@ -37,7 +51,7 @@ module RuboCop
37
51
  env_with_bracket?(node) do |name_node|
38
52
  break unless offensive?(node)
39
53
 
40
- message = format(MSG, key: name_node.source)
54
+ message = format(offense_message, key: name_node.source)
41
55
  add_offense(node, message: message) do |corrector|
42
56
  corrector.replace(node, new_code(name_node))
43
57
  end
@@ -46,6 +60,14 @@ module RuboCop
46
60
 
47
61
  private
48
62
 
63
+ def default_to_nil?
64
+ cop_config.fetch('DefaultToNil', true)
65
+ end
66
+
67
+ def offense_message
68
+ default_to_nil? ? MSG_WITH_NIL : MSG_WITHOUT_NIL
69
+ end
70
+
49
71
  def allowed_var?(node)
50
72
  env_key_node = node.children.last
51
73
  env_key_node.str_type? && cop_config['AllowedVars'].include?(env_key_node.value)
@@ -53,12 +75,12 @@ module RuboCop
53
75
 
54
76
  def used_as_flag?(node)
55
77
  return false if node.root?
56
- return true if used_if_condition_in_body(node)
78
+ return true if used_if_condition_in_body?(node)
57
79
 
58
80
  node.parent.send_type? && (node.parent.prefix_bang? || node.parent.comparison_method?)
59
81
  end
60
82
 
61
- def used_if_condition_in_body(node)
83
+ def used_if_condition_in_body?(node)
62
84
  if_node = node.ancestors.find(&:if_type?)
63
85
 
64
86
  return false unless (condition = if_node&.condition)
@@ -125,7 +147,11 @@ module RuboCop
125
147
  end
126
148
 
127
149
  def new_code(name_node)
128
- "ENV.fetch(#{name_node.source}, nil)"
150
+ if default_to_nil?
151
+ "ENV.fetch(#{name_node.source}, nil)"
152
+ else
153
+ "ENV.fetch(#{name_node.source})"
154
+ end
129
155
  end
130
156
  end
131
157
  end