rubocop 1.89.0 → 1.91.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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +309 -14
  3. data/config/obsoletion.yml +9 -0
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop/cached_data.rb +7 -2
  6. data/lib/rubocop/changed_files.rb +112 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +52 -1
  8. data/lib/rubocop/cli.rb +11 -7
  9. data/lib/rubocop/comment_config/directive_range.rb +34 -0
  10. data/lib/rubocop/comment_config/disable_next.rb +143 -0
  11. data/lib/rubocop/comment_config/push_pop.rb +55 -0
  12. data/lib/rubocop/comment_config.rb +50 -64
  13. data/lib/rubocop/config.rb +33 -5
  14. data/lib/rubocop/config_loader.rb +13 -6
  15. data/lib/rubocop/config_loader_resolver.rb +50 -9
  16. data/lib/rubocop/config_obsoletion/cop_rule.rb +2 -1
  17. data/lib/rubocop/config_obsoletion/removed_cop.rb +8 -0
  18. data/lib/rubocop/config_obsoletion.rb +1 -1
  19. data/lib/rubocop/config_validator.rb +19 -5
  20. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  21. data/lib/rubocop/cop/badge.rb +1 -1
  22. data/lib/rubocop/cop/base.rb +49 -5
  23. data/lib/rubocop/cop/corrector.rb +3 -3
  24. data/lib/rubocop/cop/correctors.rb +2 -2
  25. data/lib/rubocop/cop/gemspec/require_mfa.rb +1 -1
  26. data/lib/rubocop/cop/generator.rb +1 -1
  27. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  28. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  29. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -0
  31. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  32. data/lib/rubocop/cop/layout/class_structure.rb +102 -26
  33. data/lib/rubocop/cop/layout/comment_indentation.rb +8 -5
  34. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  35. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  36. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  37. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  38. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  39. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  40. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  41. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +17 -4
  42. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +11 -0
  43. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -5
  44. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  45. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  46. data/lib/rubocop/cop/layout/line_length.rb +20 -3
  47. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  48. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
  49. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  50. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +14 -3
  51. data/lib/rubocop/cop/layout/space_around_operators.rb +12 -1
  52. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
  53. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  54. data/lib/rubocop/cop/lint/argument_mismatch.rb +99 -0
  55. data/lib/rubocop/cop/lint/assignment_in_condition.rb +2 -0
  56. data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
  57. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +165 -15
  58. data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
  59. data/lib/rubocop/cop/lint/duplicate_methods.rb +113 -15
  60. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  61. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  62. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  63. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  64. data/lib/rubocop/cop/lint/literal_as_condition.rb +11 -8
  65. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
  66. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +185 -0
  67. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  68. data/lib/rubocop/cop/lint/name_typo.rb +53 -6
  69. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  70. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
  71. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
  72. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +17 -1
  73. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +122 -29
  74. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  75. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  76. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  77. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  78. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  79. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  80. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
  81. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  82. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  83. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  84. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
  85. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  86. data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
  87. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  88. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  89. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  90. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  91. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  92. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  93. data/lib/rubocop/cop/lint/void.rb +1 -2
  94. data/lib/rubocop/cop/lint.rb +3 -0
  95. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  96. data/lib/rubocop/cop/migration/department_name.rb +1 -2
  97. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  98. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -1
  99. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  100. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  101. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +11 -1
  102. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +7 -4
  103. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  104. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  105. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  106. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  107. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +62 -6
  108. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  109. data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
  110. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  111. data/lib/rubocop/cop/mixin.rb +6 -2
  112. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  113. data/lib/rubocop/cop/naming/block_forwarding.rb +4 -1
  114. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  115. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  116. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  117. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  118. data/lib/rubocop/cop/offense.rb +27 -4
  119. data/lib/rubocop/cop/registry.rb +43 -11
  120. data/lib/rubocop/cop/severity.rb +12 -0
  121. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -2
  122. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  123. data/lib/rubocop/cop/style/arguments_forwarding.rb +5 -8
  124. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  125. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  126. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  127. data/lib/rubocop/cop/style/case_like_if.rb +5 -7
  128. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
  129. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  130. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  131. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
  132. data/lib/rubocop/cop/style/constant_visibility.rb +6 -3
  133. data/lib/rubocop/cop/style/copyright.rb +1 -2
  134. data/lib/rubocop/cop/style/data_inheritance.rb +14 -0
  135. data/lib/rubocop/cop/style/directive_scope.rb +304 -0
  136. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +83 -13
  137. data/lib/rubocop/cop/style/documentation_method.rb +1 -9
  138. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  139. data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
  140. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  141. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
  142. data/lib/rubocop/cop/style/empty_method.rb +12 -2
  143. data/lib/rubocop/cop/style/endless_method.rb +31 -12
  144. data/lib/rubocop/cop/style/file_null.rb +2 -2
  145. data/lib/rubocop/cop/style/float_division.rb +10 -16
  146. data/lib/rubocop/cop/style/for.rb +5 -0
  147. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  148. data/lib/rubocop/cop/style/guard_clause.rb +5 -2
  149. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  150. data/lib/rubocop/cop/style/hash_lookup_method.rb +3 -0
  151. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  152. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  153. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  154. data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
  155. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  156. data/lib/rubocop/cop/style/magic_comment_format.rb +1 -1
  157. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  159. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -1
  160. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  161. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  162. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  163. data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
  164. data/lib/rubocop/cop/style/nested_modifier.rb +9 -4
  165. data/lib/rubocop/cop/style/nil_comparison.rb +36 -9
  166. data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
  167. data/lib/rubocop/cop/style/non_nil_check.rb +18 -2
  168. data/lib/rubocop/cop/style/not.rb +3 -1
  169. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +11 -1
  170. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  171. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  172. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  173. data/lib/rubocop/cop/style/parentheses_around_condition.rb +6 -1
  174. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
  175. data/lib/rubocop/cop/style/redundant_condition.rb +6 -8
  176. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +11 -3
  177. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  178. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
  179. data/lib/rubocop/cop/style/redundant_format.rb +2 -4
  180. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -5
  181. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  182. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +6 -5
  183. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +6 -6
  184. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  185. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  186. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  187. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  188. data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
  189. data/lib/rubocop/cop/style/sample.rb +11 -6
  190. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  191. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  192. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  193. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  194. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
  195. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  196. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  197. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  198. data/lib/rubocop/cop/style/struct_inheritance.rb +14 -0
  199. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  200. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  201. data/lib/rubocop/cop/style/time_now.rb +41 -0
  202. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  203. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  204. data/lib/rubocop/cop/style.rb +2 -1
  205. data/lib/rubocop/cop/team.rb +11 -3
  206. data/lib/rubocop/cop/util.rb +3 -5
  207. data/lib/rubocop/cop/variable_force/variable.rb +1 -2
  208. data/lib/rubocop/cops_documentation_generator.rb +26 -8
  209. data/lib/rubocop/directive_comment.rb +73 -10
  210. data/lib/rubocop/formatter/disabled_config_formatter.rb +19 -1
  211. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  212. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  213. data/lib/rubocop/formatter/github_actions_formatter.rb +6 -5
  214. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  215. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  216. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  217. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  218. data/lib/rubocop/formatter/sarif_formatter.rb +181 -0
  219. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  220. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  221. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  222. data/lib/rubocop/formatter.rb +1 -0
  223. data/lib/rubocop/lockfile.rb +18 -0
  224. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  225. data/lib/rubocop/lsp/routes.rb +48 -19
  226. data/lib/rubocop/magic_comment.rb +27 -0
  227. data/lib/rubocop/mcp/server.rb +1 -2
  228. data/lib/rubocop/options.rb +69 -7
  229. data/lib/rubocop/path_util.rb +2 -3
  230. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  231. data/lib/rubocop/plugin/loader.rb +1 -1
  232. data/lib/rubocop/rake_task.rb +1 -2
  233. data/lib/rubocop/remote_config.rb +4 -4
  234. data/lib/rubocop/result_cache.rb +16 -5
  235. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  236. data/lib/rubocop/rspec/shared_contexts.rb +4 -2
  237. data/lib/rubocop/runner.rb +90 -44
  238. data/lib/rubocop/server/cache.rb +1 -2
  239. data/lib/rubocop/server/cli.rb +2 -4
  240. data/lib/rubocop/server.rb +14 -0
  241. data/lib/rubocop/target_finder.rb +4 -1
  242. data/lib/rubocop/todo_audit.rb +111 -0
  243. data/lib/rubocop/unified_diff.rb +257 -0
  244. data/lib/rubocop/version.rb +3 -5
  245. data/lib/rubocop.rb +7 -1
  246. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  247. metadata +18 -6
  248. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +0 -46
