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
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for directive scopes that can be expressed with the tighter
7
+ # next-statement forms: a `disable`/`enable` pair, an
8
+ # `enable`/`disable` pair, or a `push`/`pop` with signed arguments
9
+ # wrapping exactly one statement. A statement-scoped directive cannot
10
+ # drift as the surrounding code changes and needs no closing boundary.
11
+ #
12
+ # @safety
13
+ # The autocorrection is unsafe because the suppression scope shrinks
14
+ # from the whole region to the statement: offenses of the suppressed
15
+ # cops located on the directive lines themselves resurface.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # # rubocop:disable Metrics/AbcSize
20
+ # def foo
21
+ # end
22
+ # # rubocop:enable Metrics/AbcSize
23
+ #
24
+ # # good
25
+ # # rubocop:disable-next Metrics/AbcSize
26
+ # def foo
27
+ # end
28
+ #
29
+ # # bad
30
+ # # rubocop:push -Metrics/AbcSize
31
+ # def foo
32
+ # end
33
+ # # rubocop:pop
34
+ #
35
+ # # good
36
+ # # rubocop:disable-next Metrics/AbcSize
37
+ # def foo
38
+ # end
39
+ #
40
+ # # bad
41
+ # # rubocop:disable Metrics/AbcSize
42
+ # # rubocop:push +Metrics/AbcSize
43
+ # def foo
44
+ # end
45
+ # # rubocop:pop
46
+ # # rubocop:enable Metrics/AbcSize
47
+ #
48
+ # # good
49
+ # # rubocop:disable Metrics/AbcSize
50
+ # # rubocop:enable-next Metrics/AbcSize
51
+ # def foo
52
+ # end
53
+ # # rubocop:enable Metrics/AbcSize
54
+ #
55
+ # # good - the region spans more than one statement
56
+ # # rubocop:disable Metrics/AbcSize
57
+ # def foo
58
+ # end
59
+ #
60
+ # def bar
61
+ # end
62
+ # # rubocop:enable Metrics/AbcSize
63
+ #
64
+ class DirectiveScope < Base
65
+ include RangeHelp
66
+ extend AutoCorrector
67
+
68
+ MSG_PAIR = 'Use `%<mode>s-next` instead of a `%<mode>s`/`enable` pair ' \
69
+ 'around a single statement.'
70
+ MSG_ENABLE_PAIR = 'Use `enable-next` instead of an `enable`/`disable` pair ' \
71
+ 'around a single statement.'
72
+ MSG_PUSH_POP = 'Use `%<replacement>s` instead of `push`/`pop` around a single statement.'
73
+
74
+ def on_new_investigation
75
+ processed_source.comments.each do |comment|
76
+ directive = DirectiveComment.new(comment)
77
+ next unless comment_config.comment_only_line?(directive.line_number)
78
+
79
+ if plain_disable?(directive)
80
+ check_pair(directive)
81
+ elsif signed_push?(directive)
82
+ check_push_pop(directive)
83
+ elsif plain_enable?(directive)
84
+ check_enable_pair(directive)
85
+ end
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def comment_config
92
+ processed_source.comment_config
93
+ end
94
+
95
+ def plain_disable?(directive)
96
+ directive.disabled? && !directive.disable_next?
97
+ end
98
+
99
+ def plain_enable?(directive)
100
+ directive.enabled? && !directive.enable_next? && !directive.all_cops?
101
+ end
102
+
103
+ def signed_push?(directive)
104
+ directive.push? && !directive.signed_args.empty?
105
+ end
106
+
107
+ def check_pair(directive)
108
+ enable = single_statement_closing_directive(directive)
109
+ return unless enable&.enabled?
110
+ return unless enable.raw_cop_names.sort == directive.raw_cop_names.sort
111
+
112
+ message = format(MSG_PAIR, mode: directive.mode)
113
+ add_offense(directive.comment, message: message) do |corrector|
114
+ convert_pair(corrector, directive, enable)
115
+ end
116
+ end
117
+
118
+ def convert_pair(corrector, directive, enable)
119
+ replacement = directive.comment.text.sub(/\b#{directive.mode}\b/,
120
+ "#{directive.mode}-next")
121
+ corrector.replace(directive.comment, replacement)
122
+ remove_line(corrector, enable.comment)
123
+ end
124
+
125
+ def check_push_pop(directive)
126
+ pop_line = balancing_pop_line(directive)
127
+ return unless pop_line && comment_config.comment_only_line?(pop_line)
128
+ return unless wraps_single_statement?(directive, pop_line)
129
+
130
+ pop_comment = processed_source.comment_at_line(pop_line)
131
+ replacement = push_replacement(directive)
132
+ message = format(MSG_PUSH_POP, replacement: replacement_mode(directive))
133
+ add_offense(directive.comment, message: message) do |corrector|
134
+ corrector.replace(directive.comment, replacement)
135
+ remove_line(corrector, pop_comment)
136
+ end
137
+ end
138
+
139
+ # The line of the `pop` balancing this `push`, taking nesting into
140
+ # account, or `nil` when the push is never popped.
141
+ def balancing_pop_line(push_directive)
142
+ depth = 0
143
+ each_directive_after(push_directive) do |directive|
144
+ if directive.push?
145
+ depth += 1
146
+ elsif directive.pop?
147
+ return directive.line_number if depth.zero?
148
+
149
+ depth -= 1
150
+ end
151
+ end
152
+ nil
153
+ end
154
+
155
+ def each_directive_after(reference)
156
+ processed_source.comments.each do |comment|
157
+ directive = DirectiveComment.new(comment)
158
+ next unless directive.cop_names || directive.push? || directive.pop?
159
+
160
+ yield directive if directive.line_number > reference.line_number
161
+ end
162
+ end
163
+
164
+ def replacement_mode(directive)
165
+ ops = directive.signed_args.keys.sort
166
+ if ops == ['-']
167
+ 'disable-next'
168
+ elsif ops == ['+']
169
+ 'enable-next'
170
+ else
171
+ 'next'
172
+ end
173
+ end
174
+
175
+ def push_replacement(directive)
176
+ text = case replacement_mode(directive)
177
+ when 'disable-next'
178
+ "# rubocop:disable-next #{directive.signed_args['-'].join(', ')}"
179
+ when 'enable-next'
180
+ "# rubocop:enable-next #{directive.signed_args['+'].join(', ')}"
181
+ else
182
+ "# rubocop:next #{directive.cops}"
183
+ end
184
+ reason = directive.reason
185
+ reason ? "#{text} -- #{reason}" : text
186
+ end
187
+
188
+ # An `enable`/`disable` pair around one statement inside a disabled
189
+ # region re-enables the cops for just that statement - `enable-next`
190
+ # says the same without the closing boundary. The pair only counts
191
+ # when the `enable` really closed open disables (otherwise the
192
+ # trailing `disable` opens a new region and the conversion would
193
+ # change what is covered).
194
+ def check_enable_pair(directive)
195
+ closing = enable_pair_closing(directive)
196
+ return unless closing
197
+
198
+ add_offense(directive.comment, message: MSG_ENABLE_PAIR) do |corrector|
199
+ corrector.replace(directive.comment,
200
+ directive.comment.text.sub(/\benable\b/, 'enable-next'))
201
+ remove_line(corrector, closing.comment)
202
+ end
203
+ end
204
+
205
+ def enable_pair_closing(directive)
206
+ scope = comment_config.statement_scope_after(directive.line_number)
207
+ return nil unless scope && scope.begin == directive.line_number + 1
208
+
209
+ closing = re_disable_below(directive, scope.end + 1)
210
+ closing if closing && closed_open_disables?(directive, closing)
211
+ end
212
+
213
+ def re_disable_below(directive, line)
214
+ return nil unless comment_config.comment_only_line?(line)
215
+
216
+ comment = processed_source.comment_at_line(line)
217
+ return nil unless comment
218
+
219
+ closing = DirectiveComment.new(comment)
220
+ return nil unless closing.disabled? && !closing.disable_next?
221
+ return nil unless closing.raw_cop_names.sort == directive.raw_cop_names.sort
222
+
223
+ closing
224
+ end
225
+
226
+ # Every cop of the pair must have a range the `enable` closed and a
227
+ # range the trailing `disable` reopened - otherwise the `enable` was
228
+ # not inside a disabled region and the conversion would change what
229
+ # is covered.
230
+ def closed_open_disables?(directive, closing)
231
+ directive.cop_names.all? do |cop|
232
+ ranges = comment_config.cop_disabled_line_ranges[qualified_name(cop)]
233
+ ranges && closed_at?(ranges, directive.line_number) && reopened_by?(ranges, closing)
234
+ end
235
+ end
236
+
237
+ def closed_at?(ranges, line)
238
+ ranges.any? { |range| range.end == line }
239
+ end
240
+
241
+ def reopened_by?(ranges, closing)
242
+ ranges.any? do |range|
243
+ range.respond_to?(:directive) && range.directive.comment.equal?(closing.comment)
244
+ end
245
+ end
246
+
247
+ def qualified_name(cop_name)
248
+ Registry.qualified_cop_name(cop_name.strip, processed_source.file_path,
249
+ correct_namespace: false)
250
+ end
251
+
252
+ # The directive closing this one's scope, when that scope wraps
253
+ # exactly one statement - `nil` otherwise.
254
+ def single_statement_closing_directive(directive)
255
+ closing_line = single_closing_line(directive)
256
+ return nil unless closing_line
257
+
258
+ closing_comment = processed_source.comment_at_line(closing_line)
259
+ return nil unless closing_comment
260
+ return nil unless wraps_single_statement?(directive, closing_line)
261
+
262
+ DirectiveComment.new(closing_comment)
263
+ end
264
+
265
+ # The single line on which every disabled range opened by this
266
+ # directive ends, or `nil` when the ranges disagree or never close.
267
+ # For a `disable` that is the `enable` line; for a `push` it is the
268
+ # line before the `pop`.
269
+ def single_closing_line(directive)
270
+ ranges = ranges_opened_by(directive)
271
+ return nil if ranges.empty?
272
+
273
+ ends = ranges.map(&:end).uniq
274
+ return nil unless ends.size == 1 && ends.first.to_f.finite?
275
+
276
+ ends.first
277
+ end
278
+
279
+ def ranges_opened_by(directive)
280
+ comment_config.cop_disabled_line_ranges.each_value.flat_map do |ranges|
281
+ ranges.select do |range|
282
+ range.respond_to?(:directive) && range.directive.comment.equal?(directive.comment)
283
+ end
284
+ end
285
+ end
286
+
287
+ # The region wraps a single statement only when the directive sits
288
+ # immediately above it and the closing directive immediately below -
289
+ # any other line inside the region (a comment, a blank line) may
290
+ # carry offenses of the suppressed cops that the tighter scope
291
+ # would no longer cover.
292
+ def wraps_single_statement?(directive, closing_line)
293
+ scope = comment_config.statement_scope_after(directive.line_number)
294
+
295
+ !scope.nil? && scope.begin == directive.line_number + 1 && scope.end + 1 == closing_line
296
+ end
297
+
298
+ def remove_line(corrector, comment)
299
+ corrector.remove(range_by_whole_lines(comment.source_range, include_final_newline: true))
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Lint/RedundantCopDisableDirective
3
+ # rubocop:disable Lint/RedundantCopDisableDirective -- the examples below read as real directives
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -12,6 +12,10 @@ module RuboCop
12
12
  # Specific cops can be allowed with the `AllowedCops` configuration. Note that
