rubocop 1.39.0 → 1.42.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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +91 -10
  5. data/exe/rubocop +1 -1
  6. data/lib/rubocop/cli.rb +1 -1
  7. data/lib/rubocop/comment_config.rb +5 -0
  8. data/lib/rubocop/config.rb +39 -15
  9. data/lib/rubocop/config_loader.rb +14 -5
  10. data/lib/rubocop/config_loader_resolver.rb +6 -2
  11. data/lib/rubocop/config_validator.rb +1 -1
  12. data/lib/rubocop/cop/badge.rb +9 -4
  13. data/lib/rubocop/cop/base.rb +84 -74
  14. data/lib/rubocop/cop/commissioner.rb +8 -3
  15. data/lib/rubocop/cop/cop.rb +29 -29
  16. data/lib/rubocop/cop/corrector.rb +23 -11
  17. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +22 -6
  18. data/lib/rubocop/cop/gemspec/dependency_version.rb +16 -18
  19. data/lib/rubocop/cop/internal_affairs/cop_description.rb +3 -1
  20. data/lib/rubocop/cop/internal_affairs/lambda_or_proc.rb +46 -0
  21. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  22. data/lib/rubocop/cop/layout/class_structure.rb +32 -11
  23. data/lib/rubocop/cop/layout/comment_indentation.rb +3 -1
  24. data/lib/rubocop/cop/layout/empty_lines.rb +2 -0
  25. data/lib/rubocop/cop/layout/extra_spacing.rb +10 -6
  26. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +38 -2
  27. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +49 -2
  28. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +61 -2
  29. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +52 -2
  30. data/lib/rubocop/cop/layout/indentation_style.rb +7 -2
  31. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +5 -0
  32. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +11 -5
  33. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  34. data/lib/rubocop/cop/layout/multiline_array_line_breaks.rb +51 -2
  35. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -1
  36. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +49 -2
  37. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +53 -2
  38. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +58 -2
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +2 -2
  40. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  41. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +1 -1
  42. data/lib/rubocop/cop/layout/trailing_whitespace.rb +11 -4
  43. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  44. data/lib/rubocop/cop/lint/assignment_in_condition.rb +11 -1
  45. data/lib/rubocop/cop/lint/constant_resolution.rb +4 -0
  46. data/lib/rubocop/cop/lint/debugger.rb +3 -1
  47. data/lib/rubocop/cop/lint/deprecated_constants.rb +8 -1
  48. data/lib/rubocop/cop/lint/duplicate_branch.rb +0 -2
  49. data/lib/rubocop/cop/lint/duplicate_methods.rb +19 -8
  50. data/lib/rubocop/cop/lint/empty_block.rb +1 -5
  51. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  52. data/lib/rubocop/cop/lint/interpolation_check.rb +4 -3
  53. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -5
  54. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +19 -0
  55. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -0
  56. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +15 -3
  57. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +1 -1
  58. data/lib/rubocop/cop/lint/regexp_as_condition.rb +6 -0
  59. data/lib/rubocop/cop/lint/require_parentheses.rb +3 -1
  60. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +10 -12
  61. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +5 -4
  62. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +4 -3
  63. data/lib/rubocop/cop/lint/unused_method_argument.rb +2 -1
  64. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +5 -3
  65. data/lib/rubocop/cop/lint/void.rb +6 -6
  66. data/lib/rubocop/cop/metrics/block_length.rb +9 -4
  67. data/lib/rubocop/cop/metrics/class_length.rb +10 -5
  68. data/lib/rubocop/cop/metrics/method_length.rb +9 -4
  69. data/lib/rubocop/cop/metrics/module_length.rb +10 -5
  70. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  71. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -4
  72. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +6 -3
  73. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  74. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +2 -2
  75. data/lib/rubocop/cop/mixin/annotation_comment.rb +13 -6
  76. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +21 -9
  77. data/lib/rubocop/cop/mixin/first_element_line_break.rb +11 -7
  78. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +28 -5
  79. data/lib/rubocop/cop/mixin/line_length_help.rb +8 -1
  80. data/lib/rubocop/cop/mixin/method_complexity.rb +5 -3
  81. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +5 -3
  82. data/lib/rubocop/cop/mixin/percent_array.rb +3 -5
  83. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  84. data/lib/rubocop/cop/mixin/require_library.rb +2 -0
  85. data/lib/rubocop/cop/mixin/rescue_node.rb +3 -3
  86. data/lib/rubocop/cop/mixin/statement_modifier.rb +15 -1
  87. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  88. data/lib/rubocop/cop/naming/class_and_module_camel_case.rb +2 -0
  89. data/lib/rubocop/cop/naming/inclusive_language.rb +4 -1
  90. data/lib/rubocop/cop/registry.rb +51 -36
  91. data/lib/rubocop/cop/security/compound_hash.rb +2 -1
  92. data/lib/rubocop/cop/style/alias.rb +9 -1
  93. data/lib/rubocop/cop/style/array_intersect.rb +111 -0
  94. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  95. data/lib/rubocop/cop/style/concat_array_literals.rb +86 -0
  96. data/lib/rubocop/cop/style/documentation.rb +11 -5
  97. data/lib/rubocop/cop/style/guard_clause.rb +44 -9
  98. data/lib/rubocop/cop/style/hash_syntax.rb +10 -7
  99. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -0
  100. data/lib/rubocop/cop/style/if_with_semicolon.rb +4 -4
  101. data/lib/rubocop/cop/style/inverse_methods.rb +2 -0
  102. data/lib/rubocop/cop/style/line_end_concatenation.rb +4 -1
  103. data/lib/rubocop/cop/style/map_to_set.rb +61 -0
  104. data/lib/rubocop/cop/style/method_def_parentheses.rb +11 -4
  105. data/lib/rubocop/cop/style/min_max_comparison.rb +73 -0
  106. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  107. data/lib/rubocop/cop/style/redundant_argument.rb +3 -0
  108. data/lib/rubocop/cop/style/redundant_constant_base.rb +85 -0
  109. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +39 -0
  110. data/lib/rubocop/cop/style/redundant_return.rb +7 -0
  111. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  112. data/lib/rubocop/cop/style/redundant_string_escape.rb +6 -3
  113. data/lib/rubocop/cop/style/require_order.rb +142 -0
  114. data/lib/rubocop/cop/style/safe_navigation.rb +35 -6
  115. data/lib/rubocop/cop/style/select_by_regexp.rb +13 -5
  116. data/lib/rubocop/cop/style/semicolon.rb +2 -1
  117. data/lib/rubocop/cop/style/signal_exception.rb +8 -6
  118. data/lib/rubocop/cop/style/string_literals.rb +1 -5
  119. data/lib/rubocop/cop/style/symbol_proc.rb +2 -4
  120. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -4
  121. data/lib/rubocop/cop/style/word_array.rb +41 -0
  122. data/lib/rubocop/cop/style/yoda_expression.rb +74 -0
  123. data/lib/rubocop/cop/style/zero_length_predicate.rb +31 -14
  124. data/lib/rubocop/cop/team.rb +30 -30
  125. data/lib/rubocop/cop/util.rb +32 -5
  126. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  127. data/lib/rubocop/cop/variable_force.rb +17 -29
  128. data/lib/rubocop/cops_documentation_generator.rb +33 -11
  129. data/lib/rubocop/directive_comment.rb +1 -1
  130. data/lib/rubocop/file_patterns.rb +43 -0
  131. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  132. data/lib/rubocop/formatter/html_formatter.rb +1 -1
  133. data/lib/rubocop/formatter.rb +5 -1
  134. data/lib/rubocop/options.rb +8 -0
  135. data/lib/rubocop/path_util.rb +39 -16
  136. data/lib/rubocop/result_cache.rb +2 -2
  137. data/lib/rubocop/rspec/cop_helper.rb +4 -1
  138. data/lib/rubocop/rspec/support.rb +2 -2
  139. data/lib/rubocop/runner.rb +10 -3
  140. data/lib/rubocop/server/core.rb +1 -1
  141. data/lib/rubocop/target_finder.rb +1 -1
  142. data/lib/rubocop/target_ruby.rb +1 -2
  143. data/lib/rubocop/version.rb +1 -1
  144. data/lib/rubocop.rb +19 -6
  145. metadata +19 -9
