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
@@ -24,7 +24,7 @@ module RuboCop
24
24
  MSG = 'Literal interpolation detected.'
25
25
  COMPOSITE = %i[array hash pair irange erange].freeze
26
26
 
27
- # rubocop:disable Metrics/AbcSize
27
+ # rubocop:disable-next Metrics/AbcSize
28
28
  def on_interpolation(begin_node)
29
29
  final_node = begin_node.children.last
30
30
  return unless offending?(final_node)
@@ -50,7 +50,6 @@ module RuboCop
50
50
  corrector.replace(final_node.parent, replacement)
51
51
  end
52
52
  end
53
- # rubocop:enable Metrics/AbcSize
54
53
 
55
54
  private
56
55
 
@@ -74,7 +73,7 @@ module RuboCop
74
73
  node.array_type? && grandparent.regexp_type?
75
74
  end
76
75
 
77
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
76
+ # rubocop:disable-next Metrics/MethodLength, Metrics/CyclomaticComplexity
78
77
  def autocorrected_value(node)
79
78
  case node.type
80
79
  when :int
@@ -95,7 +94,6 @@ module RuboCop
95
94
  node.source.gsub('"', '\"')
96
95
  end
97
96
  end
98
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
99
97
 
100
98
  def handle_special_regexp_chars(begin_node, value)
101
99
  parent_node = begin_node.parent
@@ -155,7 +153,7 @@ module RuboCop
155
153
  "{#{hash_string}}"
156
154
  end
157
155
 
158
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
156
+ # rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
159
157
  def autocorrected_value_in_hash(node)
160
158
  case node.type
161
159
  when :int
@@ -174,7 +172,6 @@ module RuboCop
174
172
  node.source.gsub('"', '\"')
175
173
  end
176
174
  end
177
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
178
175
 
179
176
  # Does node print its own source when converted to a string?
