rubocop 1.88.2 → 1.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +139 -16
  3. data/exe/rubocop +1 -1
  4. data/lib/rubocop/cached_data.rb +7 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +31 -0
  6. data/lib/rubocop/cli.rb +2 -4
  7. data/lib/rubocop/comment_config/directive_range.rb +32 -0
  8. data/lib/rubocop/comment_config/disable_next.rb +97 -0
  9. data/lib/rubocop/comment_config.rb +81 -49
  10. data/lib/rubocop/config.rb +76 -5
  11. data/lib/rubocop/config_loader.rb +1 -2
  12. data/lib/rubocop/config_loader_resolver.rb +1 -2
  13. data/lib/rubocop/config_validator.rb +39 -3
  14. data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
  15. data/lib/rubocop/cop/badge.rb +8 -0
  16. data/lib/rubocop/cop/base.rb +40 -6
  17. data/lib/rubocop/cop/bundler.rb +19 -0
  18. data/lib/rubocop/cop/corrector.rb +4 -4
  19. data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
  20. data/lib/rubocop/cop/gemspec.rb +22 -0
  21. data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
  22. data/lib/rubocop/cop/generator.rb +4 -0
  23. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
  24. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
  25. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  26. data/lib/rubocop/cop/layout/class_structure.rb +108 -26
  27. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
  28. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
  29. data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
  31. data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
  32. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  33. data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
  34. data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
  35. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
  36. data/lib/rubocop/cop/layout/line_length.rb +84 -12
  37. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
  38. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
  40. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
  41. data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
  42. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +31 -34
  43. data/lib/rubocop/cop/layout.rb +114 -0
  44. data/lib/rubocop/cop/lazy_loader.rb +41 -0
  45. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
  46. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
  47. data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
  48. data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
  49. data/lib/rubocop/cop/lint/constant_reassignment.rb +18 -21
  50. data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
  51. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
  52. data/lib/rubocop/cop/lint/deprecated_reference.rb +194 -0
  53. data/lib/rubocop/cop/lint/duplicate_methods.rb +300 -22
  54. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
  55. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
  56. data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
  57. data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
  58. data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -6
  59. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +4 -7
  60. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
  61. data/lib/rubocop/cop/lint/missing_super.rb +31 -1
  62. data/lib/rubocop/cop/lint/name_typo.rb +270 -0
  63. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -4
  64. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
  65. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
  66. data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
  67. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
  68. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
  69. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
  70. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
  71. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -2
  72. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
  73. data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
  74. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
  75. data/lib/rubocop/cop/lint/to_enum_arguments.rb +38 -3
  76. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
  77. data/lib/rubocop/cop/lint/unused_private_method.rb +234 -0
  78. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
  80. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
  81. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  82. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
  83. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
  84. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
  85. data/lib/rubocop/cop/lint/void.rb +18 -8
  86. data/lib/rubocop/cop/lint.rb +178 -0
  87. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  88. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  89. data/lib/rubocop/cop/metrics.rb +31 -0
  90. data/lib/rubocop/cop/migration/department_name.rb +1 -6
  91. data/lib/rubocop/cop/migration.rb +13 -0
  92. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
  93. data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
  94. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
  95. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
  96. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
  97. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
  98. data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
  99. data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
  100. data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
  101. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
  102. data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
  103. data/lib/rubocop/cop/mixin/range_help.rb +1 -2
  104. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
  105. data/lib/rubocop/cop/mixin/statement_modifier.rb +46 -4
  106. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
  107. data/lib/rubocop/cop/mixin.rb +82 -77
  108. data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
  109. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
  110. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
  111. data/lib/rubocop/cop/naming/method_name.rb +5 -2
  112. data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
  113. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
  114. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  115. data/lib/rubocop/cop/naming.rb +31 -0
  116. data/lib/rubocop/cop/offense.rb +37 -10
  117. data/lib/rubocop/cop/registry.rb +180 -46
  118. data/lib/rubocop/cop/security.rb +19 -0
  119. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
  120. data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
  121. data/lib/rubocop/cop/style/array_first_last.rb +1 -2
  122. data/lib/rubocop/cop/style/array_intersect.rb +24 -5
  123. data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
  124. data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
  125. data/lib/rubocop/cop/style/case_like_if.rb +2 -4
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +79 -20
  127. data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
  128. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
  129. data/lib/rubocop/cop/style/conditional_assignment.rb +23 -2
  130. data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
  131. data/lib/rubocop/cop/style/copyright.rb +5 -1
  132. data/lib/rubocop/cop/style/directive_scope.rb +171 -0
  133. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +76 -12
  134. data/lib/rubocop/cop/style/documentation.rb +47 -0
  135. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  136. data/lib/rubocop/cop/style/endless_method.rb +0 -10
  137. data/lib/rubocop/cop/style/format_string_token.rb +1 -2
  138. data/lib/rubocop/cop/style/guard_clause.rb +1 -2
  139. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
  140. data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
  141. data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
  142. data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
  143. data/lib/rubocop/cop/style/if_unless_modifier.rb +8 -50
  144. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
  145. data/lib/rubocop/cop/style/lambda.rb +4 -0
  146. data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
  147. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -7
  148. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
  149. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
  150. data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
  151. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
  152. data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
  153. data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
  154. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
  155. data/lib/rubocop/cop/style/negative_array_index.rb +3 -3
  156. data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
  157. data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
  158. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
  159. data/lib/rubocop/cop/style/redundant_assignment.rb +12 -2
  160. data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
  161. data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
  162. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
  163. data/lib/rubocop/cop/style/redundant_each.rb +1 -2
  164. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
  165. data/lib/rubocop/cop/style/redundant_format.rb +14 -7
  166. data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
  167. data/lib/rubocop/cop/style/redundant_parentheses.rb +65 -67
  168. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
  169. data/lib/rubocop/cop/style/redundant_return.rb +1 -2
  170. data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
  171. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
  172. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
  173. data/lib/rubocop/cop/style/safe_navigation.rb +11 -2
  174. data/lib/rubocop/cop/style/sample.rb +11 -6
  175. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
  176. data/lib/rubocop/cop/style/semicolon.rb +2 -4
  177. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  178. data/lib/rubocop/cop/style/sole_nested_conditional.rb +21 -5
  179. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
  180. data/lib/rubocop/cop/style/static_class.rb +21 -0
  181. data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
  182. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  183. data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
  184. data/lib/rubocop/cop/style/time_now.rb +41 -0
  185. data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
  186. data/lib/rubocop/cop/style.rb +312 -0
  187. data/lib/rubocop/cop/team.rb +59 -6
  188. data/lib/rubocop/cop/util.rb +2 -4
  189. data/lib/rubocop/cop/variable_force/variable.rb +19 -8
  190. data/lib/rubocop/cops_documentation_generator.rb +4 -7
  191. data/lib/rubocop/directive_comment.rb +39 -3
  192. data/lib/rubocop/feature_loader.rb +0 -6
  193. data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
  194. data/lib/rubocop/formatter/html_formatter.rb +2 -4
  195. data/lib/rubocop/formatter/json_formatter.rb +11 -6
  196. data/lib/rubocop/formatter/junit_formatter.rb +26 -9
  197. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
  198. data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
  199. data/lib/rubocop/formatter/tap_formatter.rb +3 -1
  200. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  201. data/lib/rubocop/lockfile.rb +18 -0
  202. data/lib/rubocop/lsp/diagnostic.rb +4 -8
  203. data/lib/rubocop/lsp/routes.rb +89 -22
  204. data/lib/rubocop/lsp/runtime.rb +4 -0
  205. data/lib/rubocop/lsp/server.rb +4 -0
  206. data/lib/rubocop/lsp/stdin_runner.rb +20 -0
  207. data/lib/rubocop/mcp/server.rb +1 -2
  208. data/lib/rubocop/options.rb +10 -6
  209. data/lib/rubocop/path_util.rb +1 -2
  210. data/lib/rubocop/pending_cops_reporter.rb +1 -6
  211. data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
  212. data/lib/rubocop/project_index_loader.rb +16 -0
  213. data/lib/rubocop/rake_task.rb +1 -2
  214. data/lib/rubocop/result_cache.rb +6 -4
  215. data/lib/rubocop/rspec/expect_offense.rb +2 -4
  216. data/lib/rubocop/rspec/shared_contexts.rb +16 -2
  217. data/lib/rubocop/runner.rb +71 -43
  218. data/lib/rubocop/server/cache.rb +11 -7
  219. data/lib/rubocop/server/cli.rb +2 -4
  220. data/lib/rubocop/server.rb +14 -0
  221. data/lib/rubocop/target_finder.rb +4 -1
  222. data/lib/rubocop/target_ruby.rb +1 -1
  223. data/lib/rubocop/todo_audit.rb +111 -0
  224. data/lib/rubocop/version.rb +3 -5
  225. data/lib/rubocop.rb +20 -626
  226. data/lib/ruby_lsp/rubocop/addon.rb +1 -2
  227. metadata +28 -5