@@ -175,7 +175,7 @@ module RuboCop
175
175
 
176
176
  def remove_optarg_equals(asgn_tokens, processed_source)
177
177
  optargs = processed_source.ast.each_node(:optarg)
178
- optarg_eql = optargs.map { |o| o.loc.operator.begin_pos }.to_set
178
+ optarg_eql = optargs.to_set { |o| o.loc.operator.begin_pos }
179
179
  asgn_tokens.reject { |t| optarg_eql.include?(t.begin_pos) }
180
180
  end
181
181
  end
@@ -7,6 +7,8 @@ module RuboCop
7
7
  module RequireLibrary
8
8
  extend NodePattern::Macros
9
9
 
10
+ RESTRICT_ON_SEND = [:require].freeze
11
+
10
12
  def ensure_required(corrector, node, library_name)
11
13
  node = node.parent while node.parent&.parent?
12
14
 
@@ -4,8 +4,8 @@ module RuboCop
4
4
  module Cop
5
5
  # Common functionality for checking `rescue` nodes.
6
6
  module RescueNode
7
- def on_new_investigation
8
- @modifier_locations = processed_source.tokens.select(&:rescue_modifier?).map(&:pos)
7
+ def modifier_locations
8
+ @modifier_locations ||= processed_source.tokens.select(&:rescue_modifier?).map!(&:pos)
9
9
  end
