rubocop 1.88.2 → 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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +139 -16
  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 +81 -49
  10. data/lib/rubocop/config.rb +76 -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 +39 -3
  14. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  15. data/lib/rubocop/cop/badge.rb +8 -0
  16. data/lib/rubocop/cop/base.rb +40 -6
  17. data/lib/rubocop/cop/bundler.rb +19 -0
  18. data/lib/rubocop/cop/corrector.rb +4 -4
  19. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  20. data/lib/rubocop/cop/gemspec.rb +22 -0
  21. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  22. data/lib/rubocop/cop/generator.rb +4 -0
  23. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  24. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  25. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  26. data/lib/rubocop/cop/layout/class_structure.rb +108 -26
  27. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  28. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  29. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  31. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  32. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  33. data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
  34. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  35. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  36. data/lib/rubocop/cop/layout/line_length.rb +84 -12
  37. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  38. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  40. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  41. data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
  42. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +31 -34
  43. data/lib/rubocop/cop/layout.rb +114 -0
  44. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  45. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  46. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  47. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  48. data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
  49. data/lib/rubocop/cop/lint/constant_reassignment.rb +18 -21
  50. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  51. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
  52. data/lib/rubocop/cop/lint/deprecated_reference.rb +194 -0
  53. data/lib/rubocop/cop/lint/duplicate_methods.rb +300 -22
  54. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  55. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  56. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  57. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  58. data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -6
  59. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +4 -7
  60. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  61. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  62. data/lib/rubocop/cop/lint/name_typo.rb +270 -0
  63. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -4
  64. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
  65. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  66. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  67. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  68. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  69. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  70. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  71. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -2
  72. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  73. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  74. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  75. data/lib/rubocop/cop/lint/to_enum_arguments.rb +38 -3
  76. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  77. data/lib/rubocop/cop/lint/unused_private_method.rb +234 -0
  78. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  80. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  81. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  82. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  83. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  84. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  85. data/lib/rubocop/cop/lint/void.rb +18 -8
  86. data/lib/rubocop/cop/lint.rb +178 -0
  87. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  88. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  89. data/lib/rubocop/cop/metrics.rb +31 -0
  90. data/lib/rubocop/cop/migration/department_name.rb +1 -6
  91. data/lib/rubocop/cop/migration.rb +13 -0
  92. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  93. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  94. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  95. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  96. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  97. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
  98. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  99. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  100. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  101. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
  102. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  103. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  104. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  105. data/lib/rubocop/cop/mixin/statement_modifier.rb +46 -4
  106. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  107. data/lib/rubocop/cop/mixin.rb +82 -77
  108. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  109. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  110. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  111. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  112. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  113. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  114. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  115. data/lib/rubocop/cop/naming.rb +31 -0
  116. data/lib/rubocop/cop/offense.rb +37 -10
  117. data/lib/rubocop/cop/registry.rb +180 -46
  118. data/lib/rubocop/cop/security.rb +19 -0
  119. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  120. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
  121. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  122. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  124. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  125. data/lib/rubocop/cop/style/case_like_if.rb +2 -4
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +79 -20
  127. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  128. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  129. data/lib/rubocop/cop/style/conditional_assignment.rb +23 -2
  130. data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
  131. data/lib/rubocop/cop/style/copyright.rb +5 -1
  132. data/lib/rubocop/cop/style/directive_scope.rb +171 -0
  133. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +76 -12
  134. data/lib/rubocop/cop/style/documentation.rb +47 -0
  135. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  136. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  137. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  138. data/lib/rubocop/cop/style/guard_clause.rb +1 -2
  139. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  140. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  141. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  142. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  143. data/lib/rubocop/cop/style/if_unless_modifier.rb +8 -50
  144. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  145. data/lib/rubocop/cop/style/lambda.rb +4 -0
  146. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  147. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -7
  148. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  149. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
  150. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  151. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  152. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  153. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  154. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  155. data/lib/rubocop/cop/style/negative_array_index.rb +3 -3
  156. data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
  157. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  158. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  159. data/lib/rubocop/cop/style/redundant_assignment.rb +12 -2
  160. data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
  161. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  162. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
  163. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  164. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  165. data/lib/rubocop/cop/style/redundant_format.rb +14 -7
  166. data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
  167. data/lib/rubocop/cop/style/redundant_parentheses.rb +65 -67
  168. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  169. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  170. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  171. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  172. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  173. data/lib/rubocop/cop/style/safe_navigation.rb +11 -2
  174. data/lib/rubocop/cop/style/sample.rb +11 -6
  175. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  176. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  177. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  178. data/lib/rubocop/cop/style/sole_nested_conditional.rb +21 -5
  179. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  180. data/lib/rubocop/cop/style/static_class.rb +21 -0
  181. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  182. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  183. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  184. data/lib/rubocop/cop/style/time_now.rb +41 -0
  185. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  186. data/lib/rubocop/cop/style.rb +312 -0
  187. data/lib/rubocop/cop/team.rb +59 -6
  188. data/lib/rubocop/cop/util.rb +2 -4
  189. data/lib/rubocop/cop/variable_force/variable.rb +19 -8
  190. data/lib/rubocop/cops_documentation_generator.rb +4 -7
  191. data/lib/rubocop/directive_comment.rb +39 -3
  192. data/lib/rubocop/feature_loader.rb +0 -6
  193. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  194. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  195. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  196. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  197. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  198. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  199. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  200. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  201. data/lib/rubocop/lockfile.rb +18 -0
  202. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  203. data/lib/rubocop/lsp/routes.rb +89 -22
  204. data/lib/rubocop/lsp/runtime.rb +4 -0
  205. data/lib/rubocop/lsp/server.rb +4 -0
  206. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  207. data/lib/rubocop/mcp/server.rb +1 -2
  208. data/lib/rubocop/options.rb +10 -6
  209. data/lib/rubocop/path_util.rb +1 -2
  210. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  211. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  212. data/lib/rubocop/project_index_loader.rb +16 -0
  213. data/lib/rubocop/rake_task.rb +1 -2
  214. data/lib/rubocop/result_cache.rb +6 -4
  215. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  216. data/lib/rubocop/rspec/shared_contexts.rb +16 -2
  217. data/lib/rubocop/runner.rb +71 -43
  218. data/lib/rubocop/server/cache.rb +11 -7
  219. data/lib/rubocop/server/cli.rb +2 -4
  220. data/lib/rubocop/server.rb +14 -0
  221. data/lib/rubocop/target_finder.rb +4 -1
  222. data/lib/rubocop/target_ruby.rb +1 -1
  223. data/lib/rubocop/todo_audit.rb +111 -0
  224. data/lib/rubocop/version.rb +3 -5
  225. data/lib/rubocop.rb +20 -626
  226. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  227. metadata +28 -5