13
13
  # if this configuration is set, `rubocop:disable all` is still disallowed.
14
14
  #
15
+ # `AllowedCops` and `DisallowedCops` accept cop names and department names
16
+ # alike. Listing a cop does not cover its whole department: a directive
17
+ # disabling `Metrics` is broader than one disabling `Metrics/AbcSize`.
18
+ #
15
19
  # Alternatively, specific cops can be disallowed with the `DisallowedCops`
16
20
  # configuration. When `DisallowedCops` is set, only directives for the listed
17
21
  # cops (and `all`) will be flagged. This is useful when you want
@@ -19,6 +23,18 @@ module RuboCop
19
23
  # allowlisting all other cops. `AllowedCops` and `DisallowedCops` should not
20
24
  # both be set at the same time; if `DisallowedCops` is set, it takes precedence.
21
25
  #
26
+ # `AllowedDirectives` names directive forms - `disable`, `todo`,
27
+ # `disable-next`, `todo-next`, `push`, `next` - and exempts them from the
28
+ # cop entirely. `rubocop:todo` is the usual candidate, since
29
+ # `--disable-uncorrectable` generates those and they record debt rather
30
+ # than a decision someone should be asked to justify.
31
+ #
32
+ # With `AllowWithReason` set to `true`, a disable directive carrying
33
+ # a `--` trailing justification comment is allowed, so a team can require
34
+ # every disable to be documented instead of banning them outright. Enable
35
+ # directives are not checked in this mode, since they end a suppression
36
+ # rather than start one.
37
+ #
22
38
  # This cop cannot be disabled via directive comments when it is explicitly