@@ -2,7 +2,7 @@
2
2
 
3
3
  # The Lint/RedundantCopDisableDirective cop needs to be disabled so as
4
4
  # to be able to provide a (bad) example of a redundant disable.
5
- # rubocop:disable Lint/RedundantCopDisableDirective
5
+ # rubocop:disable-next Lint/RedundantCopDisableDirective -- the examples below read as real directives
6
6
  module RuboCop
7
7
  module Cop
8
8
  module Lint
@@ -104,17 +104,35 @@ module RuboCop
104
104
  end
105
105
 
106
106
  def each_redundant_disable(&block)
107
+ each_detached_next_directive(&block)
108
+
107
109
  cop_disabled_line_ranges.each do |cop, line_ranges|
110
+ # A pending cop that is not enabled in this run produces no
111
+ # offenses, so its directives cannot be judged - they typically
112
+ # prepare the code for the moment the cop gets enabled.
113
+ next if pending_cop_not_run?(cop)
114
+
108
115
  each_already_disabled(cop, line_ranges, &block)
109
116
  each_line_range(cop, line_ranges, &block)
110
117
  end
111
118
  end
112
119
 
120
+ def pending_cop_not_run?(cop)
121
+ # `Config#for_cop` corrects wrongly-namespaced names, but a name that
122
+ # is not registered as written never suppresses anything, so it is
123
+ # reported as unknown rather than exempted via the corrected cop.
124
+ return false unless all_cop_names.include?(cop)
125
+
126
+ cop_cfg = config.for_cop(cop)
127
+ cop_cfg['Enabled'] == 'pending' &&
128
+ !processed_source.registry.enabled_pending_cop?(cop_cfg, config, cop)
129
+ end
130
+
113
131
  def each_line_range(cop, line_ranges)
