rubocop 1.75.8 → 1.81.7

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -16
  3. data/config/default.yml +121 -28
  4. data/config/obsoletion.yml +6 -3
  5. data/exe/rubocop +1 -8
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  7. data/lib/rubocop/cli.rb +18 -3
  8. data/lib/rubocop/config_loader.rb +4 -39
  9. data/lib/rubocop/config_loader_resolver.rb +5 -4
  10. data/lib/rubocop/config_store.rb +5 -0
  11. data/lib/rubocop/cop/autocorrect_logic.rb +4 -4
  12. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  14. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  16. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
  18. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  20. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -1
  21. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  22. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +4 -1
  23. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  24. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  25. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  26. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  27. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  28. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  29. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  30. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
  31. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -1
  32. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
  33. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
  34. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -5
  35. data/lib/rubocop/cop/layout/line_length.rb +35 -6
  36. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  37. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  38. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  39. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  40. data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
  41. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
  42. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  43. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  44. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  45. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +13 -7
  46. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  47. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  48. data/lib/rubocop/cop/lint/duplicate_methods.rb +25 -4
  49. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  50. data/lib/rubocop/cop/lint/empty_interpolation.rb +14 -1
  51. data/lib/rubocop/cop/lint/float_comparison.rb +4 -4
  52. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  53. data/lib/rubocop/cop/lint/literal_as_condition.rb +34 -28
  54. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +17 -8
  55. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  56. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  57. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  58. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +4 -4
  59. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  60. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  61. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  62. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  63. data/lib/rubocop/cop/lint/self_assignment.rb +31 -5
  64. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  65. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  66. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  67. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  68. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  69. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  70. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  71. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  72. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  73. data/lib/rubocop/cop/lint/void.rb +7 -0
  74. data/lib/rubocop/cop/message_annotator.rb +1 -1
  75. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  76. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  77. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  78. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  79. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  80. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  81. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  82. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  83. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  84. data/lib/rubocop/cop/naming/method_name.rb +129 -13
  85. data/lib/rubocop/cop/naming/predicate_method.rb +319 -0
  86. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  87. data/lib/rubocop/cop/security/eval.rb +2 -1
  88. data/lib/rubocop/cop/security/json_load.rb +33 -11
  89. data/lib/rubocop/cop/security/open.rb +1 -0
  90. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  91. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  92. data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
  93. data/lib/rubocop/cop/style/array_intersect.rb +99 -35
  94. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  95. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  96. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  97. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  98. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  99. data/lib/rubocop/cop/style/conditional_assignment.rb +11 -5
  100. data/lib/rubocop/cop/style/constant_visibility.rb +14 -9
  101. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  102. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  103. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  104. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  105. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  106. data/lib/rubocop/cop/style/exponential_notation.rb +3 -2
  107. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  108. data/lib/rubocop/cop/style/float_division.rb +15 -1
  109. data/lib/rubocop/cop/style/hash_conversion.rb +16 -8
  110. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  111. data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
  112. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  113. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  114. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  115. data/lib/rubocop/cop/style/it_block_parameter.rb +36 -15
  116. data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
  117. data/lib/rubocop/cop/style/map_to_set.rb +1 -3
  118. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +4 -6
  119. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +16 -0
  120. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  121. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  122. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  123. data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
  124. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  125. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  126. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  127. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  128. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -9
  129. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  130. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  131. data/lib/rubocop/cop/style/redundant_interpolation.rb +12 -3
  132. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  133. data/lib/rubocop/cop/style/redundant_parentheses.rb +55 -16
  134. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  135. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  136. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  137. data/lib/rubocop/cop/style/safe_navigation.rb +44 -12
  138. data/lib/rubocop/cop/style/semicolon.rb +23 -7
  139. data/lib/rubocop/cop/style/single_line_methods.rb +7 -4
  140. data/lib/rubocop/cop/style/sole_nested_conditional.rb +40 -3
  141. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  142. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  143. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  144. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  145. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  146. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  147. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  148. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  149. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  150. data/lib/rubocop/cop/variable_force.rb +25 -8
  151. data/lib/rubocop/cops_documentation_generator.rb +5 -4
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  153. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  154. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  155. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  156. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  157. data/lib/rubocop/lsp/diagnostic.rb +25 -24
  158. data/lib/rubocop/lsp/routes.rb +65 -9
  159. data/lib/rubocop/lsp/runtime.rb +2 -2
  160. data/lib/rubocop/lsp/server.rb +2 -2
  161. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  162. data/lib/rubocop/pending_cops_reporter.rb +56 -0
  163. data/lib/rubocop/result_cache.rb +14 -12
  164. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  165. data/lib/rubocop/runner.rb +6 -4
  166. data/lib/rubocop/server/cache.rb +4 -2
  167. data/lib/rubocop/server/client_command/base.rb +10 -0
  168. data/lib/rubocop/server/client_command/exec.rb +2 -1
  169. data/lib/rubocop/server/client_command/start.rb +11 -1
  170. data/lib/rubocop/target_finder.rb +9 -9
  171. data/lib/rubocop/target_ruby.rb +10 -1
  172. data/lib/rubocop/version.rb +1 -1
  173. data/lib/rubocop.rb +12 -1
  174. data/lib/ruby_lsp/rubocop/addon.rb +25 -10
  175. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  176. metadata +18 -7
