rubocop 1.89.0 → 1.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +74 -7
  3. data/exe/rubocop +1 -1
  4. data/lib/rubocop/cached_data.rb +7 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +31 -0
  6. data/lib/rubocop/cli.rb +2 -4
  7. data/lib/rubocop/comment_config/directive_range.rb +32 -0
  8. data/lib/rubocop/comment_config/disable_next.rb +97 -0
  9. data/lib/rubocop/comment_config.rb +46 -30
  10. data/lib/rubocop/config.rb +23 -5
  11. data/lib/rubocop/config_loader.rb +1 -2
  12. data/lib/rubocop/config_loader_resolver.rb +1 -2
  13. data/lib/rubocop/config_validator.rb +1 -2
  14. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  15. data/lib/rubocop/cop/base.rb +34 -2
  16. data/lib/rubocop/cop/corrector.rb +3 -3
  17. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  18. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  19. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  20. data/lib/rubocop/cop/layout/class_structure.rb +102 -26
  21. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  22. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  23. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  24. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  25. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  26. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  27. data/lib/rubocop/cop/layout/line_length.rb +1 -2
  28. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  29. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
  30. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  31. data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
  32. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
  33. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  34. data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
  35. data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
  36. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
  37. data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
  38. data/lib/rubocop/cop/lint/duplicate_methods.rb +112 -14
  39. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  40. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  41. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  42. data/lib/rubocop/cop/lint/literal_as_condition.rb +3 -6
  43. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
  44. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  45. data/lib/rubocop/cop/lint/name_typo.rb +53 -6
  46. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
  47. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
  48. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  49. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  50. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  51. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  52. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  53. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  54. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
  55. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  56. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  57. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  58. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
  59. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  60. data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
  61. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  62. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  63. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  64. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  65. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  66. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  67. data/lib/rubocop/cop/lint/void.rb +1 -2
  68. data/lib/rubocop/cop/lint.rb +2 -0
  69. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  70. data/lib/rubocop/cop/migration/department_name.rb +1 -2
  71. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  72. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  73. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
  74. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  75. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  76. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  77. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
  78. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  79. data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
  80. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  81. data/lib/rubocop/cop/mixin.rb +4 -0
  82. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  83. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  84. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  85. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  86. data/lib/rubocop/cop/offense.rb +27 -4
  87. data/lib/rubocop/cop/registry.rb +34 -10
  88. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  89. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
  90. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  91. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  92. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  93. data/lib/rubocop/cop/style/case_like_if.rb +2 -4
  94. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
  95. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  96. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  97. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
  98. data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
  99. data/lib/rubocop/cop/style/copyright.rb +1 -2
  100. data/lib/rubocop/cop/style/directive_scope.rb +171 -0
  101. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +36 -10
  102. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  103. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  104. data/lib/rubocop/cop/style/guard_clause.rb +1 -2
  105. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  106. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  107. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  108. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  109. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
  110. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  111. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  112. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  113. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  114. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  115. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  116. data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
  117. data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
  118. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  119. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  120. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
  121. data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
  122. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
  123. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  124. data/lib/rubocop/cop/style/redundant_format.rb +2 -4
  125. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -4
  126. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  127. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  128. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  129. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  130. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  131. data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
  132. data/lib/rubocop/cop/style/sample.rb +11 -6
  133. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  134. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  135. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  136. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
  137. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  138. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  139. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  140. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  141. data/lib/rubocop/cop/style/time_now.rb +41 -0
  142. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  143. data/lib/rubocop/cop/style.rb +2 -0
  144. data/lib/rubocop/cop/team.rb +11 -3
  145. data/lib/rubocop/cop/util.rb +2 -4
  146. data/lib/rubocop/cop/variable_force/variable.rb +1 -2
  147. data/lib/rubocop/cops_documentation_generator.rb +3 -6
  148. data/lib/rubocop/directive_comment.rb +37 -3
  149. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  150. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  151. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  152. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  153. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  154. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  155. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  156. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  157. data/lib/rubocop/lockfile.rb +18 -0
  158. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  159. data/lib/rubocop/lsp/routes.rb +48 -19
  160. data/lib/rubocop/mcp/server.rb +1 -2
  161. data/lib/rubocop/options.rb +10 -6
  162. data/lib/rubocop/path_util.rb +1 -2
  163. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  164. data/lib/rubocop/rake_task.rb +1 -2
  165. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  166. data/lib/rubocop/rspec/shared_contexts.rb +4 -2
  167. data/lib/rubocop/runner.rb +26 -23
  168. data/lib/rubocop/server/cache.rb +1 -2
  169. data/lib/rubocop/server/cli.rb +2 -4
  170. data/lib/rubocop/server.rb +14 -0
  171. data/lib/rubocop/target_finder.rb +4 -1
  172. data/lib/rubocop/todo_audit.rb +111 -0
  173. data/lib/rubocop/version.rb +3 -5
  174. data/lib/rubocop.rb +3 -0
  175. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  176. metadata +13 -5
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for `super` calls with explicit arguments that pass the wrong
7
+ # number of positional arguments to the overridden implementation, using
8
+ # the project index.
9
+ #
10
+ # The check is powered by the project-wide index, so it only runs when
11
+ # `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed.
12
+ # Without the index the cop does nothing.
13
+ #
14
+ # Only `super` inside a plain instance method defined directly in a class
15
+ # body is considered, and only when the enclosing class resolves in the
16
+ # index and its entire ancestry is resolved, so a superclass method coming
17
+ # from a gem, the standard library, or a dynamic definition never produces
18
+ # an offense. The overridden implementation is the first ancestor after
19
+ # the enclosing class in the index's method resolution order that defines
20
+ # the method; when it is not a plain method definition (an `attr_*` or an
21
+ # alias) or its definitions disagree on arity, the call is not checked.
22
+ # Calls that forward arguments (`*`, `**`, `...`) are skipped, and bare
23
+ # `super` (which forwards the current method's parameters) is not checked.
24
+ # Methods the index attributes to `Object`, `Kernel`, or `BasicObject`
25
+ # are never used as the overridden implementation, since a `def` inside
26
+ # a block at the top level (`Struct.new do ... end`) is indexed under
27
+ # `Object` even though it defines a method somewhere else entirely.
28
+ #
29
+ # @example
30
+ # # Given the project defines:
31
+ # # class Base
32
+ # # def initialize(name, size); end
33
+ # # end
34
+ #
35
+ # # bad
36
+ # class Widget < Base
37
+ # def initialize(name)
38
+ # super(name)
39
+ # end
40
+ # end
41
+ #
42
+ # # good
43
+ # class Widget < Base
44
+ # def initialize(name)
45
+ # super(name, 0)
46
+ # end
47
+ # end
48
+ #
49
+ class SuperArgumentMismatch < Base
50
+ include ProjectIndexHelp
51
+ include IndexedMethodArity
52
+
53
+ MSG = 'Wrong number of arguments to `super` ' \
54
+ '(given %<given>d, expected %<expected>s for `%<parent>s#%<method>s`).'
55
+
56
+ # Blocks that change what method (or scope) `super` refers to, or in
57
+ # which `super` is not statically resolvable.
58
+ CONTEXT_CHANGING_BLOCKS = %i[
59
+ define_method define_singleton_method class_eval class_exec
60
+ module_eval module_exec instance_eval instance_exec refine
61
+ ].freeze
62
+
63
+ # Methods the index attributes to the root namespaces are not
64
+ # trustworthy super targets: a `def` inside a block at the top level
65
+ # (`Struct.new do ... end`, `Class.new do ... end`) is indexed under
66
+ # `Object` even though it defines a method somewhere else entirely.
67
+ BUILTIN_ROOTS = %w[Object Kernel BasicObject].freeze
68
+
69
+ def on_super(node)
70
+ return unless project_index
71
+
72
+ def_node = enclosing_instance_method(node)
73
+ return unless def_node
74
+
75
+ shape, parent = resolved_super_shape(def_node)
76
+ return unless shape
77
+
78
+ min, max, has_keywords = shape
79
+ given = positional_argument_count(node, has_keywords)
80
+ return if given.nil? || arity_satisfied?(given, min, max)
81
+
82
+ add_offense(node.loc.keyword,
83
+ message: message(def_node.method_name, parent, given, min, max))
84
+ end
85
+
86
+ private
87
+
88
+ def message(method_name, parent, given, min, max)
89
+ format(MSG, given: given, expected: expected_range(min, max),
90
+ parent: parent.name, method: method_name)
91
+ end
92
+
93
+ # The `def` node whose method the `super` call re-dispatches, or nil
94
+ # when the context is not statically known: `super` inside a
95
+ # singleton method, inside `define_method` or an eval/refine block,
96
+ # or outside any method.
97
+ def enclosing_instance_method(node)
98
+ node.each_ancestor(:any_def, :any_block, :sclass).each do |ancestor|
99
+ case ancestor.type
100
+ when :def then return ancestor
101
+ when :defs, :sclass then return nil
102
+ else
103
+ return nil if CONTEXT_CHANGING_BLOCKS.include?(ancestor.method_name)
104
+ end
105
+ end
106
+
107
+ nil
108
+ end
109
+
110
+ # `[[min, max, has_keywords], parent]` for the implementation `super`
111
+ # dispatches to, or nil when the enclosing class or the overridden
112
+ # method is not statically known.
113
+ def resolved_super_shape(def_node)
114
+ declaration = enclosing_class_declaration(def_node)
115
+ return nil unless declaration
116
+ return nil unless fully_resolved_index_ancestry?(declaration, ignore_extend: true)
117
+
118
+ super_method_shape(declaration, def_node.method_name)
119
+ rescue StandardError
120
+ nil
121
+ end
122
+
123
+ # The index declaration of the class whose body directly contains
124
+ # `def_node`, or nil when the method is not defined directly in a
125
+ # class body (module methods are excluded because their `super`
126
+ # target depends on where the module is mixed in) or the class does
127
+ # not resolve in the index.
128
+ def enclosing_class_declaration(def_node)
129
+ class_node = def_node.each_ancestor(:class, :module, :sclass, :any_block).first
130
+ return nil unless class_node&.class_type?
131
+
132
+ declaration = project_index[lexical_nesting_of(def_node).join('::')]
133
+ return nil unless declaration.is_a?(Rubydex::Namespace)
134
+ return nil unless declared_at?(declaration, class_node)
135
+
136
+ declaration
137
+ end
138
+
139
+ # Whether one of the declaration's definitions is the given class
140
+ # node, anchoring the name-based lookup to the source being
141
+ # inspected (a qualified identifier like `class Foo::Bar` can
142
+ # resolve to a namespace other than the joined nesting names).
143
+ def declared_at?(declaration, class_node)
144
+ declaration.definitions.any? do |definition|
145
+ location = definition.location
146
+ location.uri.start_with?(FILE_URI_PREFIX) &&
147
+ location.to_display.start_line == class_node.first_line &&
148
+ same_file?(location.to_file_path, processed_source.file_path)
149
+ end
150
+ end
151
+
152
+ # The shape of the first implementation of `method_name` after the
153
+ # enclosing class in the index's method resolution order, or nil
154
+ # when no indexed ancestor defines it (e.g. it comes from a gem or
155
+ # the standard library, which are not indexed).
156
+ def super_method_shape(declaration, method_name)
157
+ ancestors = declaration.ancestors.to_a
158
+ position = ancestors.index { |ancestor| ancestor.name == declaration.name }
159
+ return nil unless position
160
+
161
+ ancestors.drop(position + 1).each do |ancestor|
162
+ return nil if BUILTIN_ROOTS.include?(ancestor.name)
163
+
164
+ member = ancestor.member("#{method_name}()")
165
+ next unless member
166
+
167
+ # The first definer along the chain is the receiving
168
+ # implementation; when its shape is unknown there is nothing
169
+ # further to check.
170
+ shape = indexed_member_shape(member)
171
+ return shape && [shape, ancestor]
172
+ end
173
+
174
+ nil
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -78,7 +78,7 @@ module RuboCop
78
78
 