23
39
  # enabled with `Enabled: true`. This prevents users from bypassing the cop
24
40
  # with `# rubocop:disable Style/DisableCopsWithinSourceCodeDirective`.
@@ -52,16 +68,42 @@ module RuboCop
52
68
  # foo
53
69
  # # rubocop:enable Metrics/AbcSize
54
70
  #
71
+ # @example AllowedDirectives: [todo]
72
+ # # good - the cop does not look at `todo` directives at all
73
+ # x = 0 # rubocop:todo Layout/SpaceAroundOperators
74
+ #
75
+ # # bad
76
+ # x = 0 # rubocop:disable Layout/SpaceAroundOperators
77
+ #
78
+ # @example AllowedCops: [Metrics]
79
+ # # good - every cop the directive disables is in an exempt department
80
+ # # rubocop:disable Metrics/AbcSize
81
+ # def foo
82
+ # end
83
+ # # rubocop:enable Metrics/AbcSize
84
+ #
85
+ # @example AllowWithReason: true
86
+ # # bad
87
+ # x = 0 # rubocop:disable Layout/SpaceAroundOperators
88
+ #
89
+ # # good
90
+ # x = 0 # rubocop:disable Layout/SpaceAroundOperators -- would misalign the table
91
+ #
55
92
  class DisableCopsWithinSourceCodeDirective < Base