@@ -30,21 +30,55 @@ module RuboCop
30
30
  # foo = "1"
31
31
  # # rubocop:enable all
32
32
  # baz
33
+ #
34
+ # # bad
35
+ # foo = 1
36
+ # # rubocop:pop
37
+ #
38
+ # # good
39
+ # # rubocop:push -Style/StringLiterals
40
+ # foo = "1"
41
+ # # rubocop:pop
33
42
  class RedundantCopEnableDirective < Base
34
43
  include RangeHelp
35
44
  include SurroundingSpace
36
45
  extend AutoCorrector
37
46
 
38
47
  MSG = 'Unnecessary enabling of %<cop>s.'
48
+ MSG_ORPHAN_POP = 'Unnecessary `rubocop:pop` without a matching `rubocop:push`.'
39
49
 
40
50
  def on_new_investigation
41
- return if processed_source.blank? || !processed_source.raw_source.include?('enable')
51
+ return if processed_source.blank?
42
52
 
53
+ source = processed_source.raw_source
54
+ check_extra_enables if source.include?('enable')
55
+ check_orphan_pops if source.include?('pop')
56
+ end
57
+
58
+ private
59
+
60
+ def check_extra_enables
43
61
  offenses = processed_source.comment_config.extra_enabled_comments
44
62
  offenses.each { |comment, cop_names| register_offense(comment, cop_names) }
45
63
  end
46
64
 
47
- private
65
+ def check_orphan_pops
66
+ push_depth = 0
67
+ processed_source.comments.each do |comment|
68
+ directive = DirectiveComment.new(comment)
69
+ if directive.push?
70
+ push_depth += 1
71
+ elsif directive.pop?
72
+ push_depth.zero? ? register_orphan_pop(directive) : push_depth -= 1
73
+ end
74
+ end
75
+ end
76
+
77
+ def register_orphan_pop(directive)
78
+ add_offense(directive.range, message: MSG_ORPHAN_POP) do |corrector|
79
+ corrector.remove(range_with_surrounding_space(directive.range, side: :right))
80
+ end
81
+ end
48
82
 
49
83
  def register_offense(comment, cop_names)
50
84
  directive = DirectiveComment.new(comment)
@@ -55,15 +89,21 @@ module RuboCop
55
89
  range_of_offense(comment, name),
56
90
  message: format(MSG, cop: all_or_name(name))
57
91
  ) do |corrector|