79
79
  minimum_target_ruby_version 2.6
80
80
 
81
- # rubocop:disable Metrics/AbcSize
81
+ # rubocop:disable-next Metrics/AbcSize
82
82
  def on_rescue(node)
83
83
  if (method, exception_classes = begin_numeric_constructor_rescue_nil(node.parent))
84
84
  return unless expected_exception_classes_only?(exception_classes)
@@ -100,7 +100,6 @@ module RuboCop
100
100
  corrector.replace(node, prefer)
101
101
  end
102
102
  end
103
- # rubocop:enable Metrics/AbcSize
104
103
 
105
104
  private
106
105
 
@@ -135,7 +135,7 @@ module RuboCop
135
135
  end
136
136
  end
137
137
 
138
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
138
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
139
139
  def argument_match?(send_arg, def_arg)
140
140
  def_arg_name = def_arg.children[0]
141
141
 
@@ -157,7 +157,6 @@ module RuboCop
157
157
  send_arg.forwarded_args_type?
158
158
  end
159
159
  end
160
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
161
160
 
162
161
  def keyword_hash_argument?(send_arg)
163
162
  send_arg.hash_type? && !send_arg.braces?
@@ -83,7 +83,7 @@ module RuboCop
83
83
  }
84
84
  PATTERN
85
85
 