56
93
  extend AutoCorrector
57
94
 
58
95
  # rubocop:enable Lint/RedundantCopDisableDirective
59
96
  MSG = 'RuboCop disable/enable directives are not permitted.'
60
97
  MSG_FOR_COPS = 'RuboCop disable/enable directives for %<cops>s are not permitted.'
98
+ MSG_MISSING_REASON = 'RuboCop disable directives without a `--` justification ' \
99
+ 'comment are not permitted.'
61
100
 
62
101
  def on_new_investigation
63
102
  processed_source.comments.each do |comment|
64
- directive_cops = directive_cops(comment)
103
+ directive = DirectiveComment.new(comment)
104
+ next if ignored?(directive)
105
+
106
+ directive_cops = directive_cops(directive)
65
107
  disallowed_cops = compute_disallowed_cops(directive_cops)
66
108
 
67
109
  next unless disallowed_cops.any?
@@ -77,21 +119,15 @@ module RuboCop
77
119
  if directive_cops.include?('all')
78
120
  directive_cops
79
121
  else
80
- directive_cops.uniq.select { |cop| disallowed_cops_config.include?(cop) }
122
+ directive_cops.uniq.select { |cop| listed?(disallowed_cops_config, cop) }
81
123
  end
82
124
  else
83
- directive_cops.reject { |cop| allowed_cops.include?(cop) }
125
+ directive_cops.reject { |cop| cop != 'all' && listed?(allowed_cops, cop) }
84
126
  end