114
132
  line_ranges.each_with_index do |line_range, line_range_index|
115
133
  next if should_skip_line_range?(cop, line_range)
116
134
 
117
- comment = processed_source.comment_at_line(line_range.begin)
135
+ comment = directive_comment(line_range)
118
136
  next if skip_directive?(comment)
119
137
 
120
138
  next_range = line_ranges[line_range_index + 1]
@@ -127,9 +145,39 @@ module RuboCop
127
145
  ignore_offense?(line_range) || expected_final_disable?(cop, line_range)
128
146
  end
129
147
 
148
+ # A range opened by a `disable-next` directive starts at the statement,
149
+ # not at the directive comment above it, so ask the range itself.
150
+ def directive_comment(line_range)
151
+ if line_range.respond_to?(:directive)
152
+ line_range.directive.comment
153
+ else
154
+ processed_source.comment_at_line(line_range.begin)
155
+ end
156
+ end
157
+
158
+ # A next-statement directive with no statement attached (or misplaced
159
+ # at the end of a code line) affects nothing, so it is redundant by
160
+ # definition.
161
+ def each_detached_next_directive
162
+ processed_source.comment_config.detached_next_directives.each do |directive|
163
+ names = if directive.next?
164
+ directive.signed_args.values.flatten
165
+ else
166
+ directive.raw_cop_names
167
+ end
168
+ names.each { |cop| yield directive.comment, cop }
169
+ end
170
+ end
171
+
130
172
  def skip_directive?(comment)
173
+ # A cop disabled in the configuration is represented by a synthetic directive that
174
+ # has no comment in the source, so there is nothing to report or to remove.
175
+ # Its range starts at `CONFIG_DISABLED_LINE_RANGE_MIN` and runs to the end of the file,
176
+ # but a `pop` splits it, and the pieces in between are bounded on both sides.
177
+ return true if comment.is_a?(CommentConfig::ConfigDisabledCopDirectiveComment)
178
+
131
179
  directive = DirectiveComment.new(comment)