86
- # rubocop:disable Metrics/MethodLength
86
+ # rubocop:disable-next Metrics/MethodLength
87
87
  def flow_expression?(node)
88
88
  return report_on_flow_command?(node) if flow_command?(node)
89
89
 
@@ -102,7 +102,6 @@ module RuboCop
102
102
  false
103
103
  end
104
104
  end
105
- # rubocop:enable Metrics/MethodLength
106
105
 
107
106
  def check_if(node)
108
107
  if_branch = node.if_branch
@@ -14,10 +14,17 @@ module RuboCop
14
14
  # in the indexed project (regardless of the receiver), when it is the source
15
15
  # of an `alias`, or when its name appears in the same file as a symbol or
16
16
  # inside a string literal (covering `send(:name)` and declarative DSLs like
17
- # `before_action :name`). Methods defined in classes or modules with
18
- # descendants are not checked, since they may be invoked through `super`
19
- # or inherited dispatch, and neither are methods whose names are built
20
- # dynamically (e.g. `send("do_#{action}")`).
17
+ # `before_action :name`). An interpolated symbol or string with a literal
18
+ # prefix (e.g. `send(:"format_#{type}")`) counts as a reference to every
19
+ # method whose name starts with that prefix; fully dynamic names cannot
20
+ # be detected. Methods defined in classes or modules with descendants are
21
+ # not checked, since they may be invoked through `super` or inherited
22
+ # dispatch.
23
+ #
24
+ # Methods whose names are composed by a framework (e.g. Rails
25
+ # `attribute_method_suffix` generating `attribute?` methods) can be
26
+ # excluded from the check with `AllowedNames` (exact names) or
27
+ # `AllowedPatterns` (regular expressions).
21
28
  #