85
127
  end
86
128
 
87
129
  def register_offense(comment, directive_cops, disallowed_cops)
88
- message = if any_cops_allowed? || disallowed_cops_config.any?
89
- format(MSG_FOR_COPS, cops: "`#{disallowed_cops.join('`, `')}`")
90
- else
91
- MSG
92
- end
93
-
94
- add_offense(comment, message: message) do |corrector|
130
+ add_offense(comment, message: offense_message(disallowed_cops)) do |corrector|
95
131
  replacement = ''
96
132
 
97
133
  if directive_cops.length != disallowed_cops.length
@@ -103,15 +139,49 @@ module RuboCop
103
139
  end
104
140
  end
105
141
 
106
- def directive_cops(comment)
107
- match_captures = DirectiveComment.new(comment).match_captures
142
+ def offense_message(disallowed_cops)
143
+ if allow_with_reason?
144
+ MSG_MISSING_REASON
145
+ elsif any_cops_allowed? || disallowed_cops_config.any?
146
+ format(MSG_FOR_COPS, cops: "`#{disallowed_cops.join('`, `')}`")
147
+ else
148
+ MSG
149
+ end
150
+ end
151
+
152
+ def directive_cops(directive)
153
+ match_captures = directive.match_captures
108
154
  match_captures && match_captures[1] ? match_captures[1].split(',').map(&:strip) : []
109
155
  end
110
156
 
157
+ def ignored?(directive)
158
+ return true if exempt_mode?(directive)
159
+
160
+ allow_with_reason? && (directive.reason || directive.enabled?)
161
+ end
162
+
163
+ def exempt_mode?(directive)
164
+ allowed_directives.include?(directive.mode)
165
+ end
166
+
167
+ def allowed_directives
168
+ @allowed_directives ||= Array(cop_config['AllowedDirectives'])
169
+ end
170
+
171
+ def allow_with_reason?
172
+ cop_config['AllowWithReason']
173
+ end
174
+
111
175
  def allowed_cops
112
176
  @allowed_cops ||= Array(cop_config['AllowedCops']).to_set
113
177
  end
114
178
 
179
+ # A name matches either outright or through its department, so
180
+ # `Metrics` covers `Metrics/AbcSize` but not the other way round.
181
+ def listed?(names, cop)
182
+ names.include?(cop) || names.include?(cop.split('/').first)
183
+ end
184
+
115
185
  def any_cops_allowed?
116
186
  allowed_cops.any?
117
187
  end
@@ -119,6 +119,7 @@ module RuboCop
119
119
 
120
120
  def on_def(node)
121
121
  return if node.method?(:initialize)
122
+ return if allowed_methods.include?(node.method_name)
122
123
 
123
124
  parent = node.parent
124
125
  modifier_node?(parent) ? check(parent) : check(node)
@@ -130,7 +131,6 @@ module RuboCop
130
131
  def check(node)
131
132
  return if non_public?(node) && !require_for_non_public_methods?
132
133
  return if documentation_comment?(node)
133
- return if method_allowed?(node)
134
134
 
135
135
  add_offense(node)
136
136
  end
@@ -139,14 +139,6 @@ module RuboCop
139
139
  cop_config['RequireForNonPublicMethods']
140
140
  end
141
141
 
142
- def method_allowed?(node)
143
- # For a modifier form like `module_function def foo; end`, `node` is the
144
- # `module_function`/`ruby2_keywords` send; the real method name is on its
145
- # `def`/`defs` argument, not the modifier itself.
146
- method_name = node.send_type? ? node.first_argument.method_name : node.method_name
147
- allowed_methods.include?(method_name)
148
- end
149
-
150
142
  def allowed_methods