132
- directive.push? || directive.pop?
180
+ directive.push? || directive.pop? || directive.next?
133
181
  end
134
182
 
135
183
  def find_redundant_directive(cop, comment, line_range, next_range)
@@ -142,7 +190,7 @@ module RuboCop
142
190
  end
143
191
  end
144
192
 
145
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
193
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
146
194
  def each_already_disabled(cop, line_ranges)
147
195
  line_ranges.each_cons(2) do |previous_range, range|
148
196
  next if ignore_offense?(range)
@@ -152,13 +200,13 @@ module RuboCop
152
200
  # whether there are offenses or not.
153
201
  next unless followed_ranges?(previous_range, range)
154
202
 
155
- comment = processed_source.comment_at_line(range.begin)
203
+ comment = directive_comment(range)
156
204
 
157
- next unless comment
158
- # Comments disabling all cops don't count since it's reasonable
159
- # to disable a few select cops first and then all cops further
160
- # down in the code.
161
- next if all_disabled?(comment)
205
+ # `push`/`next` signed arguments are not analyzed for redundancy
206
+ # (yet), and comments disabling all cops don't count since it's
207
+ # reasonable to disable a few select cops first and then all cops
208
+ # further down in the code.
209
+ next if comment.nil? || skip_directive?(comment) || all_disabled?(comment)
162
210
 
163
211
  redundant =
164
212
  if department_disabled?(cop, comment)
@@ -170,7 +218,6 @@ module RuboCop
170
218
  yield comment, redundant if redundant
171
219
  end
172
220
  end
173
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
174
221
 
175
222
  def find_redundant_cop(cop, range)
176
223
  cop_offenses = offenses_to_check.select { |offense| offense.cop_name == cop }
@@ -198,7 +245,11 @@ module RuboCop
198
245
  end
199
246
 
200
247
  def range_with_offense?(range, offenses = offenses_to_check)
201
- offenses.none? { |offense| range.cover?(offense.line) }
248
+ # An offense is covered when the disabled range overlaps any line of
249
+ # its span, mirroring how offenses are suppressed.
250
+ offenses.none? do |offense|
251
+ offense.line <= range.end && offense.location.last_line >= range.begin
252
+ end
202
253
  end
203
254
 
204
255
  def all_disabled?(comment)
@@ -241,17 +292,33 @@ module RuboCop
241
292
  end
242
293
 
243
294
  def add_offense_for_entire_comment(comment, cops)
244
- location = DirectiveComment.new(comment).range
295
+ directive = DirectiveComment.new(comment)
296
+ location = directive.range
245
297
  cop_names = cops.sort.map { |c| describe(c) }.join(', ')
246
298
 
247
- add_offense(location, message: message(cop_names)) do |corrector|
248
- range = comment_range_with_surrounding_space(location, comment.source_range)
299
+ # An unknown cop may just not be loaded in this run (e.g. a custom
300
+ # cop whose configuration failed to load) - removing its directive
301
+ # would destroy something that cannot be restored, so only report.
302
+ # A misplaced EOL `disable-next` should be moved, not deleted.
303
+ message = message(cop_names, enabling: directive.enable_next?)
304
+ if any_unknown_cop?(cops) || misplaced_next_directive?(comment)
305
+ return add_offense(location, message: message)
306
+ end
249
307
 
250
- if leave_free_comment?(comment, range)
251
- corrector.replace(range, ' # ')
252
- else
253
- corrector.remove(range)
254
- end
308
+ add_offense(location, message: message) do |corrector|
309
+ remove_entire_comment(corrector, comment)
310
+ end
311
+ end
312
+
313
+ def remove_entire_comment(corrector, comment)
314
+ # Take the `--` reason with the directive; `leave_free_comment?` keeps anything else.
315
+ directive_range = DirectiveComment.new(comment).range_with_reason
316
+ range = comment_range_with_surrounding_space(directive_range, comment.source_range)
317
+
318
+ if leave_free_comment?(comment, range)
319
+ corrector.replace(range, ' # ')
320
+ else
321
+ corrector.remove(range)
255
322
  end