@@ -46,7 +46,7 @@ module RuboCop
46
46
  @corrected_nodes = nil
47
47
  end
48
48
 
49
- # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
49
+ # rubocop:disable-next Metrics/AbcSize,Metrics/CyclomaticComplexity
50
50
  def on_if(node)
51
51
  return unless if_else?(node)
52
52
  return unless (condition = unwrap_begin_nodes(node.condition))
@@ -64,7 +64,6 @@ module RuboCop
64
64
  end
65
65
  end
66
66
  end
67
- # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity
68
67
 
69
68
  private
70
69
 
@@ -91,8 +91,9 @@ module RuboCop
91
91
  end
92
92
  end
93
93
 
94
+ # rubocop:disable-next Metrics/CyclomaticComplexity
94
95
  def range_with_length_subtraction?(range_node, array_receiver)
95
- return false unless range_node.range_type?
96
+ return false unless range_node&.range_type?
96
97
 
97
98
  range_end = range_node.end
98
99
  range_start = range_node.begin
@@ -122,7 +123,7 @@ module RuboCop
122
123
  end
123
124
  end
124
125
 
125
- # rubocop:disable Metrics/ParameterLists
126
+ # rubocop:disable-next Metrics/ParameterLists
126
127
  def build_range_offense_data(receiver, range_node, range_end, inner_end, negative_index,
127
128
  index_arg)