10
10
 
11
11
  private
@@ -13,7 +13,7 @@ module RuboCop
13
13
  def rescue_modifier?(node)
14
14
  return false unless node.respond_to?(:resbody_type?)
15
15
 
16
- node.resbody_type? && @modifier_locations.include?(node.loc.keyword)
16
+ node.resbody_type? && modifier_locations.include?(node.loc.keyword)
17
17
  end
18
18
 
19
19
  # @deprecated Use ResbodyNode#exceptions instead
@@ -48,11 +48,25 @@ module RuboCop
48
48
  end
49
49
 
50
50
  def to_modifier_form(node)
51
- expression = [node.body.source, node.keyword, node.condition.source].compact.join(' ')
51
+ body = if_body_source(node.body)
52
+ expression = [body, node.keyword, node.condition.source].compact.join(' ')
52
53
  parenthesized = parenthesize?(node) ? "(#{expression})" : expression
53
54
  [parenthesized, first_line_comment(node)].compact.join(' ')
54
55
  end
55
56
 
57
+ def if_body_source(if_body)
58
+ if if_body.call_type? &&
59
+ if_body.last_argument&.hash_type? && if_body.last_argument.pairs.last&.value_omission?
60
+ "#{method_source(if_body)}(#{if_body.arguments.map(&:source).join(', ')})"
61
+ else
62
+ if_body.source
63
+ end
64
+ end
65
+
66
+ def method_source(if_body)
67
+ range_between(if_body.loc.expression.begin_pos, if_body.loc.selector.end_pos).source
68
+ end
69
+
56
70
  def first_line_comment(node)
57
71
  comment = processed_source.find_comment { |c| same_line?(c, node) }
58
72
  return unless comment
@@ -108,7 +108,7 @@ module RuboCop
108
108
  return if node.body.nil?
109
109
 
110
110
  node.body.each_descendant(:lvar, :lvasgn).any? do |lvar|
111
- !lvar.parent.block_pass_type? && lvar.source == last_argument
111
+ !lvar.parent.block_pass_type? && lvar.node_parts[0].to_s == last_argument
112
112
  end
113
113
  end
114
114
 
@@ -30,6 +30,8 @@ module RuboCop
30
30
  MSG = 'Use CamelCase for classes and modules.'
31
31
 
32
32
  def on_class(node)
33
+ return unless node.loc.name.source.include?('_')
34
+
33
35
  allowed = /#{cop_config['AllowedNames'].join('|')}/
34
36
  name = node.loc.name.source.gsub(allowed, '')
35
37
  return unless /_/.match?(name)
@@ -207,7 +207,10 @@ module RuboCop
207
207
  end
208
208
 
209
209
  def scan_for_words(input)
210
- mask_input(input).enum_for(:scan, @flagged_terms_regex).map do
210
+ masked_input = mask_input(input)
211
+ return EMPTY_ARRAY unless masked_input.match?(@flagged_terms_regex)
212
+
213
+ masked_input.enum_for(:scan, @flagged_terms_regex).map do
211
214
  match = Regexp.last_match