58
- if directive.match?(cop_names)
59
- corrector.remove(range_with_surrounding_space(directive.range, side: :right))
60
- else
61
- corrector.remove(range_with_comma(comment, name))
62
- end
92
+ corrector.remove(removal_range(directive, comment, cop_names, name))
63
93
  end
64
94
  end
65
95
  end
66
96
 
97
+ # When every cop on the directive is redundant the whole comment goes, `--` reason
98
+ # included; otherwise just the one cop is spliced out of the list.
99
+ def removal_range(directive, comment, cop_names, name)
100
+ if directive.match?(cop_names)
101
+ range_with_surrounding_space(directive.range_with_reason, side: :right)
102
+ else
103
+ range_with_comma(comment, name)
104
+ end
105
+ end
106
+
67
107
  def range_of_offense(comment, name)
68
108
  start_pos = comment_start(comment) + cop_name_indention(comment, name)
69
109
  range_between(start_pos, start_pos + name.size)
@@ -63,7 +63,7 @@ module RuboCop
63
63
 
64
64
  private
65
65
 
66
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
66
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
67
67
  def redundant_feature?(feature_name)
68
68
  feature_name == 'enumerator' ||
69
69
  (target_ruby_version >= 2.1 && feature_name == 'thread') ||
@@ -73,7 +73,6 @@ module RuboCop
73
73
  (target_ruby_version >= 3.2 && feature_name == 'set') ||
74
74
  (target_ruby_version >= 4.0 && feature_name == 'pathname')
75
75
  end
76
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
77
76
  end
78
77
  end
79
78
  end
@@ -208,7 +208,7 @@ module RuboCop
208
208
  add_offense(range) { |corrector| corrector.replace(range, '.') }
209
209
  end
210
210
 
211
- # rubocop:disable Metrics/AbcSize
211
+ # rubocop:disable-next Metrics/AbcSize
212
212
  def on_or(node)
213
213
  conversion_with_default?(node) do |send_node|
214
214
  range = send_node.loc.dot.begin.join(node.source_range.end)
@@ -221,7 +221,6 @@ module RuboCop
221
221
  end
222
222
  end
223
223
  end
224
- # rubocop:enable Metrics/AbcSize
225
224
 
226
225
  private
227
226
 
@@ -139,7 +139,7 @@ module RuboCop
139
139
  grandparent.array_type? && grandparent.children.size > 1
140
140
  end
141
141
 
142
- # rubocop:disable Metrics/AbcSize
142
+ # rubocop:disable-next Metrics/AbcSize
143
143
  def replacement_range_and_content(node)
144
144
  variable = node.children.first
145
145
  expression = node.source_range
@@ -157,7 +157,6 @@ module RuboCop
157
157
  [node.loc.operator, '']
158
158
  end
159
159
  end
160
- # rubocop:enable Metrics/AbcSize
161
160
 
162
161
  def array_splat?(node)
163
162
  node.children.first.array_type?
@@ -192,7 +192,7 @@ module RuboCop
192
192
  (hash (pair (sym :exception) false))
193
193
  PATTERN
194
194
 
195
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
195
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity
196
196
  def on_send(node)
197
197
  return if node.arguments.any? || hash_or_set_with_block?(node)
198
198
 
@@ -208,7 +208,6 @@ module RuboCop
208
208
  corrector.remove(node.loc.dot.join(node.loc.end || node.loc.selector))
209
209
  end
210
210
  end
211
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
212
211
  alias on_csend on_send
213
212
 
214
213
  private
@@ -33,7 +33,7 @@ module RuboCop
33
33
  MSG_EACH_WITH_INDEX = 'Use `each` instead of `each_with_index`.'
34
34
  MSG_WITH_INDEX = 'Remove redundant `with_index`.'
35
35
 
36
- # rubocop:disable Metrics/AbcSize
36
+ # rubocop:disable-next Metrics/AbcSize
37
37
  def on_block(node)
38
38
  return unless node.receiver
39
39
  return if node.method?(:with_index) && !node.receiver.receiver
@@ -50,7 +50,6 @@ module RuboCop
50
50
  end
51
51
  end
52
52
  end
53
- # rubocop:enable Metrics/AbcSize
54
53
 
55
54
  alias on_numblock on_block
56
55
  alias on_itblock on_block
@@ -9,6 +9,13 @@ module RuboCop
9
9
  # safe navigation operator after a safe navigation operator.
10
10
  # This cop checks for the problem outlined above.
11
11
  #