180
177
  def prints_as_self?(node)
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for magic comments placed where Ruby silently ignores them.
7
+ #
8
+ # The `encoding` magic comment is only honored on the first line of a file,
9
+ # or on the second line when the first line is a shebang. Anywhere else it
10
+ # is silently ignored - even directly below another comment or a blank line.
11
+ #
12
+ # Other magic comments (such as `frozen_string_literal`) are honored
13
+ # anywhere before the first token of code, but are ignored after any code,
14
+ # with a warning emitted only when running Ruby with `-w`.
15
+ #
16
+ # A magic comment misplaced ahead of a shebang also renders the shebang
17
+ # ineffective, since a shebang is only recognized on the first line.
18
+ #
19
+ # NOTE: An `encoding` comment that is only preceded by other magic
20
+ # comments is not flagged by this cop; that case is handled by
21
+ # `Lint/OrderedMagicComments`. `shareable_constant_value` is never
22
+ # flagged, as Ruby intentionally allows it mid-file with block scoping.
23
+ #
24
+ # @safety
25
+ # This cop's autocorrection is unsafe because moving the magic comment
26
+ # to its effective position activates it, which changes runtime
27
+ # behavior (e.g. the source encoding or string mutability).
28
+ #
29
+ # @example
30
+ # # bad
31
+ # # Documentation comment
32
+ # # encoding: ascii-8bit
33
+ # puts 'hello'
34
+ #
35
+ # # good
36
+ # # encoding: ascii-8bit
37
+ # # Documentation comment
38
+ # puts 'hello'
39
+ #
40
+ # # bad
41
+ # require 'foo'
42
+ # # frozen_string_literal: true
43
+ #
44
+ # # good
45
+ # # frozen_string_literal: true
46
+ # require 'foo'
47
+ #
48
+ # # bad
49
+ # # frozen_string_literal: true
50
+ # #!/usr/bin/env ruby
51
+ # puts 'hello'
52
+ #
53
+ # # good
54
+ # #!/usr/bin/env ruby
55
+ # # frozen_string_literal: true
56
+ # puts 'hello'
57
+ class MisplacedMagicComment < Base
58
+ include RangeHelp
59
+ extend AutoCorrector
60
+
61
+ MSG_ENCODING = 'The `encoding` magic comment is ignored unless placed on the first ' \
62
+ 'line (or below a shebang on the first line).'
63
+ MSG_AFTER_CODE = 'The `%<directive>s` magic comment is ignored after any code.'
64
+ MSG_ABOVE_SHEBANG = 'A magic comment above a shebang renders the shebang ineffective.'
65
+
66
+ def on_new_investigation
67
+ return if processed_source.buffer.source.empty?
68
+
69
+ check_magic_comment_above_shebang
70
+ processed_source.comments.each { |comment| check_comment(comment) }
71
+ end
72
+
73
+ private
74
+
75
+ def check_comment(comment)
76
+ return unless magic_comment_shaped?(comment.text)
77
+
78
+ magic_comment = MagicComment.parse(comment.text)
79
+ return unless magic_comment.valid?
80
+
81
+ if magic_comment.encoding_specified?
82
+ return unless known_encoding?(magic_comment.encoding)
83
+
84
+ check_encoding_comment(comment)
85
+ elsif magic_comment.frozen_string_literal_specified?
86
+ check_top_block_comment(comment, 'frozen_string_literal')
87
+ end
88
+ end
89
+
90
+ # The Emacs and Vim regexps in `MagicComment` match anywhere inside a
91
+ # comment, which would flag documentation that merely quotes a magic
92
+ # comment (e.g. `# # -*- coding: UTF-8 -*-`). Require the magic
93
+ # comment to start right after the `#`.
94
+ def magic_comment_shaped?(text)
95
+ /\A#(?![^#]*#)/.match?(text)
96
+ end
97
+
98
+ # Prose that happens to open with `Encoding:` parses as an encoding
99
+ # comment (`# Encoding: force given encoding` yields `force`). Ruby
100
+ # ignores a misplaced encoding comment entirely, so a name no encoding
101
+ # answers to means the line is a comment, not a directive.
102
+ def known_encoding?(name)
103
+ Encoding.find(name)
104
+ true
105
+ rescue ArgumentError
106
+ false
107
+ end
108
+
109
+ def shebang?
110
+ processed_source.lines.first.to_s.start_with?('#!')
111
+ end
112
+
113
+ # First line that may carry an effective encoding comment.
114
+ def effective_encoding_line
115
+ shebang? ? 2 : 1
116
+ end
117
+
118
+ def first_code_token
119
+ @first_code_token ||= processed_source.sorted_tokens.find { |token| !token.comment? }
120
+ end
121
+
122
+ def check_encoding_comment(comment)
123
+ line = comment.source_range.line
124
+ return if line == effective_encoding_line && comment_starts_line?(comment)
125
+ # Leave `# frozen_string_literal: true` + `# encoding: x` runs at the
126
+ # very top to Lint/OrderedMagicComments, which already reorders them.
127
+ return if preceded_only_by_magic_comments?(comment)
128
+
129
+ add_offense(comment, message: MSG_ENCODING) do |corrector|
130
+ move_comment(corrector, comment, effective_encoding_line)
131
+ end
132
+ end
133
+
134
+ def check_top_block_comment(comment, directive)
135
+ return unless first_code_token
136
+ return unless comment.source_range.begin_pos > first_code_token.begin_pos
137
+
138
+ message = format(MSG_AFTER_CODE, directive: directive)
139
+ add_offense(comment, message: message) do |corrector|
140
+ move_comment(corrector, comment, effective_encoding_line)
141
+ end
142
+ end
143
+
144
+ def check_magic_comment_above_shebang
145
+ shebang_comment = processed_source.comments.find do |comment|
146
+ comment.source_range.line > 1 && comment.text.start_with?('#!') &&
147
+ comment.source_range.column.zero?
148
+ end
149
+ return unless shebang_comment
150
+ # Only flag when what precedes the shebang is magic comments -
151
+ # a `#!` comment deep inside a file is not an intended shebang.
152
+ return unless preceded_only_by_magic_comments?(shebang_comment)
153
+
154
+ add_offense(shebang_comment, message: MSG_ABOVE_SHEBANG)
155
+ end
156
+
157
+ def preceded_only_by_magic_comments?(comment)
158
+ (1...comment.source_range.line).all? do |line_number|
159
+ text = processed_source.lines[line_number - 1]
160
+ (line_number == 1 && text.start_with?('#!')) || MagicComment.parse(text).valid?
161
+ end
162
+ end
163
+
164
+ def comment_starts_line?(comment)
165
+ processed_source.lines[comment.source_range.line - 1].lstrip.start_with?('#')
166
+ end
167
+
168
+ def move_comment(corrector, comment, target_line)
169
+ removal_range = if comment_starts_line?(comment)
170
+ range_by_whole_lines(comment.source_range, include_final_newline: true)
171
+ else
172
+ range_with_surrounding_space(comment.source_range, side: :left)
173
+ end
174
+ corrector.remove(removal_range)
175
+ target_range = processed_source.buffer.line_range(target_line)
176
+ if comment.source_range.line < target_line
177
+ corrector.insert_after(target_range, "\n#{comment.text}")
178
+ else
179
+ corrector.insert_before(target_range, "#{comment.text}\n")
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Lint/RedundantCopDisableDirective
3
+ # rubocop:disable-next Lint/RedundantCopDisableDirective -- the examples below read as real directives
4
4
  module RuboCop