151
143
  @allowed_methods ||= cop_config.fetch('AllowedMethods', []).map(&:to_sym)
152
144
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  MSG = 'Use `Integer#times` for a simple loop which iterates a fixed number of times.'
28
28
 
29
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
29
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- only matches blocks that take no parameters
30
30
  return unless offending?(node)
31
31
 
32
32
  send_node = node.send_node
@@ -28,7 +28,7 @@ module RuboCop
28
28
 
29
29
  MSG = 'Omit pipes for the empty block parameters.'
30
30
 
31
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
31
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- the offense is an empty `||`
32
32
  send_node = node.send_node
33
33
  check(node) unless send_node.send_type? && send_node.lambda_literal?
34
34
  end
@@ -42,7 +42,7 @@ module RuboCop
42
42
  MSG = 'Do not use empty `case` condition, instead use an `if` expression.'
43
43
  NOT_SUPPORTED_PARENT_TYPES = %i[return break next send csend yield super].freeze
44
44
 
45
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
45
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
46
46
  def on_case(case_node)
47
47
  if case_node.condition || NOT_SUPPORTED_PARENT_TYPES.include?(case_node.parent&.type)
48
48
  return
@@ -56,7 +56,6 @@ module RuboCop
56
56
 
57
57
  add_offense(case_node.loc.keyword) { |corrector| autocorrect(corrector, case_node) }
58
58
  end
59
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
60
59
 
61
60
  private
62
61
 
@@ -23,7 +23,7 @@ module RuboCop
23
23
 
24
24
  MSG = 'Omit parentheses for the empty lambda parameters.'
25
25
 
26
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
26
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler -- the offense is an empty `()`
27
27
  send_node = node.send_node
28
28
  return unless send_node.send_type?
29
29
 
@@ -53,11 +53,11 @@ module RuboCop
53
53
 
54
54
  def on_def(node)
55
55
  return if node.body || processed_source.contains_comment?(node.source_range)
56
- return if correct_style?(node)
56
+ return if correct_style?(node) || compact_style_disallowed?
57
57
 
58
58
  add_offense(node) do |corrector|
59
59
  correction = corrected(node)
60
- next if compact_style? && max_line_length && correction.size > max_line_length
60
+ next if correction_exceeds_line_length?(correction)
61
61
 
62
62
  corrector.replace(node, correction)
63
63
  end
@@ -74,6 +74,12 @@ module RuboCop
74
74
  (compact_style? && compact?(node)) || (expanded_style? && expanded?(node))
75
75
  end
76
76
 
77
+ def compact_style_disallowed?
78
+ return false unless compact_style?
79
+
80
+ config.for_enabled_cop('Style/SingleLineMethods')['AllowIfMethodIsEmpty'] == false
81
+ end
82
+
77
83
  def corrected(node)
78
84
  scope = node.receiver ? "#{node.receiver.source}." : ''
79
85
  arguments = if node.arguments?
@@ -86,6 +92,10 @@ module RuboCop
86
92
  ["def #{signature}", 'end'].join(joint(node))
87
93
  end
88
94
 
95
+ def correction_exceeds_line_length?(correction)
96
+ compact_style? && max_line_length && correction.size > max_line_length
97
+ end
98
+
89
99
  def joint(node)
90
100
  indent = ' ' * node.loc.column
91
101
 
@@ -166,6 +166,8 @@ module RuboCop
166
166
  return if node.single_line? || style == :allow_always
167
167
 
168
168
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
169
+ next if endless_parent?(node)
170
+
169
171
  correct_to_multiline(corrector, node)
170
172
  end
171
173
  end
@@ -175,11 +177,11 @@ module RuboCop
175
177
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
176
178
  correct_to_multiline(corrector, node)
177
179
  end
178
- elsif !node.endless? && can_be_made_endless?(node) && node.body.single_line?
180
+ elsif !node.endless? && can_be_made_endless?(node) && single_line_when_made_endless?(node)
179
181
  return if too_long_when_made_endless?(node)