212
215
  WordLocation.new(match.to_s, match.offset(0).first)
213
216
  end
@@ -19,6 +19,28 @@ module RuboCop
19
19
  class Registry
20
20
  include Enumerable
21
21
 
22
+ def self.all
23
+ global.without_department(:Test).cops
24
+ end
25
+
26
+ def self.qualified_cop_name(name, origin)
27
+ global.qualified_cop_name(name, origin)
28
+ end
29
+
30
+ # Changes momentarily the global registry
31
+ # Intended for testing purposes
32
+ def self.with_temporary_global(temp_global = global.dup)
33
+ previous = @global
34
+ @global = temp_global
35
+ yield
36
+ ensure
37
+ @global = previous
38
+ end
39
+
40
+ def self.reset!
41
+ @global = new
42
+ end
43
+
22
44
  attr_reader :options
23
45
 
24
46
  def initialize(cops = [], options = {})
@@ -28,6 +50,9 @@ module RuboCop
28
50
 
29
51
  @enrollment_queue = cops
30
52
  @options = options
53
+
54
+ @enabled_cache = {}.compare_by_identity
55
+ @disabled_cache = {}.compare_by_identity
31
56
  end
32
57
 
33
58
  def enlist(cop)
@@ -61,7 +86,7 @@ module RuboCop
61
86
 
62
87
  # @return [Boolean] Checks if given name is department
63
88
  def department?(name)
64
- departments.include? name.to_sym
89
+ departments.include?(name.to_sym)
65
90
  end
66
91
 
67
92
  def contains_cop_matching?(names)
@@ -72,27 +97,27 @@ module RuboCop
72
97
  #
73
98
  # @example gives back a correctly qualified cop name
74
99
  #
75
- # cops = RuboCop::Cop::Cop.all
76
- # cops.
77
- # qualified_cop_name('Layout/EndOfLine') # => 'Layout/EndOfLine'
100
+ # registry = RuboCop::Cop::Registry
101
+ # registry.qualified_cop_name('Layout/EndOfLine', '') # => 'Layout/EndOfLine'
78
102
  #
79
103
  # @example fixes incorrect namespaces
80
104
  #
81
- # cops = RuboCop::Cop::Cop.all
82
- # cops.qualified_cop_name('Lint/EndOfLine') # => 'Layout/EndOfLine'
105
+ # registry = RuboCop::Cop::Registry
106
+ # registry.qualified_cop_name('Lint/EndOfLine', '') # => 'Layout/EndOfLine'
83
107
  #
84
108
  # @example namespaces bare cop identifiers
85
109
  #
86
- # cops = RuboCop::Cop::Cop.all
87
- # cops.qualified_cop_name('EndOfLine') # => 'Layout/EndOfLine'
110
+ # registry = RuboCop::Cop::Registry
111
+ # registry.qualified_cop_name('EndOfLine', '') # => 'Layout/EndOfLine'
88
112
  #
89
113
  # @example passes back unrecognized cop names
90
114
  #
91
- # cops = RuboCop::Cop::Cop.all
92
- # cops.qualified_cop_name('NotACop') # => 'NotACop'
115
+ # registry = RuboCop::Cop::Registry
116
+ # registry.qualified_cop_name('NotACop', '') # => 'NotACop'
93
117
  #
94
118
  # @param name [String] Cop name extracted from config
95
119
  # @param path [String, nil] Path of file that `name` was extracted from
120
+ # @param warn [Boolean] Print a warning if no department given for `name`
96
121
  #
97
122
  # @raise [AmbiguousCopName]
98
123
  # if a bare identifier with two possible namespaces is provided
@@ -150,15 +175,15 @@ module RuboCop
150
175
  end
151
176
 
152
177
  def enabled(config)
153
- select { |cop| enabled?(cop, config) }
178
+ @enabled_cache[config] ||= select { |cop| enabled?(cop, config) }
154
179
  end
155
180
 
156
181
  def disabled(config)
157
- reject { |cop| enabled?(cop, config) }
182
+ @disabled_cache[config] ||= reject { |cop| enabled?(cop, config) }
158
183
  end