128
129
  range_op = range_node.erange_type? ? '...' : '..'
@@ -138,7 +139,6 @@ module RuboCop
138
139
 
139
140
  [message, replacement]
140
141
  end
141
- # rubocop:enable Metrics/ParameterLists
142
142
 
143
143
  def format_range_message_parts(range_start, negative_index, index_arg)
144
144
  has_parentheses = index_arg.begin_type?
@@ -42,7 +42,7 @@ module RuboCop
42
42
  # @!method nil_check?(node)
43
43
  def_node_matcher :nil_check?, '(send _ :nil?)'
44
44
 
45
- # rubocop:disable Metrics/AbcSize
45
+ # rubocop:disable-next Metrics/AbcSize
46
46
  def on_send(node)
47
47
  return unless node.receiver
48
48
 
@@ -61,7 +61,6 @@ module RuboCop
61
61
  end
62
62
  end
63
63
  end
64
- # rubocop:enable Metrics/AbcSize
65
64
 
66
65
  private
67
66
 
@@ -26,7 +26,7 @@ module RuboCop
26
26
  splat kwsplat forwarded_args forwarded_restarg forwarded_kwrestarg block_pass
27
27
  ].freeze
28
28
 
29
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
29
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
30
30
  def on_send(node)
31
31
  return unless (dot = node.loc.dot)
32
32
  return if unary_method_no_operator?(node)
@@ -44,7 +44,6 @@ module RuboCop
44
44
  corrector.insert_after(selector, ' ') if insert_space_after?(node)
45
45
  end
46
46
  end
47
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
48
47
 
49
48
  private
50
49
 
@@ -184,9 +184,8 @@ module RuboCop
184
184
  def accesses?(rhs, lhs)
185
185
  if lhs.method?(:[]=)
186
186
  # FIXME: Workaround `rubocop:disable` comment for JRuby.
187
- # rubocop:disable Performance/RedundantEqualityComparisonBlock
187
+ # rubocop:disable-next Performance/RedundantEqualityComparisonBlock
188
188
  matching_calls(rhs, lhs.receiver, :[]).any? { |args| args == lhs.arguments }
189
- # rubocop:enable Performance/RedundantEqualityComparisonBlock
190
189
  else
191
190
  access_method = lhs.method_name.to_s.chop.to_sym
192
191
  matching_calls(rhs, lhs.receiver, access_method).any?
@@ -5,6 +5,9 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for redundant assignment before returning.
7
7
  #
8
+ # When there are comments between the assignment and reference,
9
+ # the cop will report an offense but it will not autocorrect.
10
+ #
8
11
  # @example
9
12
  # # bad
10
13
  # def test
@@ -54,7 +57,7 @@ module RuboCop
54
57
 
55
58
  private
56
59
 
57
- # rubocop:disable Metrics/CyclomaticComplexity
60
+ # rubocop:disable-next Metrics/CyclomaticComplexity
58
61
  def check_branch(node)
59
62
  return unless node
60
63
 
@@ -69,7 +72,6 @@ module RuboCop
69
72
  check_begin_node(node)
70
73
  end
71
74
  end