22
29
  # The cop is disabled by default because symbol-based references from
23
30
  # *other* files (e.g. a Rails callback declared in a concern) cannot be
@@ -49,28 +56,56 @@ module RuboCop
49
56
  # end
50
57
  # end
51
58
  #
59
+ # @example AllowedNames: ['attribute?']
60
+ # # good - the name is explicitly allowed
61
+ # class Contact
62
+ # private
63
+ #
64
+ # def attribute?
65
+ # end
66
+ # end
67
+ #
68
+ # @example AllowedPatterns: ['_hook\z']
69
+ # # good - the name matches an allowed pattern
70
+ # class Service
71
+ # private
72
+ #
73
+ # def before_save_hook
74
+ # end
75
+ # end
76
+ #
52
77
  class UnusedPrivateMethod < Base
78
+ include AllowedPattern
53
79
  include ProjectIndexHelp
54
80
 
55
81
  MSG = 'Private method `%<method>s` appears to be unused.'
56
82
 
57
83
  IDENTIFIER_PATTERN = /[a-zA-Z_]\w*[?!=]?/.freeze
84
+ NAME_PREFIX_PATTERN = /[a-zA-Z_]\w*\z/.freeze
58
85
 
59
86
  # Methods invoked implicitly by the Ruby runtime.