5
5
  module Cop
6
6
  module Lint
@@ -55,7 +55,7 @@ module RuboCop
55
55
  class MissingCopEnableDirective < Base
56
56
  include RangeHelp
57
57
 
58
- MSG = 'Re-enable %<cop>s %<type>s with `# rubocop:enable` after disabling it.'
58
+ MSG = 'Re-enable %<cop>s %<type>s with `%<directive>s` after disabling it.'
59
59
  MSG_BOUND = 'Re-enable %<cop>s %<type>s within %<max_range>s lines after disabling it.'
60
60
 
61
61
  def on_new_investigation
@@ -72,7 +72,12 @@ module RuboCop
72
72
 
73
73
  def each_missing_enable
74
74
  processed_source.disabled_line_ranges.each do |cop, line_ranges|
75
- line_ranges.each { |line_range| yield cop, line_range }
75
+ line_ranges.each do |line_range|
76
+ # A `disable-next` scope closes itself with its statement.
77
+ next if line_range.respond_to?(:directive) && line_range.directive.disable_next?
78
+
79
+ yield cop, line_range
80
+ end
76
81
  end
77
82
  end
78
83
 
@@ -80,17 +85,22 @@ module RuboCop
80
85
  # This has to remain a strict inequality to handle
81
86
  # the case when max_range is Float::INFINITY
82
87
  return true if line_range.max - line_range.min < max_range + 2
88
+ # A name that is not registered as written (e.g. a wrongly-namespaced
89
+ # cop) suppresses nothing; `Lint/RedundantCopDisableDirective` reports
90
+ # it, so demanding its re-enablement would only add noise.
91
+ return true unless Registry.global.names.include?(cop)
92
+
93
+ expected_open_range?(cop, line_range)
94
+ end
95
+
96
+ def expected_open_range?(cop, line_range)
83
97
  # This cop is disabled in the config, it is not expected to be re-enabled
84
98
  return true if line_range.min == CommentConfig::CONFIG_DISABLED_LINE_RANGE_MIN
85
99
 
86
- cop_class = RuboCop::Cop::Registry.global.find_by_cop_name cop
87
- if cop_class &&
100
+ cop_class = RuboCop::Cop::Registry.global.find_by_cop_name(cop)
101
+ !!(cop_class &&
88
102
  !processed_source.registry.enabled?(cop_class, config) &&
89
- line_range.max == Float::INFINITY
90
- return true
91
- end
92
-
93
- false
103
+ line_range.max == Float::INFINITY)
94
104
  end
95
105
 
96
106
  def max_range
@@ -98,13 +108,17 @@ module RuboCop
98
108
  end
99
109
 
100
110
  def message(cop, comment, type = 'cop')
111
+ directive = DirectiveComment.new(comment)
101
112
  if department_enabled?(cop, comment)
102
113
  type = 'department'
103
114
  cop = cop.split('/').first
104
115
  end
105
116
 
106
117
  if max_range == Float::INFINITY
107
- format(MSG, cop: cop, type: type)
118
+ # A range opened by `rubocop:push` is closed by `rubocop:pop`,
119
+ # not by `rubocop:enable`.
120
+ enabling_directive = directive.push? ? '# rubocop:pop' : '# rubocop:enable'
121
+ format(MSG, cop: cop, type: type, directive: enabling_directive)
108
122
  else
109
123
  format(MSG_BOUND, cop: cop, type: type, max_range: max_range)
110
124
  end
@@ -117,4 +131,3 @@ module RuboCop
117
131
  end
118
132
  end
119
133
  end
120
- # rubocop:enable Lint/RedundantCopDisableDirective
@@ -27,6 +27,13 @@ module RuboCop
27
27
  # alone is not reported, since the index does not see gems or the
28
28
  # standard library.
