rubocop 1.73.2 → 1.75.2

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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +63 -8
  4. data/config/obsoletion.yml +3 -1
  5. data/lib/rubocop/cli.rb +1 -1
  6. data/lib/rubocop/config.rb +35 -6
  7. data/lib/rubocop/config_loader.rb +4 -0
  8. data/lib/rubocop/config_loader_resolver.rb +2 -1
  9. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -3
  10. data/lib/rubocop/config_obsoletion/renamed_cop.rb +18 -3
  11. data/lib/rubocop/config_obsoletion.rb +46 -2
  12. data/lib/rubocop/config_validator.rb +1 -0
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +2 -1
  14. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +6 -5
  15. data/lib/rubocop/cop/layout/block_alignment.rb +2 -2
  16. data/lib/rubocop/cop/layout/block_end_newline.rb +1 -0
  17. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  18. data/lib/rubocop/cop/layout/else_alignment.rb +1 -1
  19. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +3 -3
  20. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  21. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -0
  22. data/lib/rubocop/cop/layout/indentation_width.rb +1 -0
  23. data/lib/rubocop/cop/layout/line_length.rb +5 -1
  24. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -0
  25. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +1 -0
  26. data/lib/rubocop/cop/layout/redundant_line_break.rb +9 -5
  27. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -5
  28. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  29. data/lib/rubocop/cop/layout/space_before_block_braces.rb +1 -0
  30. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -0
  31. data/lib/rubocop/cop/lint/debugger.rb +2 -2
  32. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +1 -1
  33. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -3
  34. data/lib/rubocop/cop/lint/literal_as_condition.rb +4 -0
  35. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  36. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +3 -3
  37. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -1
  38. data/lib/rubocop/cop/lint/raise_exception.rb +29 -10
  39. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +16 -7
  40. data/lib/rubocop/cop/lint/redundant_with_index.rb +3 -0
  41. data/lib/rubocop/cop/lint/redundant_with_object.rb +3 -0
  42. data/lib/rubocop/cop/lint/return_in_void_context.rb +9 -11
  43. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +8 -1
  44. data/lib/rubocop/cop/lint/shared_mutable_default.rb +12 -1
  45. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  46. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -1
  47. data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
  48. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +2 -0
  49. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -0
  50. data/lib/rubocop/cop/lint/unreachable_loop.rb +5 -5
  51. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -0
  52. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +2 -11
  53. data/lib/rubocop/cop/lint/void.rb +1 -0
  54. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  55. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  56. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  57. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  58. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +2 -2
  59. data/lib/rubocop/cop/mixin/def_node.rb +1 -1
  60. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -1
  61. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +20 -0
  62. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +16 -0
  63. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +0 -1
  64. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -0
  65. data/lib/rubocop/cop/mixin/target_ruby_version.rb +1 -1
  66. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  67. data/lib/rubocop/cop/naming/method_name.rb +64 -8
  68. data/lib/rubocop/cop/naming/variable_name.rb +6 -19
  69. data/lib/rubocop/cop/registry.rb +9 -6
  70. data/lib/rubocop/cop/style/array_intersect.rb +39 -28
  71. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  72. data/lib/rubocop/cop/style/class_and_module_children.rb +29 -7
  73. data/lib/rubocop/cop/style/class_equality_comparison.rb +1 -1
  74. data/lib/rubocop/cop/style/collection_methods.rb +1 -0
  75. data/lib/rubocop/cop/style/combinable_loops.rb +1 -0
  76. data/lib/rubocop/cop/style/commented_keyword.rb +9 -2
  77. data/lib/rubocop/cop/style/comparable_between.rb +75 -0
  78. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -0
  79. data/lib/rubocop/cop/style/double_negation.rb +2 -2
  80. data/lib/rubocop/cop/style/empty_literal.rb +4 -0
  81. data/lib/rubocop/cop/style/explicit_block_argument.rb +2 -2
  82. data/lib/rubocop/cop/style/exponential_notation.rb +2 -2
  83. data/lib/rubocop/cop/style/for.rb +1 -0
  84. data/lib/rubocop/cop/style/format_string_token.rb +38 -11
  85. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +3 -2
  86. data/lib/rubocop/cop/style/global_std_stream.rb +3 -0
  87. data/lib/rubocop/cop/style/guard_clause.rb +2 -1
  88. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -2
  89. data/lib/rubocop/cop/style/hash_fetch_chain.rb +105 -0
  90. data/lib/rubocop/cop/style/hash_syntax.rb +3 -0
  91. data/lib/rubocop/cop/style/if_inside_else.rb +10 -13
  92. data/lib/rubocop/cop/style/if_unless_modifier.rb +2 -2
  93. data/lib/rubocop/cop/style/inverse_methods.rb +1 -0
  94. data/lib/rubocop/cop/style/invertible_unless_condition.rb +2 -2
  95. data/lib/rubocop/cop/style/ip_addresses.rb +2 -2
  96. data/lib/rubocop/cop/style/it_block_parameter.rb +100 -0
  97. data/lib/rubocop/cop/style/keyword_parameters_order.rb +1 -1
  98. data/lib/rubocop/cop/style/lambda.rb +1 -0
  99. data/lib/rubocop/cop/style/map_into_array.rb +1 -0
  100. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +4 -4
  101. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +1 -0
  102. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -0
  103. data/lib/rubocop/cop/style/next.rb +44 -0
  104. data/lib/rubocop/cop/style/object_then.rb +1 -0
  105. data/lib/rubocop/cop/style/proc.rb +1 -0
  106. data/lib/rubocop/cop/style/raise_args.rb +8 -8
  107. data/lib/rubocop/cop/style/redundant_begin.rb +1 -0
  108. data/lib/rubocop/cop/style/redundant_condition.rb +13 -1
  109. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +14 -4
  110. data/lib/rubocop/cop/style/redundant_format.rb +10 -3
  111. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
  112. data/lib/rubocop/cop/style/redundant_self.rb +1 -0
  113. data/lib/rubocop/cop/style/redundant_sort_by.rb +17 -1
  114. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -0
  115. data/lib/rubocop/cop/style/return_nil.rb +2 -2
  116. data/lib/rubocop/cop/style/select_by_regexp.rb +4 -1
  117. data/lib/rubocop/cop/style/single_line_do_end_block.rb +3 -1
  118. data/lib/rubocop/cop/style/sole_nested_conditional.rb +41 -100
  119. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  120. data/lib/rubocop/cop/style/symbol_proc.rb +2 -0
  121. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -0
  122. data/lib/rubocop/cop/util.rb +1 -1
  123. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  124. data/lib/rubocop/cop/variable_force/variable.rb +2 -7
  125. data/lib/rubocop/cop/variable_force.rb +1 -1
  126. data/lib/rubocop/directive_comment.rb +1 -1
  127. data/lib/rubocop/ext/regexp_node.rb +0 -1
  128. data/lib/rubocop/lsp/runtime.rb +4 -4
  129. data/lib/rubocop/lsp/stdin_runner.rb +3 -1
  130. data/lib/rubocop/magic_comment.rb +8 -0
  131. data/lib/rubocop/rspec/cop_helper.rb +4 -1
  132. data/lib/rubocop/rspec/shared_contexts.rb +20 -0
  133. data/lib/rubocop/rspec/support.rb +2 -0
  134. data/lib/rubocop/runner.rb +5 -1
  135. data/lib/rubocop/server/cache.rb +13 -10
  136. data/lib/rubocop/target_finder.rb +1 -1
  137. data/lib/rubocop/target_ruby.rb +1 -1
  138. data/lib/rubocop/version.rb +14 -7
  139. data/lib/rubocop.rb +5 -0
  140. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +20 -2
  141. metadata +11 -6