60
- IMPLICITLY_INVOKED_METHODS = %i[initialize initialize_copy initialize_clone
61
- initialize_dup method_missing respond_to_missing?
62
- marshal_dump marshal_load].to_set.freeze
87
+ IMPLICITLY_INVOKED_METHODS = %i[
88
+ initialize initialize_copy initialize_clone initialize_dup
89
+ method_missing respond_to_missing? marshal_dump marshal_load
90
+ coerce instance_variables_to_inspect
91
+ inherited included extended prepended
92
+ append_features extend_object prepend_features
93
+ const_missing const_added
94
+ method_added method_removed method_undefined
95
+ singleton_method_added singleton_method_removed singleton_method_undefined
96
+ ].to_set.freeze
63
97
 
64
98
  class << self
65
- # The reference-name set is derived once per index and shared by the
66
- # per-file cop instances.
67
- def reference_names_cache
68
- @reference_names_cache ||= {}.compare_by_identity
69
- end
99
+ # The reference-name set is a property of the index, not of the cop
100
+ # instance, so all per-file instances share one computation. A
101
+ # single-entry cache (instead of a hash keyed by index) avoids
102
+ # retaining stale graphs in long-lived processes.
103
+ attr_accessor :cached_reference_names
70
104
  end
71
105
 
72
106
  def on_new_investigation
73
107
  @literal_names = nil
108
+ @literal_name_prefixes = nil
74
109
  super
75
110
  end
76
111
 
@@ -90,6 +125,7 @@ module RuboCop
90
125
 
91
126
  def checkable_declaration(node)
92
127
  return nil if IMPLICITLY_INVOKED_METHODS.include?(node.method_name)
128
+ return nil if allowed_name?(node.method_name)
93
129
  return nil if node.each_ancestor(:any_def, :any_block, :sclass).any?
94
130
  return nil unless (namespace_node = node.each_ancestor(:class, :module).first)
95
131
 
@@ -97,6 +133,12 @@ module RuboCop
97
133
  declaration if declaration&.private?
98
134
  end
99
135
 
136
+ def allowed_name?(method_name)
137
+ name = method_name.to_s
138
+
139
+ cop_config.fetch('AllowedNames', []).include?(name) || matches_allowed_pattern?(name)
140
+ end
141
+
100
142
  def method_declaration(node, namespace_node)
101
143
  namespace = resolve_constant_in_index(namespace_node.identifier)
102
144
  return nil unless namespace.is_a?(Rubydex::Namespace)
@@ -107,13 +149,22 @@ module RuboCop
107
149
  def referenced?(method_name)
108
150
  name = method_name.to_s
109
151
 
110
- referenced_names.include?(name) || literal_names.include?(name)
152
+ referenced_names.include?(name) || literal_names.include?(name) ||
153
+ literal_name_prefixes.any? { |prefix| name.start_with?(prefix) }
111
154
  end
112
155
 
113
156
  def referenced_names
114
- self.class.reference_names_cache[project_index] ||=
115
- project_index.method_references
116
- .to_set { |reference| reference.name.delete_suffix('()') }
157
+ index, names = self.class.cached_reference_names
158
+ return names if index.equal?(project_index)
159
+
160
+ compute_referenced_names.tap do |computed|
161
+ self.class.cached_reference_names = [project_index, computed]
162
+ end
163
+ end
164
+
165
+ def compute_referenced_names
166
+ project_index.method_references
167
+ .to_set { |reference| reference.name.delete_suffix('()') }
117
168
  end
118
169
 
119
170
  # Symbol literals and identifier-like tokens inside string literals in the
@@ -127,11 +178,37 @@ module RuboCop
127
178
  if literal.sym_type?
128
179
  names << literal.value.to_s
129
180
  else
130
- literal.value.scan(IDENTIFIER_PATTERN) { |token| names << token }
181
+ # `scan` raises on binary string literals with invalid byte sequences.
182
+ value = literal.value
183
+ value = value.scrub unless value.valid_encoding?
184
+ value.scan(IDENTIFIER_PATTERN) { |token| names << token }
131
185
  end
132
186
  end
133
187
  end
134
188
 