72
- # rubocop:enable Metrics/CyclomaticComplexity
73
75
 
74
76
  def check_case_node(node)
75
77
  node.when_branches.each { |when_node| check_branch(when_node.body) }
@@ -99,6 +101,8 @@ module RuboCop
99
101
  def check_begin_node(node)
100
102
  if (assignment = redundant_assignment?(node))
101
103
  add_offense(assignment) do |corrector|
104
+ next if comments_between_assignment_and_reference?(assignment)
105
+
102
106
  expression = assignment.children[1]
103
107
  corrector.replace(assignment, expression.source)
104
108
  corrector.remove(assignment.right_sibling)
@@ -108,6 +112,12 @@ module RuboCop
108
112
  check_branch(last_expr)
109
113
  end
110
114
  end
115
+
116
+ def comments_between_assignment_and_reference?(assignment)
117
+ line_span = assignment.source_range.line..assignment.right_sibling.source_range.line
118
+
119
+ processed_source.each_comment_in_lines(line_span).any?
120
+ end
111
121
  end
112
122
  end
113
123
  end
@@ -180,7 +180,7 @@ module RuboCop
180
180
  !use_hash_key_access?(if_branch)
181
181
  end
182
182
 
183
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
183
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
184
184
  def if_branch_is_true_type_and_else_is_not?(node)
185
185
  return false unless node.ternary? || node.if?
186
186
 
@@ -190,7 +190,6 @@ module RuboCop
190
190
 
191
191
  node.if_branch&.true_type? && node.else_branch && !node.else_branch.true_type?
192
192
  end
193
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
194
193
 
195
194
  def branches_have_assignment?(node)
196
195
  _condition, if_branch, else_branch = *node # rubocop:disable InternalAffairs/NodeDestructuring
@@ -240,7 +239,7 @@ module RuboCop
240
239
  "#{method.source}(#{arguments.source})"
241
240
  end
242
241
 
243
- # rubocop:disable Metrics/AbcSize
242
+ # rubocop:disable-next Metrics/AbcSize
244
243
  def if_source(if_branch, arithmetic_operation)
245
244
  if branches_have_method?(if_branch.parent) && if_branch.parenthesized?
246
245
  if_branch.source.delete_suffix(')')
@@ -257,7 +256,6 @@ module RuboCop
257
256
  if_branch.source
258
257
  end
259
258
  end
260
- # rubocop:enable Metrics/AbcSize
261
259
 
262
260
  def else_source(else_branch, arithmetic_operation) # rubocop:disable Metrics/AbcSize
263
261
  if arithmetic_operation
@@ -14,6 +14,11 @@ module RuboCop
14
14
  # to prevent conflicting rules. This is because it respects user configurations
15
15
  # that want to enable `Lint/ConstantResolution` cop which is disabled by default.
16
16
  #
17
+ # When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
18
+ # installed, a leading `::` inside a namespace is also reported when the
19
+ # constant provably resolves to the same declaration with and without
20
+ # the base, i.e. nothing in the surrounding nesting shadows it.
21
+ #
17
22
  # @example
18
23
  # # bad
19
24
  # ::Const
@@ -41,13 +46,14 @@ module RuboCop
41
46
  # ::Const
42
47
  # end
43
48
  class RedundantConstantBase < Base
49
+ include ProjectIndexHelp
44
50
  extend AutoCorrector
45
51
 
46
52
  MSG = 'Remove redundant `::`.'
47
53
 
48
54
  def on_cbase(node)
49
55
  return if lint_constant_resolution_cop_enabled?
50
- return unless bad?(node)
56
+ return unless bad?(node) || provably_unshadowed?(node)
51
57
 
52
58
  add_offense(node) do |corrector|
53
59
  corrector.remove(node)
@@ -56,6 +62,36 @@ module RuboCop
56
62
 
57
63
  private
58
64
 
65
+ # When `AllCops/UseProjectIndex` is enabled, `::` inside a namespace is
66
+ # also redundant when the first constant segment resolves to the same
67
+ # declaration with and without the base, i.e. nothing in the
68
+ # surrounding nesting shadows it.
69
+ def provably_unshadowed?(node)
70
+ return false unless project_index
71
+ return false unless (segment = first_constant_segment(node))
72
+
73
+ nesting = module_nesting_ancestors_of(node)
74
+ .map { |ancestor| ancestor.identifier.const_name }.reverse
75
+
76
+ !nesting.empty? && same_resolution?(segment, nesting)
77
+ rescue StandardError
78
+ false
79
+ end
80
+
81
+ def same_resolution?(segment, nesting)
82
+ relative = project_index.resolve_constant(segment, nesting)
83
+ absolute = project_index.resolve_constant(segment, [])
84
+
85
+ !relative.nil? && !absolute.nil? && relative.name == absolute.name
86
+ end
87
+
88
+ def first_constant_segment(node)
89
+ parent = node.parent
90
+ return nil unless parent&.const_type?
91
+
92
+ parent.short_name.to_s
93
+ end
94
+
59
95
  def lint_constant_resolution_cop_enabled?