@@ -35,12 +35,12 @@ module RuboCop
35
35
  comment_line_numbers = processed_source.comments.map { |comment| comment.loc.line }
36
36
 
37
37
  comment_line_numbers.any? do |comment_line_number|
38
- comment_line_number >= node.first_line && comment_line_number <= node.last_line
38
+ comment_line_number.between?(node.first_line, node.last_line)
39
39
  end
40
40
  end
41
41
 
42
42
  def safe_to_split?(node)
43
- node.each_descendant(:if, :case, :kwbegin, :def, :defs).none? &&
43
+ node.each_descendant(:if, :case, :kwbegin, :any_def).none? &&
44
44
  node.each_descendant(:dstr, :str).none? { |n| n.heredoc? || n.value.include?("\n") } &&
45
45
  node.each_descendant(:begin, :sym).none? { |b| !b.single_line? }
46
46
  end
@@ -19,7 +19,7 @@ module RuboCop
19
19
 
20
20
  # @!method non_public_modifier?(node)
21
21
  def_node_matcher :non_public_modifier?, <<~PATTERN
22
- (send nil? {:private :protected :private_class_method} ({def defs} ...))
22
+ (send nil? {:private :protected :private_class_method} (any_def ...))
23
23
  PATTERN
24
24
  end
25
25
  end