159
184
 
160
185
  def enabled?(cop, config)
161
- return true if options.fetch(:only, []).include?(cop.cop_name)
186
+ return true if options[:only]&.include?(cop.cop_name)
162
187
 
163
188
  cfg = config.for_cop(cop)
164
189
 
@@ -182,8 +207,12 @@ module RuboCop
182
207
  cops.map(&:cop_name)
183
208
  end
184
209
 
210
+ def cops_for_department(department)
211
+ cops.select { |cop| cop.department == department.to_sym }
212
+ end
213
+
185
214
  def names_for_department(department)
186
- cops.select { |cop| cop.department == department.to_sym }.map(&:cop_name)
215
+ cops_for_department(department).map(&:cop_name)
187
216
  end
188
217
 
189
218
  def ==(other)
@@ -211,6 +240,14 @@ module RuboCop
211
240
  to_h[cop_name].first
212
241
  end
213
242
 
243
+ # When a cop name is given returns a single-element array with the cop class.
244
+ # When a department name is given returns an array with all the cop classes
245
+ # for that department.
246
+ def find_cops_by_directive(directive)
247
+ cop = find_by_cop_name(directive)
248
+ cop ? [cop] : cops_for_department(directive)
249
+ end
250
+
214
251
  def freeze
215
252
  clear_enrollment_queue
216
253
  unqualified_cop_names # build cache
@@ -223,28 +260,6 @@ module RuboCop
223
260
  attr_reader :global
224
261
  end
225
262
 
226
- def self.all
227
- global.without_department(:Test).cops
228
- end
229
-
230
- def self.qualified_cop_name(name, origin)
231
- global.qualified_cop_name(name, origin)
232
- end
233
-
234
- # Changes momentarily the global registry
235
- # Intended for testing purposes
236
- def self.with_temporary_global(temp_global = global.dup)
237
- previous = @global
238
- @global = temp_global
239
- yield
240
- ensure
241
- @global = previous
242
- end
243
-
244
- def self.reset!
245
- @global = new
246
- end
247
-
248
263
  private
249
264
 
250
265
  def initialize_copy(reg)
@@ -9,7 +9,8 @@ module RuboCop
9
9
  # Manually combining hashes is error prone and hard to follow, especially
10
10
  # when there are many values. Poor implementations may also introduce
11
11
  # performance or security concerns if they are prone to collisions.
12
- # Delegating to `Array#hash` is clearer, faster, and safer.
12
+ # Delegating to `Array#hash` is clearer and safer, although it might be slower
13
+ # depending on the use case.
13
14
  #
14
15
  # @safety
15
16
  # This cop may be unsafe if the application logic depends on the hash
@@ -7,6 +7,11 @@ module RuboCop
7
7
  # depending on configuration.
8
8
  # It also flags uses of `alias :symbol` rather than `alias bareword`.
9
9
  #
10
+ # However, it will always enforce `method_alias` when used `alias`
11
+ # in an instance method definition and in a singleton method definition.
12
+ # If used in a block, always enforce `alias_method`
13
+ # unless it is an `instance_eval` block.
14
+ #
10
15
  # @example EnforcedStyle: prefer_alias (default)
11
16
  # # bad
12
17
  # alias_method :bar, :foo
@@ -22,6 +27,7 @@ module RuboCop
22
27
  #
23
28
  # # good
24
29
  # alias_method :bar, :foo
30
+ #
25
31
  class Alias < Base
26
32
  include ConfigurableEnforcedStyle
27
33
  extend AutoCorrector
@@ -71,7 +77,9 @@ module RuboCop
71
77
  end
72
78
 
73
79
  def alias_method_possible?(node)
74
- scope_type(node) != :instance_eval && node.children.none?(&:gvar_type?)
80
+ scope_type(node) != :instance_eval &&
81
+ node.children.none?(&:gvar_type?) &&
82
+ node&.parent&.type != :def
75
83
  end
76
84
 