@@ -25,20 +25,24 @@ module RuboCop
25
25
  config.for_cop('Layout/LineLength')['AllowURI']
26
26
  end
27
27
 
28
- def allowed_uri_position?(line, uri_range)
29
- uri_range.begin < max_line_length && uri_range.end == line_length(line)
28
+ def allow_qualified_name?
29
+ config.for_cop('Layout/LineLength')['AllowQualifiedName']
30
+ end
31
+
32
+ def allowed_position?(line, range)
33
+ range.begin < max_line_length && range.end == line_length(line)
30
34
  end
31
35
 
32
36
  def line_length(line)
33
37
  line.length + indentation_difference(line)
34
38
  end
35
39
 
36
- def find_excessive_uri_range(line)
37
- last_uri_match = match_uris(line).last
38
- return nil unless last_uri_match
40
+ def find_excessive_range(line, type)
41
+ last_match = (type == :uri ? match_uris(line) : match_qualified_names(line)).last
42
+ return nil unless last_match
39
43
 
40
- begin_position, end_position = last_uri_match.offset(0)
41
- end_position = extend_uri_end_position(line, end_position)
44
+ begin_position, end_position = last_match.offset(0)
45
+ end_position = extend_end_position(line, end_position)
42
46
 
43
47
  line_indentation_difference = indentation_difference(line)
44
48
  begin_position += line_indentation_difference
@@ -57,6 +61,14 @@ module RuboCop
57
61
  matches
58
62
  end
59
63
 
64
+ def match_qualified_names(string)
65
+ matches = []
66
+ string.scan(qualified_name_regexp) do
67
+ matches << $LAST_MATCH_INFO
68
+ end
69
+ matches
70
+ end
71
+
60
72
  def indentation_difference(line)
61
73
  return 0 unless tab_indentation_width
62
74
 
@@ -70,7 +82,7 @@ module RuboCop
70
82
  index * (tab_indentation_width - 1)
71
83
  end
72
84
 
73
- def extend_uri_end_position(line, end_position)
85
+ def extend_end_position(line, end_position)
74
86
  # Extend the end position YARD comments with linked URLs of the form {<uri> <title>}
75
87
  if line&.match(/{(\s|\S)*}$/)
76
88
  match = line[end_position..line_length(line)]&.match(/(\s|\S)*}/)
@@ -101,6 +113,10 @@ module RuboCop
101
113
  end
102
114
  end
103
115
 
116
+ def qualified_name_regexp
117
+ /\b(?:[A-Z][A-Za-z0-9_]*::)+[A-Za-z_][A-Za-z0-9_]*\b/
118
+ end
119
+
104
120
  def valid_uri?(uri_ish_string)
105
121
  URI.parse(uri_ish_string)
106
122
  true
@@ -24,7 +24,7 @@ module RuboCop
24
24
  gem_canonical_name(string_a) < gem_canonical_name(string_b)
25
25
  end
26
26
 