@@ -21,7 +21,7 @@ module RuboCop
21
21
 
22
22
  # @!method empty_line_required?(node)
23
23
  def_node_matcher :empty_line_required?,
24
- '{def defs class module (send nil? {:private :protected :public})}'
24
+ '{any_def class module (send nil? {:private :protected :public})}'
25
25
 
26
26
  def check(node, body, adjusted_first_line: nil)
27
27
  return if valid_body_style?(body)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This module encapsulates the ability to forbid certain identifiers in a cop.
6
+ module ForbiddenIdentifiers
7
+ SIGILS = '@$' # if a variable starts with a sigil it will be removed
8
+
9
+ def forbidden_identifier?(name)
10
+ name = name.to_s.delete(SIGILS)
11
+
12
+ forbidden_identifiers.any? && forbidden_identifiers.include?(name)
13
+ end
14
+
15
+ def forbidden_identifiers
16
+ cop_config.fetch('ForbiddenIdentifiers', [])
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This module encapsulates the ability to forbid certain patterns in a cop.
6
+ module ForbiddenPattern
7
+ def forbidden_pattern?(name)
8
+ forbidden_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
9
+ end
10
+
11
+ def forbidden_patterns
12
+ cop_config.fetch('ForbiddenPatterns', [])
13
+ end
14
+ end
15
+ end
16
+ end
@@ -6,7 +6,6 @@ module RuboCop
6
6
  module FrozenStringLiteral
7
7
  module_function
8
8
 
9
- FROZEN_STRING_LITERAL_REGEXP = /#\s*frozen[-_]?string[-_]?literal:/i.freeze
10
9
  FROZEN_STRING_LITERAL_ENABLED = '# frozen_string_literal: true'
11
10
  FROZEN_STRING_LITERAL_TYPES_RUBY27 = %i[str dstr].freeze
12
11
 
@@ -30,6 +30,7 @@ module RuboCop
30
30
  end
31
31
 
32
32
  alias on_numblock on_block
33
+ alias on_itblock on_block
33
34
 
34
35
  private
35
36
 
@@ -29,7 +29,7 @@ module RuboCop
29
29
  min = required_minimum_ruby_version || 0
30
30
  max = required_maximum_ruby_version || Float::INFINITY
31
31
 
32
- min <= version && max >= version
32
+ version.between?(min, max)
33
33
  end
34
34
  end
35
35
  end
@@ -242,7 +242,7 @@ module RuboCop
242
242
  def find_definition(node)
243
243
  # Methods can be defined in a `def` or `defs`,
244
244
  # or dynamically via a `block` node.
245
- node.each_ancestor(:def, :defs, :block).each do |ancestor|
245
+ node.each_ancestor(:any_def, :block).each do |ancestor|
246
246
  method_node, method_name = method_definition?(ancestor)
247
247
  return [method_node, method_name] if method_node
248
248
  end
@@ -6,14 +6,31 @@ module RuboCop
6
6
  # Makes sure that all methods use the configured style,
7
7
  # snake_case or camelCase, for their names.
8
8
  #
9
- # This cop has `AllowedPatterns` configuration option.
9
+ # Method names matching patterns are always allowed.
10
10
  #
11
- # Naming/MethodName:
12
- # AllowedPatterns:
13
- # - '\AonSelectionBulkChange\z'
14
- # - '\AonSelectionCleared\z'
11
+ # The cop can be configured with `AllowedPatterns` to allow certain regexp patterns:
15
12
  #