77
85
  def add_offense_for_args(node, &block)
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # In Ruby 3.1, `Array#intersect?` has been added.
7
+ #
8
+ # This cop identifies places where `(array1 & array2).any?`
9
+ # can be replaced by `array1.intersect?(array2)`.
10
+ #
11
+ # The `array1.intersect?(array2)` method is faster than
12
+ # `(array1 & array2).any?` and is more readable.
13
+ #
14
+ # @safety
15
+ # This cop cannot guarantee that array1 and array2 are
16
+ # actually arrays while method `intersect?` is for arrays only.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # (array1 & array2).any?
21
+ # (array1 & array2).empty?
22
+ #
23
+ # # good
24
+ # array1.intersect?(array2)
25
+ # !array1.intersect?(array2)
26
+ #
27
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
28
+ # # good
29
+ # (array1 & array2).present?
30
+ # (array1 & array2).blank?
31
+ #
32
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
33
+ # # bad
34
+ # (array1 & array2).present?
35
+ # (array1 & array2).blank?
36
+ #
37
+ # # good
38
+ # array1.intersect?(array2)
39
+ # !array1.intersect?(array2)
40
+ class ArrayIntersect < Base
41
+ extend AutoCorrector
42
+ extend TargetRubyVersion
43
+
44
+ minimum_target_ruby_version 3.1
45
+
46
+ # @!method regular_bad_intersection_check?(node)
47
+ def_node_matcher :regular_bad_intersection_check?, <<~PATTERN
48
+ (send
49
+ (begin
50
+ (send $(...) :& $(...))
51
+ ) ${:any? :empty?}
52
+ )
53
+ PATTERN
54
+
55
+ # @!method active_support_bad_intersection_check?(node)
56
+ def_node_matcher :active_support_bad_intersection_check?, <<~PATTERN
57
+ (send
58
+ (begin
59
+ (send $(...) :& $(...))
60
+ ) ${:present? :any? :blank? :empty?}
61
+ )
62
+ PATTERN
63
+
64
+ MSG = 'Use `%<negated>s%<receiver>s.intersect?(%<argument>s)` ' \
65
+ 'instead of `(%<receiver>s & %<argument>s).%<method_name>s`.'
66
+ STRAIGHT_METHODS = %i[present? any?].freeze
67
+ NEGATED_METHODS = %i[blank? empty?].freeze
68
+ RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
69
+
70
+ def on_send(node)
71
+ return unless (receiver, argument, method_name = bad_intersection_check?(node))
72
+
73
+ message = message(receiver.source, argument.source, method_name)
74
+
75
+ add_offense(node, message: message) do |corrector|
76
+ if straight?(method_name)
77
+ corrector.replace(node, "#{receiver.source}.intersect?(#{argument.source})")
78
+ else
79
+ corrector.replace(node, "!#{receiver.source}.intersect?(#{argument.source})")
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def bad_intersection_check?(node)
87
+ if active_support_extensions_enabled?
88
+ active_support_bad_intersection_check?(node)
89
+ else
90
+ regular_bad_intersection_check?(node)
91
+ end
92
+ end
93
+
94
+ def straight?(method_name)
95
+ STRAIGHT_METHODS.include?(method_name.to_sym)
96
+ end
97
+
98
+ def message(receiver, argument, method_name)
99
+ negated = straight?(method_name) ? '' : '!'
100
+ format(
101
+ MSG,
102
+ negated: negated,
103
+ receiver: receiver,
104
+ argument: argument,
105
+ method_name: method_name
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -32,7 +32,7 @@ module RuboCop
32
32
  eq_begin, eq_end, contents = parts(comment)
33
33
 
34
34
  corrector.remove(eq_begin)
35
- unless contents.length.zero?
35
+ unless contents.empty?
36
36
  corrector.replace(
37
37
  contents,
38
38
  contents.source.gsub(/\A/, '# ').gsub(/\n\n/, "\n#\n").gsub(/\n(?=[^#])/, "\n# ")
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Enforces the use of `Array#push(item)` instead of `Array#concat([item])`
7
+ # to avoid redundant array literals.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe, as it can produce false positives if the receiver
11
+ # is not an `Array` object.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # list.concat([foo])
17
+ # list.concat([bar, baz])
18
+ # list.concat([qux, quux], [corge])
19
+ #
20
+ # # good
21
+ # list.push(foo)
22
+ # list.push(bar, baz)
23
+ # list.push(qux, quux, corge)
24
+ #
25
+ class ConcatArrayLiterals < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
29
+ MSG_FOR_PERCENT_LITERALS =
30
+ 'Use `push` with elements as arguments without array brackets instead of `%<current>s`.'
31
+ RESTRICT_ON_SEND = %i[concat].freeze
32
+
33
+ # rubocop:disable Metrics
34
+ def on_send(node)
35
+ return if node.arguments.empty?
36
+ return unless node.arguments.all?(&:array_type?)
37
+
38
+ offense = offense_range(node)
39
+ current = offense.source
40
+
41
+ if node.arguments.any?(&:percent_literal?)
42
+ if percent_literals_includes_only_basic_literals?(node)
43
+ prefer = preferred_method(node)
44
+ message = format(MSG, prefer: prefer, current: current)
45
+ else
46
+ message = format(MSG_FOR_PERCENT_LITERALS, current: current)
47
+ end
48
+ else
49
+ prefer = preferred_method(node)
50
+ message = format(MSG, prefer: prefer, current: current)
51
+ end
52
+
53
+ add_offense(offense, message: message) do |corrector|
54
+ corrector.replace(offense, prefer)
55
+ end
56
+ end
57
+ # rubocop:enable Metrics
58
+
59
+ private
60
+
61
+ def offense_range(node)
62
+ node.loc.selector.join(node.source_range.end)
63
+ end
64
+
65
+ def preferred_method(node)
66
+ new_arguments =
67
+ node.arguments.map do |arg|
68
+ if arg.percent_literal?
69
+ arg.children.map(&:value).map(&:inspect)
70
+ else
71
+ arg.children.map(&:source)
72
+ end
73
+ end.join(', ')
74
+
75
+ "push(#{new_arguments})"
76
+ end
77
+
78
+ def percent_literals_includes_only_basic_literals?(node)
79
+ node.arguments.select(&:percent_literal?).all? do |arg|
80
+ arg.children.all? { |child| child.str_type? || child.sym_type? }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -86,6 +86,11 @@ module RuboCop
86
86
  (send nil? {:public_constant :private_constant} ({sym str} _))
87
87
  PATTERN
88
88
 
89
+ # @!method include_statement?(node)
90
+ def_node_matcher :include_statement?, <<~PATTERN
91
+ (send nil? {:include :extend :prepend} const)
92
+ PATTERN
93
+
89
94
  def on_class(node)
90
95
  return unless node.body
91
96
 
@@ -103,7 +108,7 @@ module RuboCop
103
108
  return if documentation_comment?(node)
104
109
  return if constant_allowed?(node)
105
110
  return if nodoc_self_or_outer_module?(node)
106
- return if macro_only?(body)
111
+ return if include_statement_only?(body)
107
112
 
108
113
  range = range_between(node.loc.expression.begin_pos, node.loc.name.end_pos)
109
114
  message = format(MSG, type: node.type, identifier: identifier(node))
@@ -115,9 +120,10 @@ module RuboCop
115
120
  (compact_namespace?(node) && nodoc_comment?(outer_module(node).first))
116
121
  end
117
122
 
118
- def macro_only?(body)
119
- (body.respond_to?(:macro?) && body.macro?) ||
120
- (body.respond_to?(:children) && body.children&.all? { |child| macro_only?(child) })
123
+ def include_statement_only?(body)
124
+ return true if include_statement?(body)
125
+
126
+ body.respond_to?(:children) && body.children.all? { |node| include_statement_only?(node) }
121
127
  end
122
128
 
123
129
  def namespace?(node)
@@ -176,7 +182,7 @@ module RuboCop
176
182
  end
177
183
 
178
184
  def qualify_const(node)
179
- return if node.nil? || node.cbase_type?
185
+ return if node.nil? || node.cbase_type? || node.self_type? || node.send_type?
180
186
 
181
187
  [qualify_const(node.namespace), node.short_name].compact
182
188
  end
@@ -94,6 +94,7 @@ module RuboCop
94
94
  #
95
95
  class GuardClause < Base
96
96
  extend AutoCorrector
97
+ include RangeHelp
97
98
  include MinBodyLength
98
99
  include StatementModifier
99
100
 
@@ -179,20 +180,50 @@ module RuboCop
179
180
  end
180
181
  end
181
182
 
183
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
182
184
  def autocorrect(corrector, node, condition, replacement, guard)
183
185
  corrector.replace(node.loc.keyword.join(condition.loc.expression), replacement)
184
- corrector.remove(node.loc.end)
186
+
187
+ if_branch = node.if_branch
188
+ else_branch = node.else_branch
189
+
190
+ if if_branch&.send_type? && heredoc?(if_branch.last_argument)
191
+ autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
192
+ elsif else_branch&.send_type? && heredoc?(else_branch.last_argument)
193
+ autocorrect_heredoc_argument(corrector, node, else_branch, if_branch, guard)
194
+ else
195
+ corrector.remove(node.loc.end)
196
+ return unless node.else?
197
+
198
+ corrector.remove(node.loc.else)
199
+ corrector.remove(range_of_branch_to_remove(node, guard))
200
+ end
201
+ end
202
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
203
+
204
+ def heredoc?(argument)
205
+ argument.respond_to?(:heredoc?) && argument.heredoc?
206
+ end
207
+
208
+ def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
185
209
  return unless node.else?
186
210
 
187
- corrector.remove(node.loc.else)
188
- corrector.remove(branch_to_remove(node, guard))
211
+ remove_whole_lines(corrector, leave_branch.source_range)
212
+ remove_whole_lines(corrector, node.loc.else)
213
+ remove_whole_lines(corrector, node.loc.end)
214
+ remove_whole_lines(corrector, range_of_branch_to_remove(node, guard))
215
+ corrector.insert_after(
216
+ heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
217
+ )
189
218
  end
190
219
 
191
- def branch_to_remove(node, guard)
192
- case guard
193
- when :if then node.if_branch
194
- when :else then node.else_branch
195
- end
220
+ def range_of_branch_to_remove(node, guard)
221
+ branch = case guard
222
+ when :if then node.if_branch
223
+ when :else then node.else_branch
224
+ end
225
+
226
+ branch.source_range
196
227
  end
197
228
 
198
229
  def guard_clause_source(guard_clause)
@@ -222,7 +253,7 @@ module RuboCop
222
253
  end
223
254
 
224
255
  def accepted_if?(node, ending)
225
- return true if node.modifier_form? || node.ternary?
256
+ return true if node.modifier_form? || node.ternary? || node.elsif_conditional?
226
257
 
227
258
  if ending
228
259
  node.else?
@@ -231,6 +262,10 @@ module RuboCop
231
262
  end
232
263
  end
233
264
 
265
+ def remove_whole_lines(corrector, range)
266
+ corrector.remove(range_by_whole_lines(range, include_final_newline: true))
267
+ end
268
+
234
269
  def allowed_consecutive_conditionals?
235
270
  cop_config.fetch('AllowConsecutiveConditionals', false)
236
271
  end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  # * always - forces use of the 3.1 syntax (e.g. {foo:})
29
29
  # * never - forces use of explicit hash literal value
30
30
  # * either - accepts both shorthand and explicit use of hash literal value
31
- # * consistent - like "either", but will avoid mixing styles in a single hash
31
+ # * consistent - forces use of the 3.1 syntax only if all values can be omitted in the hash
32
32
  #
33
33
  # @example EnforcedStyle: ruby19 (default)
34
34
  # # bad
@@ -92,16 +92,19 @@ module RuboCop
92
92
  #
93
93
  # @example EnforcedShorthandSyntax: consistent
94
94
  #
95
- # # bad
96
- # {foo: , bar: bar}
95
+ # # bad - `foo` and `bar` values can be omitted
96
+ # {foo: foo, bar: bar}
97
97
  #
98
- # # good
99
- # {foo:, bar:}
98
+ # # bad - `bar` value can be omitted
99
+ # {foo:, bar: bar}
100
100
  #
101
- # # bad
102
- # {foo: , bar: baz}
101
+ # # bad - mixed syntaxes
102
+ # {foo:, bar: baz}
103
103
  #
104
104
  # # good
105
+ # {foo:, bar:}
106
+ #
107
+ # # good - can't omit `baz`
105
108
  # {foo: foo, bar: baz}
106
109
  #
107
110
  class HashSyntax < Base