189
+ # The leading literal fragment of an interpolated symbol or string acts
190
+ # as a method-name prefix: `send(:"format_#{type}")` may invoke any
191
+ # method whose name starts with `format_`. Only the leading fragment
192
+ # qualifies (a mid-string fragment is not a name prefix), and a fully
193
+ # dynamic name contributes nothing.
194
+ def literal_name_prefixes
195
+ @literal_name_prefixes ||=
196
+ processed_source.ast.each_descendant(:dsym, :dstr)
197
+ .with_object(Set.new) do |literal, prefixes|
198
+ prefix = interpolation_prefix(literal)
199
+ prefixes << prefix if prefix
200
+ end
201
+ end
202
+
203
+ def interpolation_prefix(node)
204
+ return nil if node.children.all?(&:str_type?)
205
+
206
+ leading_fragment = node.children.first
207
+ return nil unless leading_fragment&.str_type?
208
+
209
+ leading_fragment.value[NAME_PREFIX_PATTERN]
210
+ end
211
+
135
212
  # A method defined in a class or module with descendants may be invoked
136
213
  # through `super` or inherited dispatch, which the index does not track.
137
214
  def owner_with_descendants?(declaration)
@@ -210,7 +210,7 @@ module RuboCop
210
210
  end
211
211
  end
212
212
 
213
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
213
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
214
214
  def check_child_nodes(node, unused, cur_vis)
215
215
  node.child_nodes.each do |child|
216
216
  if child.send_type? && access_modifier?(child)
@@ -228,7 +228,6 @@ module RuboCop
228
228
 
229
229
  [cur_vis, unused]
230
230
  end
231
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
232
231
 
233
232
  def check_send_node(node, cur_vis, unused)
234
233
  if node.bare_access_modifier?
@@ -173,7 +173,7 @@ module RuboCop
173
173
  node.receiver.nil? && !node.arguments?
174
174
  end
175
175
 
176
- # rubocop:disable Metrics/AbcSize
176
+ # rubocop:disable-next Metrics/AbcSize
177
177
  def autocorrect(corrector, assignment)
178
178
  if assignment.exception_assignment?
179
179
  remove_exception_assignment_part(corrector, assignment.node)
@@ -189,7 +189,6 @@ module RuboCop
189
189
  remove_local_variable_assignment_part(corrector, assignment.node)
190
190
  end
191
191
  end
192
- # rubocop:enable Metrics/AbcSize
193
192
 
194
193
  def remove_exception_assignment_part(corrector, node)
195
194
  range = node.parent.children.first&.source_range || node.parent.location.keyword
@@ -10,6 +10,11 @@ module RuboCop
10
10
  # It does not support autocorrection due to behavior change and multiple ways to fix it.
11
11
  # Or a public constant may be intended.
12
12
  #
13
+ # Constant assignments that define classes or modules via `Class.new`, `Module.new`,
14
+ # `Struct.new`, or `Data.define` are allowed. Those forms are class and module definitions
15
+ # written with assignment syntax, and match the common practice of placing nested
16
+ # `class` / `module` bodies after `private` without intending private constant visibility.
17
+ #
13
18
  # @example
14
19
  #
15
20
  # # bad
@@ -29,6 +34,19 @@ module RuboCop
29
34
  # PUBLIC_CONST = 42 # If private scope is not intended.
30
35
  # end
31
36
  #
37
+ # # good - class/module definitions via assignment, same as nested `class`/`module`
38
+ # class Foo
39
+ # private
40
+ #
41
+ # def some_private_method
42
+ # end
43
+ #
44
+ # MyClass = Class.new
45
+ # MyModule = Module.new
46
+ # MyStruct = Struct.new(:name)
47
+ # MyData = Data.define(:name)
48
+ # end
49
+ #
32
50
  class UselessConstantScoping < Base
33
51
  MSG = 'Useless `private` access modifier for constant scope.'
34
52
 
@@ -37,9 +55,27 @@ module RuboCop
37
55
  (send nil? :private_constant $...)
38
56
  PATTERN
39
57
 