16
- # Method names matching patterns are always allowed.
13
+ # [source,yaml]
14
+ # ----
15
+ # Naming/MethodName:
16
+ # AllowedPatterns:
17
+ # - '\AonSelectionBulkChange\z'
18
+ # - '\AonSelectionCleared\z'
19
+ # ----
20
+ #
21
+ # As well, you can also forbid specific method names or regexp patterns
22
+ # using `ForbiddenIdentifiers` or `ForbiddenPatterns`:
23
+ #
24
+ # [source,yaml]
25
+ # ----
26
+ # Naming/MethodName:
27
+ # ForbiddenIdentifiers:
28
+ # - 'def'
29
+ # - 'super'
30
+ # ForbiddenPatterns:
31
+ # - '_v1\z'
32
+ # - '_gen1\z'
33
+ # ----
17
34
  #
18
35
  # @example EnforcedStyle: snake_case (default)
19
36
  # # bad
@@ -28,12 +45,26 @@ module RuboCop
28
45
  #
29
46
  # # good
30
47
  # def fooBar; end
48
+ #
49
+ # @example ForbiddenIdentifiers: ['def', 'super']
50
+ # # bad
51
+ # def def; end
52
+ # def super; end
53
+ #
54
+ # @example ForbiddenPatterns: ['_v1\z', '_gen1\z']
55
+ # # bad
56
+ # def release_v1; end
57
+ # def api_gen1; end
58
+ #
31
59
  class MethodName < Base
32
60
  include ConfigurableNaming
33
61
  include AllowedPattern
34
62
  include RangeHelp
63
+ include ForbiddenIdentifiers
64
+ include ForbiddenPattern
35
65
 
36
66
  MSG = 'Use %<style>s for method names.'
67
+ MSG_FORBIDDEN = '`%<identifier>s` is forbidden, use another method name instead.'
37
68
 
38
69
  # @!method sym_name(node)
39
70
  def_node_matcher :sym_name, '(sym $_name)'
@@ -48,19 +79,44 @@ module RuboCop
48
79
  name = attr_name(name_item)
49
80
  next if !name || matches_allowed_pattern?(name)
50
81
 
51
- check_name(node, name, range_position(node))
82
+ if forbidden_name?(name.to_s)
83
+ register_forbidden_name(node)
84
+ else
85
+ check_name(node, name, range_position(node))
86
+ end
52
87
  end
53
88
  end
54
89
 
55
90
  def on_def(node)
56
91
  return if node.operator_method? || matches_allowed_pattern?(node.method_name)
57
92
 
58
- check_name(node, node.method_name, node.loc.name)
93
+ if forbidden_name?(node.method_name.to_s)
94
+ register_forbidden_name(node)
95
+ else
96
+ check_name(node, node.method_name, node.loc.name)
97
+ end
59
98
  end
60
99
  alias on_defs on_def
61
100
 
62
101
  private
63
102
 
103
+ def forbidden_name?(name)
104
+ forbidden_identifier?(name) || forbidden_pattern?(name)
105
+ end
106
+
107
+ def register_forbidden_name(node)
108
+ if node.any_def_type?
109
+ name_node = node.loc.name
110
+ method_name = node.method_name
111
+ else
112
+ attrs = node.attribute_accessor?
113
+ name_node = attrs.last.last
114
+ method_name = attr_name(name_node)
115
+ end
116
+ message = format(MSG_FORBIDDEN, identifier: method_name)
117
+ add_offense(name_node, message: message)
118
+ end
119
+
64
120
  def attr_name(name_item)
65
121
  sym_name(name_item) || str_name(name_item)
66
122
  end
@@ -53,6 +53,8 @@ module RuboCop
53
53
  include AllowedIdentifiers
54
54
  include ConfigurableNaming
55
55
  include AllowedPattern
56
+ include ForbiddenIdentifiers
57
+ include ForbiddenPattern
56
58
 
57
59
  MSG = 'Use %<style>s for variable names.'
58
60
  MSG_FORBIDDEN = '`%<identifier>s` is forbidden, use another name instead.'