27
- def consecutive_lines(previous, current)
27
+ def consecutive_lines?(previous, current)
28
28
  first_line = get_source_range(current, treat_comments_as_separators).first_line
29
29
  previous.source_range.last_line == first_line - 1
30
30
  end
@@ -140,7 +140,7 @@ module RuboCop
140
140
  end
141
141
 
142
142
  def last_item_precedes_newline?(node)
143
- after_last_item = node.children.last.source_range.end.join(node.loc.end.begin)
143
+ after_last_item = node.children.last.source_range.end.join(node.source_range.end)
144
144
 
145
145
  after_last_item.source.start_with?(/,?\s*(#.*)?\n/)
146
146
  end
@@ -152,7 +152,7 @@ module RuboCop
152
152
 
153
153
  const_namespace, const_name = *const
154
154
  next if name != const_name && !match_acronym?(name, const_name)
155
- next unless namespace.empty? || match_namespace(child, const_namespace, namespace)
155
+ next unless namespace.empty? || namespace_matches?(child, const_namespace, namespace)
156
156
 
157
157
  return node
158
158
  end
@@ -169,7 +169,7 @@ module RuboCop
169
169
  s(:const, namespace, name) if name
170
170
  end
171
171
 
172
- def match_namespace(node, namespace, expected)
172
+ def namespace_matches?(node, namespace, expected)
173
173
  match_partial = partial_matcher!(expected)
174
174
 
175
175
  match_partial.call(namespace)
@@ -39,6 +39,26 @@ module RuboCop
39
39
  # # good
40
40
  # def foo_bar; end
41
41
  #
42
+ # # bad
43
+ # define_method :fooBar do
44
+ # end
45
+ #
46
+ # # good
47
+ # define_method :foo_bar do
48
+ # end
49
+ #
50
+ # # bad
51
+ # Struct.new(:fooBar)
52
+ #
53
+ # # good
54
+ # Struct.new(:foo_bar)
55
+ #
56
+ # # bad
57
+ # alias_method :fooBar, :some_method
58
+ #
59
+ # # good
60
+ # alias_method :foo_bar, :some_method
61
+ #
42
62
  # @example EnforcedStyle: camelCase
43
63
  # # bad
44
64
  # def foo_bar; end
@@ -46,6 +66,26 @@ module RuboCop
46
66
  # # good
47
67
  # def fooBar; end
48
68
  #
69
+ # # bad
70
+ # define_method :foo_bar do
71
+ # end
72
+ #
73
+ # # good
74
+ # define_method :fooBar do
75
+ # end
76
+ #
77
+ # # bad
78
+ # Struct.new(:foo_bar)
79
+ #
80
+ # # good
81
+ # Struct.new(:fooBar)
82
+ #
83
+ # # bad
84
+ # alias_method :foo_bar, :some_method
85
+ #
86
+ # # good
87
+ # alias_method :fooBar, :some_method
88
+ #
49
89
  # @example ForbiddenIdentifiers: ['def', 'super']
50
90
  # # bad
51
91
  # def def; end
@@ -66,13 +106,81 @@ module RuboCop
66
106
  MSG = 'Use %<style>s for method names.'
67
107
  MSG_FORBIDDEN = '`%<identifier>s` is forbidden, use another method name instead.'
68
108
 
109
+ OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * /
110
+ % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze
111
+
69
112
  # @!method sym_name(node)
70
113
  def_node_matcher :sym_name, '(sym $_name)'
71
114
 
72
115
  # @!method str_name(node)
73
116
  def_node_matcher :str_name, '(str $_name)'
74
117
 
118
+ # @!method new_struct?(node)
119
+ def_node_matcher :new_struct?, '(send (const {nil? cbase} :Struct) :new ...)'
120
+
121
+ # @!method define_data?(node)
122
+ def_node_matcher :define_data?, '(send (const {nil? cbase} :Data) :define ...)'
123
+
75
124
  def on_send(node)
125
+ if node.method?(:define_method) || node.method?(:define_singleton_method)
126
+ handle_define_method(node)
127
+ elsif new_struct?(node)
128
+ handle_new_struct(node)
129
+ elsif define_data?(node)
130
+ handle_define_data(node)
131
+ elsif node.method?(:alias_method)
132
+ handle_alias_method(node)
133
+ else
134
+ handle_attr_accessor(node)
135
+ end
136
+ end
137
+
138
+ def on_def(node)
139
+ return if node.operator_method? || matches_allowed_pattern?(node.method_name)
140
+
141
+ if forbidden_name?(node.method_name.to_s)
142
+ register_forbidden_name(node)
143
+ else
144
+ check_name(node, node.method_name, node.loc.name)
145
+ end
146
+ end
147
+ alias on_defs on_def
148
+
149
+ def on_alias(node)
150
+ return unless (new_identifier = node.new_identifier).sym_type?
151
+
152
+ handle_method_name(new_identifier, new_identifier.value)
153
+ end
154
+
155
+ private
156
+
157
+ def handle_define_method(node)
158
+ return unless node.first_argument&.type?(:str, :sym)
159
+
160
+ handle_method_name(node, node.first_argument.value)
161
+ end
162
+
163
+ def handle_new_struct(node)
164
+ arguments = node.first_argument&.str_type? ? node.arguments[1..] : node.arguments
165
+ arguments.select { |argument| argument.type?(:sym, :str) }.each do |name|
166
+ handle_method_name(name, name.value)
167
+ end
168
+ end
169
+
170
+ def handle_define_data(node)
171
+ node.arguments.select { |argument| argument.type?(:sym, :str) }.each do |name|
172
+ handle_method_name(name, name.value)
173
+ end
174
+ end
175
+
176
+ def handle_alias_method(node)
177
+ return unless node.arguments.size == 2
178
+ return unless node.first_argument.type?(:str, :sym)
179
+
180
+ handle_method_name(node.first_argument, node.first_argument.value)
181
+ end
182
+
183
+ def handle_attr_accessor(node)
76
184
  return unless (attrs = node.attribute_accessor?)
77
185
 
78
186
  attrs.last.each do |name_item|
@@ -87,45 +195,53 @@ module RuboCop
87
195
  end
88
196
  end
89
197
 
90
- def on_def(node)
91
- return if node.operator_method? || matches_allowed_pattern?(node.method_name)
198
+ def handle_method_name(node, name)
199
+ return if !name || matches_allowed_pattern?(name)
92
200
 
93
- if forbidden_name?(node.method_name.to_s)
201
+ if forbidden_name?(name.to_s)
94
202
  register_forbidden_name(node)
95
- else
96
- check_name(node, node.method_name, node.loc.name)
203
+ elsif !OPERATOR_METHODS.include?(name.to_sym)
204
+ check_name(node, name, range_position(node))
97
205
  end
98
206
  end
99
- alias on_defs on_def
100
-
101
- private
102
207
 
103
208
  def forbidden_name?(name)
104
209
  forbidden_identifier?(name) || forbidden_pattern?(name)
105
210
  end
106
211
 
212
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
107
213
  def register_forbidden_name(node)
108
214
  if node.any_def_type?
109
215
  name_node = node.loc.name
110
216
  method_name = node.method_name
111
- else
112
- attrs = node.attribute_accessor?
217
+ elsif node.literal?
218
+ name_node = node
219
+ method_name = node.value
220
+ elsif (attrs = node.attribute_accessor?)
113
221
  name_node = attrs.last.last
114
222
  method_name = attr_name(name_node)
223
+ else
224
+ name_node = node.first_argument
225
+ method_name = node.first_argument.value
115
226
  end
116
227
  message = format(MSG_FORBIDDEN, identifier: method_name)
117
228
  add_offense(name_node, message: message)
118
229
  end
230
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
119
231
 
120
232
  def attr_name(name_item)
121
233
  sym_name(name_item) || str_name(name_item)
122
234
  end
123
235
 
124
236
  def range_position(node)
125
- selector_end_pos = node.loc.selector.end_pos + 1
126
- expr_end_pos = node.source_range.end_pos
237
+ if node.loc.respond_to?(:selector)
238
+ selector_end_pos = node.loc.selector.end_pos + 1
239
+ expr_end_pos = node.source_range.end_pos
127
240
 
128
- range_between(selector_end_pos, expr_end_pos)
241
+ range_between(selector_end_pos, expr_end_pos)
242
+ else
243
+ node.source_range
244
+ end
129
245
  end
130
246
 
131
247
  def message(style)
@@ -0,0 +1,319 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # Checks that predicate methods end with `?` and non-predicate methods do not.
7
+ #
8
+ # The names of predicate methods (methods that return a boolean value) should end
9
+ # in a question mark. Methods that don't return a boolean, shouldn't
10
+ # end in a question mark.
11
+ #
12
+ # The cop assesses a predicate method as one that returns boolean values. Likewise,
13
+ # a method that only returns literal values is assessed as non-predicate. Other predicate
14
+ # method calls are assumed to return boolean values. The cop does not make an assessment
15
+ # if the return type is unknown (non-predicate method calls, variables, etc.).
16
+ #
17
+ # NOTE: The `initialize` method and operator methods (`def ==`, etc.) are ignored.
18
+ #
19
+ # By default, the cop runs in `conservative` mode, which allows a method to be named
20
+ # with a question mark as long as at least one return value is boolean. In `aggressive`
21
+ # mode, methods with a question mark will register an offense if any known non-boolean
22
+ # return values are detected.
23
+ #
24
+ # The cop also has `AllowedMethods` configuration in order to prevent the cop from
25
+ # registering an offense from a method name that does not confirm to the naming
26
+ # guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
27
+ # configuration to allow method names by regular expression.
28
+ #
29
+ # Although returning a call to another predicate method is treated as a boolean value,
30
+ # certain method names can be known to not return a boolean, despite ending in a `?`
31
+ # (for example, `Numeric#nonzero?` returns `self` or `nil`). These methods can be
32
+ # configured using `NonBooleanPredicates`.
33
+ #
34
+ # The cop can furthermore be configured to allow all bang methods (method names
35
+ # ending with `!`), with `AllowBangMethods: true` (default false).
36
+ #
37
+ # @example Mode: conservative (default)
38
+ # # bad
39
+ # def foo
40
+ # bar == baz
41
+ # end
42
+ #
43
+ # # good
44
+ # def foo?
45
+ # bar == baz
46
+ # end
47
+ #
48
+ # # bad
49
+ # def foo?
50
+ # 5
51
+ # end
52
+ #
53
+ # # good
54
+ # def foo
55
+ # 5
56
+ # end
57
+ #
58
+ # # bad
59
+ # def foo
60
+ # x == y
61
+ # end
62
+ #
63
+ # # good
64
+ # def foo?
65
+ # x == y
66
+ # end
67
+ #
68
+ # # bad
69
+ # def foo
70
+ # !x
71
+ # end
72
+ #
73
+ # # good
74
+ # def foo?
75
+ # !x
76
+ # end
77
+ #
78
+ # # bad - returns the value of another predicate method
79
+ # def foo
80
+ # bar?
81
+ # end
82
+ #
83
+ # # good
84
+ # def foo?
85
+ # bar?
86
+ # end
87
+ #
88
+ # # good - operator method
89
+ # def ==(other)
90
+ # hash == other.hash
91
+ # end
92
+ #
93
+ # # good - at least one return value is boolean
94
+ # def foo?
95
+ # return unless bar?
96
+ # true
97
+ # end
98
+ #
99
+ # # ok - return type is not known
100
+ # def foo?
101
+ # bar
102
+ # end
103
+ #
104
+ # # ok - return type is not known
105
+ # def foo
106
+ # bar?
107
+ # end
108
+ #
109
+ # @example Mode: aggressive
110
+ # # bad - the method returns nil in some cases
111
+ # def foo?
112
+ # return unless bar?
113
+ # true
114
+ # end
115
+ #
116
+ # @example AllowedMethods: [call] (default)
117
+ # # good
118
+ # def call
119
+ # foo == bar
120
+ # end
121
+ #
122
+ # @example AllowedPatterns: [\Afoo]
123
+ # # good
124
+ # def foo?
125
+ # 'foo'
126
+ # end
127
+ #
128
+ # @example AllowBangMethods: false (default)
129
+ # # bad
130
+ # def save!
131
+ # true
132
+ # end
133
+ #
134
+ # @example AllowBangMethods: true
135
+ # # good
136
+ # def save!
137
+ # true
138
+ # end
139
+ #
140
+ class PredicateMethod < Base
141
+ include AllowedMethods
142
+ include AllowedPattern
143
+
144
+ MSG_PREDICATE = 'Predicate method names should end with `?`.'
145
+ MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
146
+
147
+ def on_def(node)
148
+ return if allowed?(node)
149
+
150
+ return_values = return_values(node.body)
151
+ return if acceptable?(return_values)
152
+
153
+ if node.predicate_method? && potential_non_predicate?(return_values)
154
+ add_offense(node.loc.name, message: MSG_NON_PREDICATE)
155
+ elsif !node.predicate_method? && all_return_values_boolean?(return_values)
156
+ add_offense(node.loc.name, message: MSG_PREDICATE)
157
+ end
158
+ end
159
+ alias on_defs on_def
160
+
161
+ private
162
+
163
+ def allowed?(node)
164
+ node.method?(:initialize) ||
165
+ allowed_method?(node.method_name) ||
166
+ matches_allowed_pattern?(node.method_name) ||
167
+ allowed_bang_method?(node) ||
168
+ node.operator_method? ||
169
+ node.body.nil?
170
+ end
171
+
172
+ def acceptable?(return_values)
173
+ # In `conservative` mode, if the method returns `super`, `zsuper`, or a
174
+ # non-comparison method call, the method name is acceptable.
175
+ return false unless conservative?
176
+
177
+ return_values.any? do |value|
178
+ value.type?(:super, :zsuper) || unknown_method_call?(value)
179
+ end
180
+ end
181
+
182
+ def unknown_method_call?(value)
183
+ return false unless value.call_type?
184
+
185
+ !method_returning_boolean?(value)
186
+ end
187
+
188
+ def return_values(node)
189
+ # Collect all the (implicit and explicit) return values of a node
190
+ return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
191
+
192
+ node.each_descendant(:return) do |return_node|
193
+ return_values << extract_return_value(return_node)
194
+ end
195
+
196
+ return_values << last_value(node)
197
+
198
+ process_return_values(return_values)
199
+ end
200
+
201
+ def all_return_values_boolean?(return_values)
202
+ values = return_values.reject { |value| value.type?(:super, :zsuper) }
203
+ return false if values.empty?
204
+
205
+ values.all? { |value| boolean_return?(value) }
206
+ end
207
+
208
+ def boolean_return?(value)
209
+ return true if value.boolean_type?
210
+
211
+ method_returning_boolean?(value)
212
+ end
213
+
214
+ def method_returning_boolean?(value)
215
+ return false unless value.call_type?
216
+ return false if wayward_predicate?(value.method_name)
217
+
218
+ value.comparison_method? || value.predicate_method? || value.negation_method?
219
+ end
220
+
221
+ def potential_non_predicate?(return_values)
222
+ # Assumes a method to be non-predicate if all return values are non-boolean literals.
223
+ #
224
+ # In `Mode: conservative`, if any of the return values is a boolean,
225
+ # the method name is acceptable.
226
+ # In `Mode: aggressive`, all return values must be booleans for a predicate
227
+ # method, or else an offense will be registered.
228
+ return false if conservative? && return_values.any? { |value| boolean_return?(value) }
229
+
230
+ return_values.any? do |value|
231
+ value.literal? && !value.boolean_type?
232
+ end
233
+ end
234
+
235
+ def extract_return_value(node)
236
+ return node unless node.return_type?
237
+
238
+ # `return` without a value is a `nil` return.
239
+ return s(:nil) if node.arguments.empty?
240
+
241
+ # When there's a multiple return, it cannot be a predicate
242
+ # so just return an `array` sexp for simplicity.
243
+ return s(:array) unless node.arguments.one?
244
+
245
+ node.first_argument
246
+ end
247
+
248
+ def last_value(node)
249
+ value = node.begin_type? ? node.children.last || s(:nil) : node
250
+
251
+ value.return_type? ? extract_return_value(value) : value
252
+ end
253
+
254
+ def process_return_values(return_values)
255
+ return_values.flat_map do |value|
256
+ if value.conditional?
257
+ process_return_values(extract_conditional_branches(value))
258
+ elsif and_or?(value)
259
+ process_return_values(extract_and_or_clauses(value))
260
+ else
261
+ value
262
+ end
263
+ end
264
+ end
265
+
266
+ def and_or?(node)
267
+ node.type?(:and, :or)
268
+ end
269
+
270
+ def extract_and_or_clauses(node)
271
+ # Recursively traverse an `and` or `or` node to collect all clauses within
272
+ return node unless and_or?(node)
273
+
274
+ [extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
275
+ end
276
+
277
+ def extract_conditional_branches(node)
278
+ return node unless node.conditional?
279
+
280
+ if node.type?(:while, :until)
281
+ # If there is no body, act as implicit `nil`.
282
+ node.body ? [last_value(node.body)] : [s(:nil)]
283
+ else
284
+ # Branches with no value act as an implicit `nil`.
285
+ branches = node.branches.map { |branch| branch ? last_value(branch) : s(:nil) }
286
+ # Missing else branches also act as an implicit `nil`.
287
+ branches.push(s(:nil)) unless node.else_branch
288
+ branches
289
+ end
290
+ end
291
+
292
+ def conservative?
293
+ cop_config.fetch('Mode', :conservative).to_sym == :conservative
294
+ end
295
+
296
+ def allowed_bang_method?(node)
297
+ return false unless allow_bang_methods?
298
+
299
+ node.bang_method?
300
+ end
301
+
302
+ def allow_bang_methods?
303
+ cop_config.fetch('AllowBangMethods', false)
304
+ end
305
+
306
+ # If a method ending in `?` is known to not return a boolean value,
307
+ # (for example, `Numeric#nonzero?`) it should be treated as a non-boolean
308
+ # value, despite the method naming.
309
+ def wayward_predicate?(name)
310
+ wayward_predicates.include?(name.to_s)
311
+ end
312
+
313
+ def wayward_predicates
314
+ Array(cop_config.fetch('WaywardPredicates', []))
315
+ end
316
+ end
317
+ end
318
+ end
319
+ end
@@ -100,12 +100,12 @@ module RuboCop
100
100
  # # good
101
101
  # def_node_matcher(:even?) { |value| }
102
102
  #
103
- class PredicateName < Base
103
+ class PredicatePrefix < Base
104
104
  include AllowedMethods
105
105
 
106
106
  # @!method dynamic_method_define(node)
107
107
  def_node_matcher :dynamic_method_define, <<~PATTERN
108
- (send nil? #method_definition_macros
108
+ (send nil? #method_definition_macro?
109
109
  (sym $_)
110
110
  ...)
111
111
  PATTERN
@@ -143,7 +143,7 @@ module RuboCop
143
143
  next if predicate_prefixes.include?(forbidden_prefix)
144
144
 
145
145
  raise ValidationError, <<~MSG.chomp
146
- The `Naming/PredicateName` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
146
+ The `Naming/PredicatePrefix` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
147
147
  MSG
148
148
  end
149
149
  end
@@ -195,7 +195,7 @@ module RuboCop
195
195
  cop_config['UseSorbetSigs']
196
196
  end
197
197
 
198
- def method_definition_macros(macro_name)
198
+ def method_definition_macro?(macro_name)
199
199
  cop_config['MethodDefinitionMacros'].include?(macro_name.to_s)
200
200
  end
201
201
  end
@@ -11,13 +11,14 @@ module RuboCop
11
11
  #
12
12
  # eval(something)
13
13
  # binding.eval(something)
14
+ # Kernel.eval(something)
14
15
  class Eval < Base
15
16
  MSG = 'The use of `eval` is a serious security risk.'
16
17
  RESTRICT_ON_SEND = %i[eval].freeze
17
18
 
18
19
  # @!method eval?(node)
19
20
  def_node_matcher :eval?, <<~PATTERN
20
- (send {nil? (send nil? :binding)} :eval $!str ...)
21
+ (send {nil? (send nil? :binding) (const {cbase nil?} :Kernel)} :eval $!str ...)
21
22
  PATTERN
22
23
 
23
24
  def on_send(node)