58
+ # Matches class/module-like constant assignments. Nested `class` / `module`
59
+ # keyword definitions are not visited by this cop; these assignment forms are
60
+ # the equivalent syntax and should be treated the same way.
61
+ # @!method class_or_module_definition_assignment?(node)
62
+ def_node_matcher :class_or_module_definition_assignment?, <<~PATTERN
63
+ {
64
+ (send (const {nil? cbase} {:Class :Module :Struct}) :new ...)
65
+ (send (const {nil? cbase} :Data) :define ...)
66
+ (any_block
67
+ {
68
+ (send (const {nil? cbase} {:Class :Module :Struct}) :new ...)
69
+ (send (const {nil? cbase} :Data) :define ...)
70
+ }
71
+ ...)
72
+ }
73
+ PATTERN
74
+
40
75
  def on_casgn(node)
41
76
  return unless after_private_modifier?(node.left_siblings)
42
77
  return if private_constantize?(node.right_siblings, node.name)
78
+ return if class_or_module_definition_assignment?(node.expression)
43
79
 
44
80
  add_offense(node)
45
81
  end
@@ -59,7 +59,7 @@ module RuboCop
59
59
  end
60
60
 
61
61
  def use_rest_or_optional_args?(node)
62
- node.arguments.any? { |arg| arg.type?(:restarg, :optarg, :kwoptarg) }
62
+ node.arguments.any? { |arg| arg.type?(:restarg, :optarg, :kwoptarg, :kwrestarg) }
63
63
  end
64
64
 
65
65
  def delegating?(node, def_node)
@@ -56,7 +56,7 @@ module RuboCop
56
56
 
57
57
  private
58
58
 
59
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
59
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity
60
60
  def only_reraising?(resbody_node)
61
61
  return false if use_exception_variable_in_ensure?(resbody_node)
62
62
 
@@ -70,7 +70,6 @@ module RuboCop
70
70
 
71
71
  exception_objects(resbody_node).include?(exception_name)
72
72
  end
73
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
74
73
 
75
74
  def use_exception_variable_in_ensure?(resbody_node)
76
75
  return false unless (exception_variable = resbody_node.exception_variable)
@@ -12,6 +12,8 @@ module RuboCop
12
12
  @receiver = receiver
13
13
  @additional_nil_methods = additional_nil_methods
14
14
  @checked_nodes = {}.compare_by_identity
15
+ @receiver_binding_name = binding_name(receiver)
16
+ @receiver_binding_scope = binding_scope(receiver) if @receiver_binding_name
15
17
  end
16
18
 
17
19
  def cant_be_nil?
@@ -20,7 +22,7 @@ module RuboCop
20
22
 
21
23
  private
22
24
 
23
- # rubocop:disable Metrics
25
+ # rubocop:disable-next Metrics
24
26
  def _cant_be_nil?(node, receiver)
25
27
  return false unless node
26
28
 
@@ -34,7 +36,9 @@ module RuboCop
34
36
  when :def, :defs, :class, :module, :sclass
35
37
  return false
36
38
  when :send
37
- return non_nil_method?(node.method_name) if node.receiver == receiver
39
+ if node.receiver == receiver && same_binding_as_receiver?(node.receiver)
40
+ return non_nil_method?(node.method_name)
41
+ end
38
42
 
39
43
  node.arguments.each do |argument|
40
44
  return true if _cant_be_nil?(argument, receiver)
@@ -74,13 +78,12 @@ module RuboCop
74
78
  false
75
79
  end
76
80
  end
77
- # rubocop:enable Metrics
78
81
 
79
82
  def non_nil_method?(method_name)
80
83
  !NIL_METHODS.include?(method_name) && !@additional_nil_methods.include?(method_name)
81
84
  end
82
85
 
83
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
86
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
84
87
  def sole_condition_of_parent_if?(node)
85
88
  child = node
86
89
  parent = node.parent
@@ -104,12 +107,63 @@ module RuboCop
104
107
 
105
108
  false
106
109
  end
107
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
108
110
 
109
111
  def non_nil_condition?(condition, node)