@@ -92,27 +94,12 @@ module RuboCop
92
94
 
93
95
  private
94
96
 
95
- def message(style)
96
- format(MSG, style: style)
97
- end
98
-
99
- def forbidden_identifiers
100
- cop_config.fetch('ForbiddenIdentifiers', [])
101
- end
102
-
103
- def forbidden_patterns
104
- cop_config.fetch('ForbiddenPatterns', [])
105
- end
106
-
107
- def matches_forbidden_pattern?(name)
108
- forbidden_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
109
- end
110
-
111
97
  def forbidden_name?(name)
112
- name = name.to_s.delete(SIGILS)
98
+ forbidden_identifier?(name) || forbidden_pattern?(name)
99
+ end
113
100
 
114
- (forbidden_identifiers.any? && forbidden_identifiers.include?(name)) ||
115
- (forbidden_patterns.any? && matches_forbidden_pattern?(name))
101
+ def message(style)
102
+ format(MSG, style: style)
116
103
  end
117
104
 
118
105
  def register_forbidden_name(node)
@@ -23,8 +23,8 @@ module RuboCop
23
23
  global.without_department(:Test).cops
24
24
  end
25
25
 
26
- def self.qualified_cop_name(name, origin)
27
- global.qualified_cop_name(name, origin)
26
+ def self.qualified_cop_name(name, origin, warn: true)
27
+ global.qualified_cop_name(name, origin, warn: warn)
28
28
  end
29
29
 
30
30
  # Changes momentarily the global registry
@@ -139,7 +139,7 @@ module RuboCop
139
139
 
140
140
  case potential_badges.size
141
141
  when 0 then name # No namespace found. Deal with it later in caller.
142
- when 1 then resolve_badge(badge, potential_badges.first, path)
142
+ when 1 then resolve_badge(badge, potential_badges.first, path, warn: warn)
143
143
  else raise AmbiguousCopName.new(badge, path, potential_badges)
144
144
  end
145
145
  end
@@ -296,11 +296,14 @@ module RuboCop
296
296
  self.class.new(cops)
297
297
  end
298
298
 
299
- def resolve_badge(given_badge, real_badge, source_path)
299
+ def resolve_badge(given_badge, real_badge, source_path, warn: true)
300
300
  unless given_badge.match?(real_badge)
301
301
  path = PathUtil.smart_path(source_path)
302
- warn "#{path}: #{given_badge} has the wrong namespace - " \
303
- "replace it with #{given_badge.with_department(real_badge.department)}"
302
+
303
+ if warn
304
+ warn("#{path}: #{given_badge} has the wrong namespace - " \
305
+ "replace it with #{given_badge.with_department(real_badge.department)}")
306
+ end
304
307
  end
305
308
 
306
309
  real_badge.to_s
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # In Ruby 3.1, `Array#intersect?` has been added.
7
7
  #
8
8
  # This cop identifies places where `(array1 & array2).any?`
9
- # can be replaced by `array1.intersect?(array2)`.
9
+ # or `(array1.intersection(array2)).any?` can be replaced by
10
+ # `array1.intersect?(array2)`.
10
11
  #
11
12
  # The `array1.intersect?(array2)` method is faster than
12
13
  # `(array1 & array2).any?` and is more readable.
@@ -20,6 +21,10 @@ module RuboCop
20
21
  # [1].intersect?([1,2]) { |x| false } # => true
21
22
  # ----
22
23
  #
24
+ # NOTE: Although `Array#intersection` can take zero or multiple arguments,
25
+ # only cases where exactly one argument is provided can be replaced with
26
+ # `Array#intersect?` and are handled by this cop.
27
+ #
23
28
  # @safety
24
29
  # This cop cannot guarantee that `array1` and `array2` are
25
30
  # actually arrays while method `intersect?` is for arrays only.
@@ -30,6 +35,11 @@ module RuboCop
30
35
  # (array1 & array2).empty?
31
36
  # (array1 & array2).none?
32
37
  #
38
+ # # bad
39
+ # array1.intersection(array2).any?
40
+ # array1.intersection(array2).empty?
41
+ # array1.intersection(array2).none?
42
+ #
33
43
  # # good