29
29
  #
30
+ # For the same reason, a namespace whose root segment names a gem in the
31
+ # bundle (`Flipper` for the `flipper` gem) is left alone: a project that
32
+ # reopens it (`module Flipper; module Adapters; ...`) makes it resolve in
33
+ # the index, while the members the gem itself defines stay invisible and
34
+ # would look like typos. Enabling `AllCops/ProjectIndexIncludesGems`
35
+ # indexes those sources and restores the check.
36
+ #
30
37
  # Names that appear as symbols or inside string literals in the same
31
38
  # file are never reported, since they usually belong to runtime
32
39
  # definitions the index cannot see (`stub_const`, `const_set`,
@@ -129,26 +136,61 @@ module RuboCop
129
136
  end
130
137
 
131
138
  def constant_typo_suggestion(node)
132
- return nil if literal_names.include?(node.short_name.to_s)
133
-
134
139
  namespace = resolve_constant_in_index(node.namespace)
135
140
  return nil unless namespace.is_a?(Rubydex::Namespace)
136
141
  return nil if namespace.find_member(node.short_name.to_s)
142
+ return nil unless complete_index_members?(namespace)
143
+ return nil if literal_names.include?(node.short_name.to_s)
137
144
 
138
145
  spell_check(node.short_name, constant_member_names(namespace))
139
146
  rescue StandardError
140
147
  nil
141
148
  end
142
149
 
150
+ # Whether the index can be trusted to list everything the namespace
151
+ # defines. A name missing from an incomplete member list is not
152
+ # evidence of a typo, so both gaps have to be ruled out first:
153
+ # an ancestor that does not resolve may contribute the name, and so
154
+ # may a gem that reopens the namespace.
155
+ def complete_index_members?(declaration)
156
+ fully_resolved_index_ancestry?(declaration) && !gem_owned_namespace?(declaration)
157
+ end
158
+
159
+ # Whether the namespace's root segment names a gem in the bundle,
160
+ # which is then free to define members the index never sees: gem
161
+ # sources are only indexed when `AllCops/ProjectIndexIncludesGems`
162
+ # is enabled. A project that reopens such a namespace to add its own
163
+ # members (`module Flipper; module Adapters; ...`) makes it resolve
164
+ # in the index while most of what it holds stays invisible.
165
+ def gem_owned_namespace?(declaration)
166
+ return false if config.for_all_cops['ProjectIndexIncludesGems']
167
+
168
+ root = declaration.name.to_s.split('::').first.to_s
169
+ bundled_gem_namespaces.include?(root.downcase)
170
+ end
171
+
172
+ # Gem names normalized towards the constant they conventionally
173
+ # provide: `flipper` for `Flipper`, `activerecord` for `ActiveRecord`.
174
+ def bundled_gem_namespaces
175
+ @bundled_gem_namespaces ||= bundled_gem_names.to_set { |name| name.delete('-_').downcase }
176
+ end
177
+
178
+ # The bundle's gems except those sourced from a local path, whose code
179
+ # lives in the project and is indexed like the rest of it — among them
180
+ # the project's own gem, which its lockfile lists in the `PATH` section.
181
+ def bundled_gem_names
182
+ (config.gem_versions_in_target || {}).keys - (config.path_sourced_gems_in_target || [])
183
+ end
184
+
143
185
  # Writers are indexed under the reader's name, so setter calls are
144
186
  # verified and spell-checked through the base name.
145
187
  def method_typo_suggestion(node)
146
188
  setter = setter_call?(node)
147
189
  base = setter ? node.method_name.to_s.delete_suffix('=') : node.method_name.to_s
148
- return nil if literal_names.include?(base)
149
190
 
150
191
  declaration = unknown_method_owner(node, base)
151
192
  return nil unless declaration
193
+ return nil if literal_names.include?(base)
152
194
 
153
195
  suggestion = spell_check(base, method_member_names(declaration))
154
196
  suggestion && setter ? "#{suggestion}=" : suggestion
@@ -156,13 +198,15 @@ module RuboCop
156
198
  nil
157
199
  end
158
200
 
159
- # The receiver's declaration when it resolves in the index, its whole
160
- # ancestry is resolved, and the method is not found — nil otherwise.
201
+ # The receiver's declaration when it resolves in the index, everything
202
+ # it can inherit or be reopened with is visible there, and the method
203
+ # is not found — nil otherwise.
161
204
  def unknown_method_owner(node, base)
