rubocop 1.39.0 → 1.42.0

Sign up to get free protection for your applications and to get access to all the features.
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