60
96
  lint_constant_resolution_config.fetch('Enabled', false)
61
97
  end
@@ -25,7 +25,7 @@ module RuboCop
25
25
  MSG = 'Remove the redundant double splat and braces, use keyword arguments directly.'
26
26
  MERGE_METHODS = %i[merge merge!].freeze
27
27
 
28
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
28
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
29
29
  def on_hash(node)
30
30
  return if node.pairs.empty? || node.pairs.any?(&:hash_rocket?)
31
31
  return unless (parent = node.parent)
@@ -38,7 +38,6 @@ module RuboCop
38
38
  autocorrect(corrector, node, kwsplat)
39
39
  end
40
40
  end
41
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
42
41
 
43
42
  private
44
43
 
@@ -60,7 +60,7 @@ module RuboCop
60
60
 
61
61
  private
62
62
 
63
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
64
64
  def redundant_each_method(node)
65
65
  return if node.last_argument&.block_pass_type?
66
66
 
@@ -81,7 +81,6 @@ module RuboCop
81
81
 
82
82
  prev_method if detected || prev_method.method?(:reverse_each)
83
83
  end
84
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
85
84
 
86
85
  def range(node)
87
86
  return node.selector unless node.method?(:each)
@@ -41,6 +41,10 @@ module RuboCop
41
41
  return unless name_node.value.end_with?('.rb')
42
42
 
43
43
  extension_range = extension_range(name_node)
44
+ # A backslash right before the extension means the path is escaped;
45
+ # dropping the extension would either produce invalid Ruby (a trailing
46
+ # backslash escaping the closing quote) or a confusing path, so skip it.
47
+ return if escaped_extension?(name_node, extension_range)
44
48
 
45
49
  add_offense(extension_range) do |corrector|
46
50
  corrector.remove(extension_range)
@@ -55,6 +59,11 @@ module RuboCop
55
59
 
56
60
  range_between(end_of_path_string - 4, end_of_path_string - 1)
57
61
  end
62
+
63
+ def escaped_extension?(name_node, extension_range)
64
+ offset = extension_range.begin_pos - name_node.source_range.begin_pos
65
+ name_node.source[0...offset].end_with?('\\')
66
+ end
58
67
  end
59
68
  end
60
69
  end
@@ -155,7 +155,7 @@ module RuboCop
155
155
  end
156
156
  end
157
157
 
158
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
158
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
159
159
  def all_fields_literal?(string, arguments)
160
160
  count = 0
161
161
  sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences
@@ -175,18 +175,17 @@ module RuboCop
175
175
 
176
176
  sequences.size == count
177
177
  end
178
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
179
178
 
180
179
  # If the sequence has a variable (`*`) width, it cannot be autocorrected
181
180
  # if the width is not given as a numeric literal argument
182
181
  def unknown_variable_width?(sequence, arguments)
183
182
  return false unless sequence.variable_width?
184
183
 
185
- argument = arguments[sequence.variable_width_argument_number - 1]
186
- !numeric?(argument)
184
+ argument = positional_argument(arguments, sequence.variable_width_argument_number)
185
+ argument.nil? || !numeric?(argument)
187
186
  end
188
187
 
189
- # rubocop:disable Metrics/AbcSize
188
+ # rubocop:disable-next Metrics/AbcSize
190
189
  def find_argument(sequence, arguments, hash)
191
190
  if hash && (sequence.annotated? || sequence.template?)
192
191
  find_hash_value_node(hash, sequence.name.to_sym).first
@@ -195,12 +194,11 @@ module RuboCop
195
194
  arguments.delete_at(sequence.variable_width_argument_number - 1)
196
195
  arguments.shift
197
196
  elsif sequence.arg_number
198
- arguments[sequence.arg_number.to_i - 1]
197
+ positional_argument(arguments, sequence.arg_number.to_i)
199
198
  else
200
199
  arguments.shift
201
200
  end
202
201
  end
203
- # rubocop:enable Metrics/AbcSize
204
202
 
205
203
  def matching_argument?(sequence, argument)