162
205
  declaration = resolve_constant_in_index(node.receiver)
163
206
  return nil unless declaration.is_a?(Rubydex::Namespace)
164
207
  return nil if responds_in_index?(declaration, node.method_name.to_s, base)
165
208
  return nil unless fully_resolved_index_ancestry?(declaration)
209
+ return nil if gem_owned_namespace?(declaration)
166
210
 
167
211
  declaration
168
212
  end
@@ -191,7 +235,10 @@ module RuboCop
191
235
  if literal.sym_type?
192
236
  names << literal.value.to_s
193
237
  else
194
- literal.value.scan(LITERAL_IDENTIFIER_PATTERN) { |token| names << token }
238
+ # `scan` raises on binary string literals with invalid byte sequences.
239
+ value = literal.value
240
+ value = value.scrub unless value.valid_encoding?
241
+ value.scan(LITERAL_IDENTIFIER_PATTERN) { |token| names << token }
195
242
  end
196
243
  end
197
244
  end
@@ -95,7 +95,7 @@ module RuboCop
95
95
  MSG = 'Method definitions must not be nested. Use `lambda` instead.'
96
96
 
97
97
  def on_def(node)
98
- subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring
98
+ subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring -- the first child differs between `def` and `defs`
99
99
  return if node.defs_type? && allowed_subject_type?(subject)
100
100
 
101
101
  def_ancestor = node.each_ancestor(:any_def).first
@@ -67,7 +67,7 @@ module RuboCop
67
67
 
68
68
  # NOTE: itblock is not handled because this cop is limited to Ruby <= 2.7
69
69
  # via `maximum_target_ruby_version`, so itblock nodes (Ruby 3.4+) are never encountered.
70
- def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler
70
+ def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler -- limited to Ruby <= 2.7, where `it` blocks do not exist
71
71
  return unless node.body
72
72
  return unless unsorted_dir_loop?(node.send_node)
73
73
 
@@ -53,6 +53,8 @@ module RuboCop
53
53
  end
54
54
 
55
55
  def after_send(node)
56
+ return if backrefs_unaffected?(node)
57
+
56
58
  @valid_ref = nil
57
59
 
58
60
  if regexp_first_argument?(node)
@@ -111,6 +113,19 @@ module RuboCop
111
113
  end
112
114
  end
113
115
 
116
+ # A call such as `hash[:key]` or `array[0]` cannot involve a regexp match:
117
+ # a non-string literal cannot act as a pattern (whereas `sub`, `gsub`,
118
+ # and `match` treat a string one as a pattern and do set backreferences),
119
+ # and `$~` is frame-local, so a user-defined method cannot change
120
+ # the caller's backreferences either.
121
+ def backrefs_unaffected?(send_node)
122
+ first_argument = send_node.first_argument
123
+ return false unless first_argument&.basic_literal?
124
+ return false if first_argument.str_type?
125
+
126
+ !regexp_receiver?(send_node)
127
+ end
128
+
114
129
  def regexp_first_argument?(send_node)
115
130
  send_node.first_argument&.regexp_type? \
116
131
  && REGEXP_ARGUMENT_METHODS.include?(send_node.method_name)
@@ -46,13 +46,29 @@ module RuboCop
46
46
 
47
47
  def valid_first_argument?(first_arg)
48
48
  first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg) ||
49
- compound_range?(first_arg)
49
+ compound_range?(first_arg) || invalid_bare_argument?(first_arg)
50
50
  end
51
51
 
52
52
  def compound_range?(first_arg)
53
53
  first_arg.range_type? && first_arg.parenthesized_call?
54
54
  end
55
55
 
56
+ def invalid_bare_argument?(node)
57
+ node = node.children.first while node&.begin_type? && node.children.one?
58
+ return false unless node
59
+
60
+ keyword_operator?(node) || modifier_expression?(node)
61
+ end
62
+
63
+ def keyword_operator?(node)
64
+ (node.operator_keyword? && node.semantic_operator?) ||
65
+ node.rescue_type? || (node.send_type? && node.prefix_not?)
66
+ end
67
+
68
+ def modifier_expression?(node)
69
+ node.type?(:if, :while, :until) && node.modifier_form?
70
+ end
71
+
56
72
  def chained_calls?(node)
57
73
  first_argument = node.first_argument
58
74
  first_argument.call_type? && (node.children.last&.children&.count || 0) > 1