34
44
  # array1.intersect?(array2)
35
45
  # !array1.intersect?(array2)
@@ -53,65 +63,66 @@ module RuboCop
53
63
 
54
64
  minimum_target_ruby_version 3.1
55
65
 
56
- # @!method regular_bad_intersection_check?(node)
57
- def_node_matcher :regular_bad_intersection_check?, <<~PATTERN
58
- (send
59
- (begin
60
- (send $(...) :& $(...))
61
- ) ${:any? :empty? :none?}
62
- )
63
- PATTERN
66
+ PREDICATES = %i[any? empty? none?].to_set.freeze
67
+ ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze
64
68
 
65
- # @!method active_support_bad_intersection_check?(node)
66
- def_node_matcher :active_support_bad_intersection_check?, <<~PATTERN
67
- (send
68
- (begin
69
- (send $(...) :& $(...))
70
- ) ${:present? :any? :blank? :empty? :none?}
69
+ # @!method bad_intersection_check?(node, predicates)
70
+ def_node_matcher :bad_intersection_check?, <<~PATTERN
71
+ (call
72
+ {
73
+ (begin (send $_ :& $_))
74
+ (call $_ :intersection $_)
75
+ }
76
+ $%1
71
77
  )
72
78
  PATTERN
73
79
 
74
- MSG = 'Use `%<negated>s%<receiver>s.intersect?(%<argument>s)` ' \
75
- 'instead of `(%<receiver>s & %<argument>s).%<method_name>s`.'
80
+ MSG = 'Use `%<negated>s%<receiver>s%<dot>sintersect?(%<argument>s)` ' \
81
+ 'instead of `%<existing>s`.'
76
82
  STRAIGHT_METHODS = %i[present? any?].freeze
77
83
  NEGATED_METHODS = %i[blank? empty? none?].freeze
78
84
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
79
85
 
80
86
  def on_send(node)
81
87
  return if node.block_literal?
82
- return unless (receiver, argument, method_name = bad_intersection_check?(node))
88
+ return unless (receiver, argument, method_name = bad_intersection?(node))
83
89
 
84
- message = message(receiver.source, argument.source, method_name)
90
+ dot = node.loc.dot.source
91
+ message = message(receiver.source, argument.source, method_name, dot, node.source)
85
92
 
86
93
  add_offense(node, message: message) do |corrector|
87
94
  bang = straight?(method_name) ? '' : '!'
88
95
 
89
- corrector.replace(node, "#{bang}#{receiver.source}.intersect?(#{argument.source})")
96
+ corrector.replace(node, "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})")
90
97
  end
91
98
  end
99
+ alias on_csend on_send
92
100
 
93
101
  private
94
102
 
95
- def bad_intersection_check?(node)
96
- if active_support_extensions_enabled?
97
- active_support_bad_intersection_check?(node)
98
- else
99
- regular_bad_intersection_check?(node)
100
- end
103
+ def bad_intersection?(node)
104
+ predicates = if active_support_extensions_enabled?
105
+ ACTIVE_SUPPORT_PREDICATES
106
+ else
107
+ PREDICATES
108
+ end
109
+
110
+ bad_intersection_check?(node, predicates)
101
111
  end
102
112
 
103
113
  def straight?(method_name)
104
114
  STRAIGHT_METHODS.include?(method_name.to_sym)
105
115
  end
106
116
 
107
- def message(receiver, argument, method_name)
117
+ def message(receiver, argument, method_name, dot, existing)
108
118
  negated = straight?(method_name) ? '' : '!'
109
119
  format(
110
120
  MSG,
111
121
  negated: negated,
112
122
  receiver: receiver,
113
123
  argument: argument,
114
- method_name: method_name
124
+ dot: dot,
125
+ existing: existing
115
126
  )
116
127
  end
117
128
  end
@@ -208,6 +208,7 @@ module RuboCop
208
208
  end
209
209
 
210
210
  alias on_numblock on_block
211
+ alias on_itblock on_block
211
212
 
212
213
  private
213
214
 
@@ -347,7 +348,7 @@ module RuboCop
347
348
  # rubocop:disable Metrics/CyclomaticComplexity
348
349
  def get_blocks(node, &block)
349
350
  case node.type
350
- when :block, :numblock
351
+ when :block, :numblock, :itblock
351
352
  yield node
352
353
  when :send, :csend
353
354
  # When a method has an argument which is another method with a block,
@@ -3,14 +3,26 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks the style of children definitions at classes and
7
- # modules. Basically there are two different styles:
6
+ # Checks that namespaced classes and modules are defined with a consistent style.
7
+ #
8
+ # With `nested` style, classes and modules should be defined separately (one constant
9
+ # on each line, without `::`). With `compact` style, classes and modules should be
10
+ # defined with fully qualified names (using `::` for namespaces).
11
+ #
12
+ # NOTE: The style chosen will affect `Module.nesting` for the class or module. Using
13
+ # `nested` style will result in each level being added, whereas `compact` style will
14
+ # only include the fully qualified class or module name.
15
+ #
16
+ # By default, `EnforcedStyle` applies to both classes and modules. If desired, separate
17
+ # styles can be defined for classes and modules by using `EnforcedStyleForClasses` and
18
+ # `EnforcedStyleForModules` respectively. If not set, or set to nil, the `EnforcedStyle`
19
+ # value will be used.
8
20
  #
9
21
  # @safety
10
22
  # Autocorrection is unsafe.
11
23
  #
12
- # Moving from compact to nested children requires knowledge of whether the
13
- # outer parent is a module or a class. Moving from nested to compact requires
24
+ # Moving from `compact` to `nested` children requires knowledge of whether the
25
+ # outer parent is a module or a class. Moving from `nested` to `compact` requires
14
26
  # verification that the outer parent is defined elsewhere. RuboCop does not
15
27
  # have the knowledge to perform either operation safely and thus requires
16
28
  # manual oversight.
@@ -42,16 +54,18 @@ module RuboCop
42
54
  def on_class(node)
43
55
  return if node.parent_class && style != :nested
44
56
 
45
- check_style(node, node.body)
57
+ check_style(node, node.body, style_for_classes)
46
58
  end
47
59
 
48
60
  def on_module(node)
49
- check_style(node, node.body)
61
+ check_style(node, node.body, style_for_modules)
50
62
  end
51
63
 
52
64
  private
53
65
 
54
66
  def nest_or_compact(corrector, node)
67
+ style = node.class_type? ? style_for_classes : style_for_modules
68
+
55
69
  if style == :nested
56
70
  nest_definition(corrector, node)
57
71
  else
@@ -141,7 +155,7 @@ module RuboCop
141
155
  node.source_range.source_line[/\A\s*/]
142
156
  end
143
157
 
144
- def check_style(node, body)
158
+ def check_style(node, body, style)
145
159
  return if node.identifier.namespace&.cbase_type?
146
160
 
147
161
  if style == :nested
@@ -183,6 +197,14 @@ module RuboCop
183
197
  def compact_node_name?(node)
184
198
  node.identifier.source.include?('::')
185
199
  end
200
+
201
+ def style_for_classes
202
+ cop_config['EnforcedStyleForClasses'] || style
203
+ end
204
+
205
+ def style_for_modules
206
+ cop_config['EnforcedStyleForModules'] || style
207
+ end
186
208
  end
187
209
  end
188
210
  end
@@ -68,7 +68,7 @@ module RuboCop
68
68
  PATTERN
69
69
 
70
70
  def on_send(node)
71
- def_node = node.each_ancestor(:def, :defs).first
71
+ def_node = node.each_ancestor(:any_def).first
72
72
  return if def_node &&
73
73
  (allowed_method?(def_node.method_name) ||
74
74
  matches_allowed_pattern?(def_node.method_name))
@@ -50,6 +50,7 @@ module RuboCop
50
50
  check_method_node(node.send_node)
51
51
  end
52
52
  alias on_numblock on_block
53
+ alias on_itblock on_block
53
54
 
54
55
  def on_send(node)
55
56
  return unless implicit_block?(node)
@@ -80,6 +80,7 @@ module RuboCop
80
80
  # rubocop:enable Metrics/CyclomaticComplexity
81
81
 
82
82
  alias on_numblock on_block
83
+ alias on_itblock on_block
83
84
 
84
85
  def on_for(node)
85
86
  return unless node.parent&.begin_type?
@@ -9,8 +9,8 @@ module RuboCop
9
9
  # These keywords are: `class`, `module`, `def`, `begin`, `end`.
10
10
  #
11
11
  # Note that some comments
12
- # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
13
- # and RBS::Inline annotation comments are allowed.
12
+ # (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`),
13
+ # RBS::Inline annotation, and Steep annotation (`steep:ignore`) are allowed.
14
14
  #
15
15
  # Autocorrection removes comments from `end` keyword and keeps comments
16
16
  # for `class`, `module`, `def` and `begin` above the keyword.
@@ -60,6 +60,8 @@ module RuboCop
60
60
  SUBCLASS_DEFINITION = /\A\s*class\s+(\w|::)+\s*<\s*(\w|::)+/.freeze
61
61
  METHOD_DEFINITION = /\A\s*def\s/.freeze
62
62
 
63
+ STEEP_REGEXP = /#\ssteep:ignore(\s|\z)/.freeze
64
+
63
65
  def on_new_investigation
64
66
  processed_source.comments.each do |comment|
65
67
  next unless offensive?(comment) && (match = source_line(comment).match(REGEXP))
@@ -86,6 +88,7 @@ module RuboCop
86
88
  def offensive?(comment)
87
89
  line = source_line(comment)
88
90
  return false if rbs_inline_annotation?(line, comment)
91
+ return false if steep_annotation?(comment)
89
92
 
90
93
  KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
91
94
  ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
@@ -105,6 +108,10 @@ module RuboCop
105
108
  false
106
109
  end
107
110
  end
111
+
112
+ def steep_annotation?(comment)
113
+ comment.text.match?(STEEP_REGEXP)
114
+ end
108
115
  end
109
116
  end
110
117
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for logical comparison which can be replaced with `Comparable#between?`.
7
+ #
8
+ # NOTE: `Comparable#between?` is on average slightly slower than logical comparison,
9
+ # although the difference generally isn't observable. If you require maximum
10
+ # performance, consider using logical comparison.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # x >= min && x <= max
16
+ #
17
+ # # bad
18
+ # x <= max && x >= min
19
+ #
20
+ # # good
21
+ # x.between?(min, max)
22
+ #
23
+ class ComparableBetween < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Prefer `%<prefer>s` over logical comparison.'
27
+
28
+ # @!method logical_comparison_between_by_min_first?(node)
29
+ def_node_matcher :logical_comparison_between_by_min_first?, <<~PATTERN
30
+ (and
31
+ (send
32
+ {$_value :>= $_min | $_min :<= $_value})
33
+ (send
34
+ {$_value :<= $_max | $_max :>= $_value}))
35
+ PATTERN
36
+
37
+ # @!method logical_comparison_between_by_max_first?(node)
38
+ def_node_matcher :logical_comparison_between_by_max_first?, <<~PATTERN
39
+ (and
40
+ (send
41
+ {$_value :<= $_max | $_max :>= $_value})
42
+ (send
43
+ {$_value :>= $_min | $_min :<= $_value}))
44
+ PATTERN
45
+
46
+ def on_and(node)
47
+ logical_comparison_between_by_min_first?(node) do |*args|
48
+ min_and_value, max_and_value = args.each_slice(2).to_a
49
+
50
+ register_offense(node, min_and_value, max_and_value)
51
+ end
52
+
53
+ logical_comparison_between_by_max_first?(node) do |*args|
54
+ max_and_value, min_and_value = args.each_slice(2).to_a
55
+
56
+ register_offense(node, min_and_value, max_and_value)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def register_offense(node, min_and_value, max_and_value)
63
+ value = (min_and_value & max_and_value).first
64
+ min = min_and_value.find { _1 != value }
65
+ max = max_and_value.find { _1 != value }
66
+
67
+ prefer = "#{value.source}.between?(#{min.source}, #{max.source})"
68
+ add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
69
+ corrector.replace(node, prefer)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end