206
204
  # Template specifiers don't give a type, any acceptable literal type is ok.
@@ -220,6 +218,15 @@ module RuboCop
220
218
  end
221
219
  end
222
220
 
221
+ # `Array#[]` raises `RangeError` for an index beyond the 64-bit range
222
+ # (e.g. `format('%*9999999999999999999999$d', 1)`) instead of returning `nil`,
223
+ # so the index needs to be checked before accessing the array.
224
+ def positional_argument(arguments, number)
225
+ index = number - 1
226
+
227
+ arguments[index] if index < arguments.size
228
+ end
229
+
223
230
  def numeric?(argument)
224
231
  argument.type?(:numeric, :str) ||
225
232
  rational_number?(argument) ||
@@ -5,10 +5,13 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for redundant line continuation.
7
7
  #
8
- # This cop marks a line continuation as redundant if removing the backslash
9
- # does not result in a syntax error.
10
- # However, a backslash at the end of a comment or
11
- # for string concatenation is not redundant and is not considered an offense.
8
+ # A line continuation is redundant when removing the backslash does not
9
+ # change how the program parses: the source is reparsed without the
10
+ # backslash and the resulting AST is compared to the original. Only
11
+ # backslashes that are pure noise are reported; backslashes that are
12
+ # significant — inside strings, for string concatenation, before an
13
+ # operator or argument that would otherwise start a new statement, and
14
+ # so on — are left alone, as are backslashes in comments.
12
15
  #
13
16
  # @example
14
17
  # # bad
@@ -66,31 +69,23 @@ module RuboCop
66
69
  #
67
70
  class RedundantLineContinuation < Base
68
71
  include MatchRange
72
+ include ReparsedEquivalence
69
73
  extend AutoCorrector
70
74
 
71
75
  MSG = 'Redundant line continuation.'
72
76
  LINE_CONTINUATION = '\\'
73
77
  LINE_CONTINUATION_PATTERN = /(\\\n)/.freeze
74
- ALLOWED_STRING_TOKENS = %i[tSTRING tSTRING_CONTENT].freeze
75
- ARGUMENT_TYPES = %i[
76
- kDEF kDEFINED kFALSE kNIL kSELF kTRUE tAMPER tBANG tCARET tCHARACTER tCOLON3 tCONSTANT
77
- tCVAR tDOT2 tDOT3 tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR tLAMBDA tLBRACK tLCURLY
78
- tLPAREN_ARG tPIPE tQSYMBOLS_BEG tQWORDS_BEG tREGEXP_BEG tSTAR tSTRING tSTRING_BEG tSYMBEG
79
- tSYMBOL tSYMBOLS_BEG tTILDE tUMINUS tUNARY_NUM tUPLUS tWORDS_BEG tXSTRING_BEG
80
- ].freeze
81
- ARGUMENT_TAKING_FLOW_TOKEN_TYPES = %i[
82
- tIDENTIFIER kBREAK kNEXT kRETURN kSUPER kYIELD
83
- ].freeze
84
- ARITHMETIC_OPERATOR_TOKENS = %i[tDIVIDE tDSTAR tMINUS tPERCENT tPLUS tSTAR2].freeze
85
- STRING_LITERAL_BEGIN_TOKENS = %i[tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tSYMBEG].freeze
78
+ STRING_TOKEN_TYPES = %i[tSTRING tSTRING_CONTENT].freeze
79
+ STRING_LITERAL_ENDING_TOKEN_TYPES = %i[tSTRING tSTRING_END].freeze
80
+ STRING_LITERAL_BEGINNING_TOKEN_TYPES = %i[tSTRING tSTRING_BEG].freeze
86
81
 
87
82
  def on_new_investigation
88
83
  return unless processed_source.ast
89
84
 
90
- each_match_range(processed_source.ast.source_range, LINE_CONTINUATION_PATTERN) do |range|
91
- next if require_line_continuation?(range)
92
- next unless redundant_line_continuation?(range)
93
-
85
+ # A backslash is redundant if the source parses to the same AST
86
+ # without it; verification is the offense logic itself, so oversized
87
+ # scopes are reparsed regardless of size.
88
+ verified_by_reparse(line_continuation_candidates, oversized: :verify).each do |range|
94
89
  add_offense(range) do |corrector|
95
90
  corrector.remove_leading(range, 1)
96
91
  end
@@ -101,87 +96,100 @@ module RuboCop
101
96
 
102
97
  private
103
98
 