12
+ # @safety
13
+ # This cop's autocorrection is unsafe because it changes the behavior of
14
+ # the code: `x&.foo.bar` raises `NoMethodError` when `x` or `x&.foo` is `nil`,
15
+ # whereas the corrected `x&.foo&.bar` returns `nil` instead. The autocorrection
16
+ # also assumes that extending safe navigation through the chain is intended,
17
+ # while removing the first `&.` may be what was intended instead.
18
+ #
12
19
  # @example
13
20
  #
14
21
  # # bad
@@ -38,7 +45,7 @@ module RuboCop
38
45
  }
39
46
  PATTERN
40
47
 
41
- # rubocop:disable Metrics/AbcSize
48
+ # rubocop:disable-next Metrics/AbcSize
42
49
  def on_send(node)
43
50
  return unless require_safe_navigation?(node)
44
51
 
@@ -56,7 +63,6 @@ module RuboCop
56
63
  end
57
64
  end
58
65
  end
59
- # rubocop:enable Metrics/AbcSize
60
66
 
61
67
  private
62
68
 
@@ -85,7 +85,7 @@ module RuboCop
85
85
  end
86
86
  end
87
87
 
88
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
88
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
89
89
  def find_consistent_parts(grouped_operands)
90
90
  csend_in_and, csend_in_or, send_in_and, send_in_or = most_left_indices(grouped_operands)
91
91
 
@@ -99,7 +99,6 @@ module RuboCop
99
99
  ['.', csend_in_or]
100
100
  end
101
101
  end
102
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
103
102
 
104
103
  def already_appropriate_call?(operand, dot_op)
105
104
  return true if operand.safe_navigation? && dot_op == '&.'
@@ -126,7 +125,7 @@ module RuboCop
126
125
  end
127
126
  end
128
127
 
129
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
128
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
130
129
  def most_left_indices(grouped_operands)
131
130
  indices = { csend_in_and: nil, csend_in_or: nil, send_in_and: nil, send_in_or: nil }
132
131
 
@@ -139,7 +138,6 @@ module RuboCop
139
138
 
140
139
  indices.values
141
140
  end
142
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
143
141
 
144
142
  def operand_in_and?(node)
145
143
  return true if node.parent.and_type?
@@ -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
 
@@ -71,7 +71,9 @@ module RuboCop
71
71
  send_arg && argument_match?(send_arg, def_arg)
72
72
  end
73
73
 
74
- all_present && !extra_positional_arguments?(arguments, def_node)
74
+ all_present &&
75
+ !extra_positional_arguments?(arguments, def_node) &&
76
+ !extra_keyword_arguments?(arguments, def_node)
75
77
  end
76
78
 
77
79
  # The enumerator re-invokes the method with these arguments, so passing more
@@ -99,7 +101,41 @@ module RuboCop
99
101
  def_node.arguments.select { |arg| arg.type?(:arg, :optarg) }
100
102
  end
101
103
 
102
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
104
+ # The enumerator re-invokes the method with these arguments, so passing
105
+ # unexpected keyword arguments raises `ArgumentError` at runtime.
106
+ def extra_keyword_arguments?(arguments, def_node)
107
+ return false if variadic_keyword_parameters?(def_node)
108
+ return false if expandable_keyword_arguments?(arguments)
109
+
110
+ (passed_keyword_arguments(arguments) - keyword_parameters(def_node)).any?
111
+ end
112
+
113
+ def variadic_keyword_parameters?(def_node)
114
+ def_node.arguments.any? { |arg| arg.type?(:kwrestarg, :forward_arg) }
115
+ end
116
+
117
+ # A call-side keyword splat or argument forwarding can expand to any set of keywords.
118
+ def expandable_keyword_arguments?(arguments)
119
+ arguments.any? do |arg|
120
+ arg.forwarded_args_type? || arg.each_child_node(:kwsplat, :forwarded_kwrestarg).any?
121
+ end
122
+ end
123
+
124
+ def passed_keyword_arguments(arguments)
125
+ arguments.filter_map do |arg|
126
+ next unless arg.hash_type?
127
+
128
+ arg.pairs.filter_map { |pair| pair.key.value if pair.key.sym_type? }
129
+ end.flatten
130
+ end
131
+
132
+ def keyword_parameters(def_node)
133
+ def_node.arguments.filter_map do |arg|
134
+ arg.children[0] if arg.type?(:kwarg, :kwoptarg)
135
+ end
136
+ end
137
+
138
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
103
139
  def argument_match?(send_arg, def_arg)
104
140
  def_arg_name = def_arg.children[0]
105
141
 
@@ -121,7 +157,6 @@ module RuboCop
121
157
  send_arg.forwarded_args_type?
122
158
  end
123
159
  end
124
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
125
160
 
126
161
  def keyword_hash_argument?(send_arg)
127
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