180
182
 
181
183
  add_offense(node, message: MSG_REQUIRE_SINGLE) do |corrector|
182
- corrector.replace(node, endless_replacement(node))
184
+ correct_to_endless(corrector, node)
183
185
  end
184
186
  end
185
187
  end
@@ -189,14 +191,22 @@ module RuboCop
189
191
  return if too_long_when_made_endless?(node)
190
192
 
191
193
  add_offense(node, message: MSG_REQUIRE_ALWAYS) do |corrector|
192
- corrector.replace(node, endless_replacement(node))
194
+ correct_to_endless(corrector, node)
193
195
  end
194
196
  end
195
197
 
196
198
  def handle_disallow_style(node)
197
199
  return unless node.endless?
198
200
 
199
- add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
201
+ add_offense(node) do |corrector|
202
+ next if endless_parent?(node)
203
+
204
+ correct_to_multiline(corrector, node)
205
+ end
206
+ end
207
+
208
+ def endless_parent?(node)
209
+ node.parent&.any_def_type? && node.parent.endless?
200
210
  end
201
211
 
202
212
  def use_heredoc?(node)
@@ -206,6 +216,17 @@ module RuboCop
206
216
  body.each_descendant(:str).any?(&:heredoc?)
207
217
  end
208
218
 
219
+ def correct_to_endless(corrector, node)
220
+ corrector.replace(signature_to_body_range(node), ' = ')
221
+ corrector.remove(node.body.source_range.end.join(node.loc.end.end))
222
+ end
223
+
224
+ def signature_to_body_range(node)
225
+ signature_end = node.arguments.any? ? node.arguments.source_range.end : node.loc.name.end
226
+
227
+ signature_end.join(node.body.source_range.begin)
228
+ end
229
+
209
230
  def endless_replacement(node)
210
231
  <<~RUBY.strip
211
232
  def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
@@ -221,19 +242,17 @@ module RuboCop
221
242
  end
222
243
 
223
244
  def can_be_made_endless?(node)
224
- node.body && !node.body.begin_type? && !node.body.kwbegin_type?
245
+ node.body && !node.body.type?(:begin, :kwbegin, :rescue, :ensure)
246
+ end
247
+
248
+ def single_line_when_made_endless?(node)
249
+ !endless_replacement(node).include?("\n")
225
250
  end
226
251
 
227
252
  def too_long_when_made_endless?(node)
228
253
  return false unless config.cop_enabled?('Layout/LineLength')
229
254
 
230
- offset = modifier_offset(node)
231
-
232
- endless_replacement(node).length + offset > max_line_length
233
- end
234
-
235
- def modifier_offset(node)
236
- same_line?(node.parent, node) ? node.loc.column - node.parent.loc.column : 0
255
+ endless_replacement(node).length + node.loc.column > max_line_length
237
256
  end
238
257
  end
239
258
  end
@@ -54,7 +54,7 @@ module RuboCop
54
54
  @contain_dev_null_string_in_file = ast.each_descendant(:str).any? do |str|
55
55
  content = str.str_content
56
56
 
57
- valid_string?(content) && content.downcase == '/dev/null' # rubocop:disable Style/FileNull
57
+ valid_string?(content) && content.downcase == '/dev/null' # rubocop:disable Style/FileNull -- the cop needs the literal it is matching
58
58
  end
59
59
  end
60
60
 
@@ -62,7 +62,7 @@ module RuboCop
62
62
  value = node.value
63
63
  return unless valid_string?(value)
64
64
  return if acceptable?(node)
65
- return if value.downcase == 'nul' && !@contain_dev_null_string_in_file # rubocop:disable Style/FileNull
65
+ return if value.downcase == 'nul' && !@contain_dev_null_string_in_file # rubocop:disable Style/FileNull -- the cop needs the literal it is matching
66
66
  return unless REGEXP.match?(value)
67
67
 
68
68
  add_offense(node, message: format(MSG, source: value)) do |corrector|