104
- def require_line_continuation?(range)
105
- !ends_with_uncommented_backslash?(range) ||
106
- string_concatenation?(range.source_line) ||
107
- start_with_arithmetic_operator?(range) ||
108
- inside_string_literal_or_method_with_argument?(range) ||
109
- inside_string_literal_with_interpolation?(range) ||
110
- leading_dot_method_chain_with_blank_line?(range)
99
+ def apply_reparse_correction(corrector, range)
100
+ corrector.remove_leading(range, 1)
111
101
  end
112
102
 
113
- def ends_with_uncommented_backslash?(range)
114
- # A line continuation always needs to be the last character on the line, which
115
- # means that it is impossible to have a comment following a continuation.
116
- # Therefore, if the line contains a comment, it cannot end with a continuation.
117
- return false if processed_source.line_with_comment?(range.line)
103
+ def line_continuation_candidates
104
+ candidates = []
105
+
106
+ each_match_range(processed_source.ast.source_range, LINE_CONTINUATION_PATTERN) do |range|
107
+ next if within_comment?(range) || implicit_string_concatenation?(range)
108
+ next if within_string_content?(range)
109
+ next if leading_dot_method_chain_with_blank_line?(range)
110
+
111
+ candidates << range
112
+ end
118
113
 
119
- range.source_line.end_with?(LINE_CONTINUATION)
114
+ candidates
120
115
  end
121
116
 