110
- return true if condition == node
112
+ return true if condition == node && same_binding_as_receiver?(condition)
113
+ return false unless condition.csend_type?
114
+
115
+ root_receiver = csend_root_receiver(condition)
116
+
117
+ root_receiver == node && same_binding_as_receiver?(root_receiver)
118
+ end
119
+
120
+ # Structurally equal occurrences can be different variables: `it` binds to
121
+ # its innermost block, and a block parameter can shadow an outer variable of
122
+ # the same name. Evidence about such a receiver only holds when both occurrences
123
+ # resolve to the same binding.
124
+ def same_binding_as_receiver?(occurrence)
125
+ return true unless @receiver_binding_name
126
+
127
+ binding_scope(occurrence).equal?(@receiver_binding_scope)
128
+ end
129
+
130
+ # The name whose binding can differ between structurally equal occurrences:
131
+ # a local variable (block parameters included), or a bare `it` call, which is
132
+ # how `it` parses when the target Ruby version is below 3.4 even though the code
133
+ # may run on 3.4+ where `it` is the implicit block parameter.
134
+ def binding_name(node)
135
+ if node.lvar_type?
136
+ node.children.first
137
+ elsif node.send_type? && !node.receiver && node.method?(:it) && node.arguments.empty?
138
+ :it
139
+ end
140
+ end
141
+
142
+ def binding_scope(occurrence)
143
+ node = occurrence
144
+
145
+ while (parent = node.parent)
146
+ case parent.type
147
+ when :block, :numblock, :itblock
148
+ # Only the body is inside the block's scope; the send node and the arguments of
149
+ # the block node evaluate in the outer scope.
150
+ return parent if parent.body.equal?(node) && block_binds_name?(parent)
151
+ when :def, :defs, :class, :module, :sclass
152
+ return parent
153
+ end
154
+
155
+ node = parent
156
+ end
157
+
158
+ nil
159
+ end
160
+
161
+ def block_binds_name?(block_node)
162
+ name = @receiver_binding_name
163
+
164
+ return true if block_node.argument_list.any? { |argument| argument.name == name }
111
165
 
112
- condition.csend_type? && csend_root_receiver(condition) == node
166
+ name == :it && block_node.arguments.empty?
113
167
  end
114
168
 
115
169
  # Whether control reaches `node` by falling through its left siblings rather than by
@@ -153,7 +153,7 @@ module RuboCop
153
153
  check_expression(case_node.else_branch) if case_node.else_branch
154
154
  end
155
155
 
156
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
156
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
157
157
  def check_void_op(node, &block)
158
158
  node = node.children.first while node&.begin_type?
159
159
  return unless node&.call_type? && OPERATORS.include?(node.method_name)
@@ -168,7 +168,6 @@ module RuboCop
168
168
  autocorrect_void_op(corrector, node)
169
169
  end
170
170
  end
171
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
172
171
 
173
172
  def check_var(node)
174
173
  return unless node.variable? || node.const_type?
@@ -13,6 +13,7 @@ module RuboCop
13
13
  register_cop :AmbiguousOperatorPrecedence, "#{__dir__}/lint/ambiguous_operator_precedence"
14
14
  register_cop :AmbiguousRange, "#{__dir__}/lint/ambiguous_range"
15
15
  register_cop :AmbiguousRegexpLiteral, "#{__dir__}/lint/ambiguous_regexp_literal"
16
+ register_cop :ArgumentMismatch, "#{__dir__}/lint/argument_mismatch"
16
17
  register_cop :ArrayLiteralInRegexp, "#{__dir__}/lint/array_literal_in_regexp"
17
18
  register_cop :AssignmentInCondition, "#{__dir__}/lint/assignment_in_condition"
18
19
  register_cop :BigDecimalNew, "#{__dir__}/lint/big_decimal_new"
@@ -128,6 +129,7 @@ module RuboCop
128
129
  register_cop :ShadowedException, "#{__dir__}/lint/shadowed_exception"
129
130
  register_cop :ShadowingOuterLocalVariable, "#{__dir__}/lint/shadowing_outer_local_variable"
130
131
  register_cop :StructNewOverride, "#{__dir__}/lint/struct_new_override"
132
+ register_cop :SuperArgumentMismatch, "#{__dir__}/lint/super_argument_mismatch"
131
133
  register_cop :SuppressedException, "#{__dir__}/lint/suppressed_exception"
132
134
  register_cop :SuppressedExceptionInNumberConversion, "#{__dir__}/lint/suppressed_exception_in_number_conversion"
133
135
  register_cop :SymbolConversion, "#{__dir__}/lint/symbol_conversion"