256
323
  end
257
324
 
@@ -261,14 +328,35 @@ module RuboCop
261
328
  ranges = cop_ranges.map { |_, r| r }
262
329
 
263
330
  cop_ranges.each do |cop, range|
264
- cop_name = describe(cop)
265
- add_offense(range, message: message(cop_name)) do |corrector|
266
- range = directive_range_in_list(range, ranges)
267
- corrector.remove(range)
268
- end
331
+ add_offense_for_cop_in_list(cop, range, ranges)
332
+ end
333
+ end
334
+
335
+ def add_offense_for_cop_in_list(cop, range, ranges)
336
+ cop_name = describe(cop)
337
+ return add_offense(range, message: message(cop_name)) if unknown_cop?(cop)
338
+
339
+ add_offense(range, message: message(cop_name)) do |corrector|
340
+ corrector.remove(directive_range_in_list(range, ranges))
269
341
  end
270
342
  end
271
343
 
344
+ def any_unknown_cop?(cops)
345
+ cops.any? { |cop| unknown_cop?(cop) }
346
+ end
347
+
348
+ def misplaced_next_directive?(comment)
349
+ directive = DirectiveComment.new(comment)
350
+ (directive.disable_next? || directive.next? || directive.enable_next?) &&
351
+ !processed_source.comment_config.comment_only_line?(directive.line_number)
352
+ end
353
+
354
+ def unknown_cop?(cop)
355
+ return false if cop == 'all' || department_marker?(cop)
356
+
357
+ !all_cop_names.include?(cop)
358
+ end
359
+
272
360
  def leave_free_comment?(comment, range)
273
361
  free_comment = comment.text.gsub(range.source.strip, '')
274
362
 
@@ -303,7 +391,13 @@ module RuboCop
303
391
 
304
392
  SIMILAR_COP_NAMES_CACHE = Hash.new do |hash, cop_name|
305
393
  hash[:all_cop_names] = Registry.global.names unless hash.key?(:all_cop_names)
306
- hash[cop_name] = NameSimilarity.find_similar_name(cop_name, hash[:all_cop_names])
394
+ # A registered cop with the same base name is a better suggestion
395
+ # than anything spelling similarity can find - the most common
396
+ # mistake is qualifying a cop with the wrong department.
397
+ basename = "/#{cop_name.split('/').last}"
398
+ same_basename = hash[:all_cop_names].find { |name| name.end_with?(basename) }
399
+ hash[cop_name] =
400
+ same_basename || NameSimilarity.find_similar_name(cop_name, hash[:all_cop_names])
307
401
  end
308
402
  private_constant :SIMILAR_COP_NAMES_CACHE
309
403
 
@@ -316,8 +410,8 @@ module RuboCop
316
410
  similar ? "`#{cop}` (did you mean `#{similar}`?)" : "`#{cop}` (unknown cop)"
317
411
  end
318
412
 
319
- def message(cop_names)
320
- "Unnecessary disabling of #{cop_names}."
413
+ def message(cop_names, enabling: false)
414
+ "Unnecessary #{enabling ? 'enabling' : 'disabling'} of #{cop_names}."
321
415
  end
322
416
 
323
417
  def all_cop_names
@@ -344,4 +438,3 @@ module RuboCop
344
438
  end
345
439
  end
346
440
  end
347
- # rubocop:enable Lint/RedundantCopDisableDirective
@@ -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
@@ -45,7 +45,7 @@ module RuboCop
45
45
  }
46
46
  PATTERN
47
47
 
48
- # rubocop:disable Metrics/AbcSize
48
+ # rubocop:disable-next Metrics/AbcSize
49
49
  def on_send(node)
50
50
  return unless require_safe_navigation?(node)
51
51
 
@@ -63,7 +63,6 @@ module RuboCop
63
63
  end
64
64
  end
65
65
  end
66
- # rubocop:enable Metrics/AbcSize
67
66
 
68
67
  private
69
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