122
- def string_concatenation?(source_line)
123
- /["']\s*\\\z/.match?(source_line)
117
+ def within_comment?(range)
118
+ processed_source.comments.any? do |comment|
119
+ comment.source_range.overlaps?(range)
120
+ end
124
121
  end
125
122
 
126
- def inside_string_literal_or_method_with_argument?(range)
127
- line_range = range_by_whole_lines(range)
123
+ # A backslash directly joining two string literals is never redundant:
124
+ # removing it moves the second literal onto its own logical line,
125
+ # severing the implicit string concatenation. Skipping these up front
126
+ # avoids reparse verification, which is prohibitively slow on generated
127
+ # files gluing thousands of string fragments with line continuations.
128
+ def implicit_string_concatenation?(range)
129
+ tokens = processed_source.sorted_tokens
128
130
 
129
- processed_source.tokens.each_cons(2).any? do |token, next_token|
130
- next if token.line == next_token.line
131
+ token_before = last_token_before(tokens, range)
132
+ return false unless string_literal_ending_on_line?(token_before, range.line)
131
133
 
132
- inside_string_literal?(range, token) ||
133
- method_with_argument?(line_range, token, next_token)
134
- end
134
+ string_literal_beginning_on_line?(first_token_after(tokens, range), range.line + 1)
135
135
  end
136
136
 
137
- def inside_string_literal_with_interpolation?(range)
138
- string_depth = 0
139
- processed_source.tokens.each do |token|
140
- break if token.pos.begin_pos >= range.begin_pos
141
-
142
- if STRING_LITERAL_BEGIN_TOKENS.include?(token.type)
143
- string_depth += 1
144
- elsif token.type == :tSTRING_END
145
- string_depth -= 1
146
- end
137
+ # A backslash in string content is never a redundant line continuation
138
+ # (removing it changes the string's value, which the reparse check would
139
+ # detect); skipping it up front just avoids a useless reparse.
140
+ def within_string_content?(range)
141
+ @string_content_ranges ||= processed_source.tokens.filter_map do |token|
142
+ token.pos if STRING_TOKEN_TYPES.include?(token.type)
147
143
  end
148
- string_depth.positive?
144
+
145
+ @string_content_ranges.any? { |pos| pos.overlaps?(range) }
149
146
  end
150
147
 
148
+ # The `parser` gem, unlike Ruby itself, accepts a leading-dot method
149
+ # chain continued across a blank line, so the reparse check alone would
150
+ # deem the backslash redundant even though removing it breaks the code
151
+ # on MRI. Prism matches Ruby here and does not need this guard.
151
152
  def leading_dot_method_chain_with_blank_line?(range)
152
153
  return false unless range.source_line.strip.start_with?('.', '&.')
153
154
 
154
155
  processed_source[range.line].strip.empty?
155
156
  end
156
157
 
157
- def redundant_line_continuation?(range)
158
- return true unless (node = find_node_for_line(range.last_line))
159
- return false if argument_newline?(node)
158
+ def last_token_before(tokens, range)
159
+ index = tokens.bsearch_index { |token| token.end_pos > range.begin_pos }
160
+ index&.positive? ? tokens[index - 1] : nil
161
+ end
162
+
163
+ def first_token_after(tokens, range)
164
+ tokens.bsearch { |token| token.begin_pos >= range.end_pos }
165
+ end
166
+
167
+ def string_literal_ending_on_line?(token, line)
168
+ return false unless token
169
+
170
+ STRING_LITERAL_ENDING_TOKEN_TYPES.include?(token.type) && token.pos.last_line == line
171
+ end
172
+
173
+ def string_literal_beginning_on_line?(token, line)
174
+ return false unless token
160
175
 
161
- # Check if source is still valid without the continuation
162
- source = processed_source.raw_source.dup
163
- source[range.begin_pos, range.length] = "\n"
164
- parse(source).valid_syntax?
176
+ STRING_LITERAL_BEGINNING_TOKEN_TYPES.include?(token.type) && token.line == line
165
177
  end
166
178
 
167
179
  def inspect_end_of_ruby_code_line_continuation
168
180
  last_line_number = processed_source.ast.last_line
169
181
  last_line = processed_source.lines[last_line_number - 1]
170
- return unless code_ends_with_continuation?(last_line)
182
+ return unless last_line&.end_with?(LINE_CONTINUATION)
171
183
 
172
- line_continuation_range = trailing_line_continuation_range(last_line_number)
184
+ range = trailing_line_continuation_range(last_line_number)
185
+ return if within_comment?(range)
186
+ return if verified_by_reparse([range], oversized: :verify).empty?
173
187
 
174
- add_offense(line_continuation_range) do |corrector|
175
- corrector.remove_trailing(line_continuation_range, 1)
188
+ add_offense(range) do |corrector|
189
+ corrector.remove_trailing(range, 1)
176
190
  end
177
191
  end
178
192
 
179
- def code_ends_with_continuation?(last_line)
180
- return false if processed_source.line_with_comment?(processed_source.ast.last_line)
181
-
182
- last_line.end_with?(LINE_CONTINUATION)
183
- end
184
-
185
193
  # The backslash is the last character of the line; locate it by the line's
186
194
  # position in the buffer rather than treating the column as an absolute offset
187
195
  # (which corrupts an earlier line in multi-line files).
@@ -189,71 +197,6 @@ module RuboCop
189
197
  line_range = processed_source.buffer.line_range(line_number)
190
198
  range_between(line_range.end_pos - 1, line_range.end_pos)
191
199
  end
192
-
193
- def inside_string_literal?(range, token)
194
- ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
195
- end
196
-
197
- # A method call without parentheses such as the following cannot remove `\`:
198
- #
199
- # do_something \
200
- # argument
201
- def method_with_argument?(line_range, current_token, next_token)
202
- return false unless ARGUMENT_TAKING_FLOW_TOKEN_TYPES.include?(current_token.type)
203
- return false unless current_token.pos.overlaps?(line_range)
204
-
205
- ARGUMENT_TYPES.include?(next_token.type)
206
- end
207
-
208
- def argument_newline?(node)
209
- return false if node.parenthesized_call?
210
-
211
- node = node.children.first if node.root? && node.begin_type?
212
-
213
- if argument_is_method?(node)
214
- argument_newline?(node.first_argument)
215
- else
216
- return false unless method_call_with_arguments?(node)
217
-
218
- node.loc.selector.line != node.first_argument.loc.line
219
- end
220
- end
221
-
222
- def find_node_for_line(last_line)
223
- processed_source.ast.each_node do |node|
224
- return node if same_line?(node, last_line)
225
- end
226
- end
227
-
228
- def same_line?(node, line)
229
- return false unless (source_range = node.source_range)
230
-
231
- if node.is_a?(AST::StrNode)
232
- if node.heredoc?
233
- (node.loc.heredoc_body.line..node.loc.heredoc_body.last_line).cover?(line)
234
- else
235
- (source_range.line..source_range.last_line).cover?(line)
236
- end
237
- else
238
- source_range.line == line
239
- end
240
- end
241
-
242
- def argument_is_method?(node)
243
- return false unless node.send_type?
244
- return false unless (first_argument = node.first_argument)
245
-
246
- method_call_with_arguments?(first_argument)
247
- end
248
-
249
- def method_call_with_arguments?(node)
250
- node.call_type? && !node.arguments.empty?
251
- end
252
-
253
- def start_with_arithmetic_operator?(range)
254
- line_range = processed_source.buffer.line_range(range.line + 1)
255
- ARITHMETIC_OPERATOR_TOKENS.include?(processed_source.first_token_of(line_range).type)
256
- end
257
200
  end
258
201
  end
259
202
  end