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
@@ -15,6 +15,7 @@ module RuboCop
15
15
  #
16
16
  class RedundantParentheses < Base # rubocop:disable Metrics/ClassLength
17
17
  include Parentheses
18
+ include ReparsedEquivalence
18
19
  extend AutoCorrector
19
20
 
20
21
  ALLOWED_NODE_TYPES = %i[or send splat kwsplat].freeze
@@ -30,12 +31,30 @@ module RuboCop
30
31
  # @!method allowed_pin_operator?(node)
31
32
  def_node_matcher :allowed_pin_operator?, '^(pin (begin !{lvar ivar cvar gvar}))'
32
33
 
34
+ def on_new_investigation
35
+ @pending_offenses = {}.compare_by_identity
36
+ super
37
+ end
38
+
33
39
  def on_begin(node)
34
40
  return if !parentheses?(node) || parens_allowed?(node) || ignore_syntax?(node)
35
41
 
36
42
  check(node)
37
43
  end
38
44
 
45
+ def on_investigation_end
46
+ # Each candidate's exact correction is verified by reparsing before
47
+ # the offense is registered, so redundancy never depends on
48
+ # hand-maintained knowledge of Ruby's grammar.
49
+ verified_by_reparse(@pending_offenses.keys).each do |node|
50
+ add_offense(node, message: @pending_offenses[node]) do |corrector|
51
+ ParenthesesCorrector.correct(corrector, node)
52
+ end
53
+ end
54
+
55
+ super
56
+ end
57
+
39
58
  private
40
59
 
41
60
  def variable?(node)
@@ -44,7 +63,6 @@ module RuboCop
44
63
 
45
64
  def parens_allowed?(node)
46
65
  empty_parentheses?(node) ||
47
- first_arg_begins_with_hash_literal?(node) ||
48
66
  rescue?(node) ||
49
67
  in_pattern_matching_in_method_argument?(node) ||
50
68
  allowed_pin_operator?(node) ||
@@ -111,15 +129,6 @@ module RuboCop
111
129
  node.children.empty?
112
130
  end
113
131
 
114
- def first_arg_begins_with_hash_literal?(node)
115
- # Don't flag `method ({key: value})` or `method ({key: value}.method)`
116
- hash_literal = method_chain_begins_with_hash_literal(node.children.first)
117
- if (root_method = node.each_ancestor(:call).to_a.last)
118
- parenthesized = root_method.parenthesized_call?
119
- end
120
- hash_literal && first_argument?(node) && !parentheses?(hash_literal) && !parenthesized
121
- end
122
-
123
132
  def in_pattern_matching_in_method_argument?(begin_node)
124
133
  return false unless begin_node.parent&.call_type?
125
134
  return false unless (node = begin_node.children.first)
@@ -127,14 +136,6 @@ module RuboCop
127
136
  target_ruby_version <= 2.7 ? node.match_pattern_type? : node.match_pattern_p_type?
128
137
  end
129
138
 
130
- def method_chain_begins_with_hash_literal(node)
131
- return if node.nil?
132
- return node if node.hash_type?
133
- return unless node.send_type?
134
-
135
- method_chain_begins_with_hash_literal(node.children.first)
136
- end
137
-
138
139
  def check(begin_node)
139
140
  node = begin_node.children.first
140
141
 
@@ -151,7 +152,7 @@ module RuboCop
151
152
  check_send(begin_node, node) if call_node?(node)
152
153
  end
153
154
 
154
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
155
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
155
156
  def find_offense_message(begin_node, node)
156
157
  return 'a keyword' if keyword_with_redundant_parentheses?(node)
157
158
  return 'a literal' if node.literal? && disallowed_literal?(begin_node, node)
@@ -188,7 +189,6 @@ module RuboCop
188
189
  'a comparison expression'
189
190
  end
190
191
  end
191
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
192
192
 
193
193
  # @!method interpolation?(node)
194
194
  def_node_matcher :interpolation?, '[^begin ^^dstr]'
@@ -231,8 +231,6 @@ module RuboCop
231
231
  return check_unary(begin_node, node) if node.unary_operation?
232
232
 
233
233
  return unless method_call_with_redundant_parentheses?(begin_node, node)
234
- return if call_chain_starts_with_int?(begin_node, node) ||
235
- do_end_block_in_method_chain?(begin_node, node)
236
234
 
237
235
  offense(begin_node, 'a method call')
238
236
  end
@@ -247,11 +245,50 @@ module RuboCop
247
245
  end
248
246
 
249
247
  def offense(node, msg)
250
- add_offense(node, message: "Don't use parentheses around #{msg}.") do |corrector|
251
- ParenthesesCorrector.correct(corrector, node)
248
+ @pending_offenses[node] = "Don't use parentheses around #{msg}."
249
+ end
250
+
251
+ def apply_reparse_correction(corrector, node)
252
+ ParenthesesCorrector.correct(corrector, node)
253
+ end
254
+
255
+ # Grouping parentheses are transparent to the comparison: single-child
256
+ # `begin` nodes are collapsed, a parenthesized statement sequence
257
+ # inside another sequence is spliced in place, and chains of the same
258
+ # `&&`/`||` operator are normalized to left association (`x && (y &&
259
+ # z)` and `x && y && z` differ as trees but `&&` and `||` cannot be
260
+ # redefined, so same-operator regrouping is semantically transparent).
261
+ def normalize_reparsed_ast(node)
262
+ return node unless node.is_a?(::Parser::AST::Node)
263
+
264
+ children = node.children.map { |child| normalize_reparsed_ast(child) }
265
+ children = splice_nested_sequences(children) if %i[begin kwbegin].include?(node.type)
266
+
267
+ node = node.updated(nil, children)
268
+ if node.begin_type? && children.one? && children.first.is_a?(::Parser::AST::Node)
269
+ children.first
270
+ else
271
+ rotate_same_operator(node)
272
+ end
273
+ end
274
+
275
+ def splice_nested_sequences(children)
276
+ children.flat_map do |child|
277
+ child.is_a?(::Parser::AST::Node) && child.begin_type? ? child.children : [child]
252
278
  end
253
279
  end
254
280
 
281
+ def rotate_same_operator(node)
282
+ return node unless node.type?(:and, :or)
283
+
284
+ right = node.rhs
285
+ return node unless right.is_a?(::Parser::AST::Node) && right.type == node.type
286
+
287
+ rotated_left = rotate_same_operator(node.updated(nil, [node.lhs, right.lhs]))
288
+
289
+ rotate_same_operator(node.updated(nil, [rotated_left, right.rhs]))
290
+ end
291
+
255
292
  def suspect_unary?(node)
256
293
  node.send_type? && node.unary_operation? && !node.prefix_not?
257
294
  end
@@ -261,16 +298,13 @@ module RuboCop
261
298
  end
262
299
 
263
300
  def disallowed_literal?(begin_node, node)
264
- if node.range_type?
265
- return false unless (parent = begin_node.parent)
301
+ return true unless node.range_type?
302
+ return false unless (parent = begin_node.parent)
266
303
 
267
- parent.begin_type? && parent.children.one?
268
- else
269
- !raised_to_power_negative_numeric?(begin_node, node)
270
- end
304
+ parent.begin_type? && parent.children.one?
271
305
  end
272
306
 
273
- # rubocop:disable Metrics/CyclomaticComplexity
307
+ # rubocop:disable-next Metrics/CyclomaticComplexity
274
308
  def body_range?(begin_node, node)
275
309
  return false if begin_node.chained?
276
310
  return false unless node.range_type?
@@ -280,7 +314,6 @@ module RuboCop
280
314
  (node.begin.nil? && begin_node == parent.children.first) ||
281
315
  (node.end.nil? && begin_node == parent.children.last)
282
316
  end
283
- # rubocop:enable Metrics/CyclomaticComplexity
284
317
 
285
318
  def disallowed_one_line_pattern_matching?(begin_node, node)
286
319
  if (parent = begin_node.parent)
@@ -291,17 +324,6 @@ module RuboCop
291
324
  node.any_match_pattern_type? && node.each_ancestor.none?(&:operator_keyword?)
292
325
  end
293
326
 
294
- def raised_to_power_negative_numeric?(begin_node, node)
295
- return false unless node.numeric_type?
296
-
297
- next_sibling = begin_node.right_sibling
298
- return false unless next_sibling
299
-
300
- base_value = node.children.first
301
-
302
- base_value.negative? && next_sibling == :**
303
- end
304
-
305
327
  def keyword_with_redundant_parentheses?(node)
306
328
  return false unless node.keyword?
307
329
  return true if node.special_keyword?
@@ -333,30 +355,6 @@ module RuboCop
333
355
  def only_begin_arg?(args)
334
356
  args.one? && args.first&.begin_type?
335
357
  end
336
-
337
- def first_argument?(node)
338
- return true if first_call_argument?(node)
339
-
340
- node.each_ancestor.any? { |ancestor| first_argument?(ancestor) }
341
- end
342
-
343
- # @!method first_call_argument?(node)
344
- def_node_matcher :first_call_argument?, <<~PATTERN
345
- {^(call _ _ equal?(%0) ...)
346
- ^({super yield} equal?(%0) ...)}
347
- PATTERN
348
-
349
- def call_chain_starts_with_int?(begin_node, send_node)
350
- recv = first_part_of_call_chain(send_node)
351
- recv&.int_type? && (parent = begin_node.parent) &&
352
- parent.send_type? && (parent.method?(:-@) || parent.method?(:+@))
353
- end
354
-
355
- def do_end_block_in_method_chain?(begin_node, node)
356
- return false unless (block = node.each_descendant(:any_block).first)
357
-
358
- block.keywords? && begin_node.each_ancestor(:call).any?
359
- end
360
358
  end
361
359
  end
362
360
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
  DETERMINISTIC_REGEX.match?(regexp_node.source)
67
67
  end
68
68
 
69
- # rubocop:disable Metrics/MethodLength
69
+ # rubocop:disable-next Metrics/MethodLength
70
70
  def preferred_argument(regexp_node)
71
71
  new_argument = replacement(regexp_node)
72
72
 
@@ -92,7 +92,6 @@ module RuboCop
92
92
 
93
93
  "#{quote}#{new_argument}#{quote}"
94
94
  end
95
- # rubocop:enable Metrics/MethodLength
96
95
 
97
96
  def replacement(regexp_node)
98
97
  regexp_content = regexp_node.content
@@ -108,7 +108,7 @@ module RuboCop
108
108
  corrector.insert_after(node.children.last, '}')
109
109
  end
110
110
 
111
- # rubocop:disable Metrics/CyclomaticComplexity
111
+ # rubocop:disable-next Metrics/CyclomaticComplexity
112
112
  def check_branch(node)
113
113
  return unless node
114
114
 
@@ -124,7 +124,6 @@ module RuboCop
124
124
  check_begin_node(node)
125
125
  end
126
126
  end
127
- # rubocop:enable Metrics/CyclomaticComplexity
128
127
 
129
128
  def check_return_node(node)
130
129
  return if cop_config['AllowMultipleReturnValues'] && node.children.size > 1
@@ -55,7 +55,7 @@ module RuboCop
55
55
  ...))
56
56
  PATTERN
57
57
 
58
- # rubocop:disable Metrics/AbcSize
58
+ # rubocop:disable-next Metrics/AbcSize
59
59
  def on_lvasgn(node)
60
60
  return unless (rhs = node.rhs)
61
61
  return unless rhs.type?(:any_block, :call) && method_returning_self?(rhs.method_name)
@@ -69,7 +69,6 @@ module RuboCop
69
69
  corrector.replace(node, rhs.source)
70
70
  end
71
71
  end
72
- # rubocop:enable Metrics/AbcSize
73
72
  alias on_ivasgn on_lvasgn
74
73
  alias on_cvasgn on_lvasgn
75
74
  alias on_gvasgn on_lvasgn
@@ -75,7 +75,7 @@ module RuboCop
75
75
  node.loc.to_hash.key?(:begin) && !node.loc.begin.nil?
76
76
  end
77
77
 
78
- # rubocop:disable Metrics/CyclomaticComplexity
78
+ # rubocop:disable-next Metrics/CyclomaticComplexity
79
79
  def allowed_escape?(node, range)
80
80
  escaped = range.source[(1..-1)]
81
81
 
@@ -95,7 +95,6 @@ module RuboCop
95
95
 
96
96
  false
97
97
  end
98
- # rubocop:enable Metrics/CyclomaticComplexity
99
98
 
100
99
  def interpolation_not_enabled?(node)
101
100
  single_quoted?(node) ||
@@ -67,7 +67,7 @@ module RuboCop
67
67
  node.parent && parentheses?(node.parent)
68
68
  end
69
69
 
70
- # rubocop:disable Metrics/AbcSize
70
+ # rubocop:disable-next Metrics/AbcSize
71
71
  def correct_rescue_block(corrector, node, parenthesized)
72
72
  operation = node.body
73
73
 
@@ -83,7 +83,6 @@ module RuboCop
83
83
  #{node_offset}end
84
84
  RESCUE_CLAUSE
85
85
  end
86
- # rubocop:enable Metrics/AbcSize
87
86
 
88
87
  def indentation_and_offset(node, parenthesized)
89
88
  node_indentation = indentation(node)
@@ -37,6 +37,16 @@ module RuboCop
37
37
  # x&.foo # raises NoMethodError
38
38
  # ----
39
39
  #
40
+ # Additionally, when a method chain is converted, a `NoMethodError` that
41
+ # the original code raised for an intermediate `nil` value is suppressed:
42
+ #
43
+ # [source,ruby]
44
+ # ----
45
+ # x = Struct.new(:foo).new(nil)
46
+ # x && x.foo.bar # raises NoMethodError
47
+ # x&.foo&.bar # returns nil
48
+ # ----
49
+ #
40
50
  # @example
41
51
  # # bad
42
52
  # foo.bar if foo
@@ -142,7 +152,7 @@ module RuboCop
142
152
  # @!method strip_begin(node)
143
153
  def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
144
154
 
145
- # rubocop:disable Metrics/AbcSize
155
+ # rubocop:disable-next Metrics/AbcSize
146
156
  def on_if(node)
147
157
  return if allowed_if_condition?(node)
148
158
 
@@ -160,7 +170,6 @@ module RuboCop
160
170
  corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
161
171
  end
162
172
  end
163
- # rubocop:enable Metrics/AbcSize
164
173
 
165
174
  def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
166
175
  collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
@@ -7,6 +7,10 @@ module RuboCop
7
7
  # `shuffle.last`, and `shuffle[]` and change them to use
8
8
  # `sample` instead.
9
9
  #
10
+ # NOTE: An offense involving a `random:` argument is registered but not autocorrected:
11
+ # `shuffle` and `sample` consume the given generator differently, so for a seeded generator
12
+ # the correction would select different elements.
13
+ #
10
14
  # @example
11
15
  # # bad
12
16
  # [1, 2, 3].shuffle.first
@@ -45,10 +49,12 @@ module RuboCop
45
49
  range = source_range(shuffle_node, node)
46
50
  message = message(shuffle_arg, method, method_args, range)
47
51
 
48
- add_offense(range, message: message) do |corrector|
49
- corrector.replace(
50
- source_range(shuffle_node, node), correction(shuffle_arg, method, method_args)
51
- )
52
+ if shuffle_arg.empty?
53
+ add_offense(range, message: message) do |corrector|
54
+ corrector.replace(range, correction(shuffle_arg, method, method_args))
55
+ end
56
+ else
57
+ add_offense(range, message: message)
52
58
  end
53
59
  end
54
60
  end
@@ -92,7 +98,7 @@ module RuboCop
92
98
  second.int_type? ? second.to_a.first : :unknown
93
99
  end
94
100
 
95
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
101
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
96
102
  def range_size(range_node)
97
103
  vals = range_node.to_a
98
104
  return :unknown unless vals.all? { |val| val.nil? || val.int_type? }
@@ -107,7 +113,6 @@ module RuboCop
107
113
  (low..high).size
108
114
  end
109
115
  end
110
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
111
116
 
112
117
  def source_range(shuffle_node, node)
113
118
  shuffle_node.loc.selector.join(node.source_range.end)
@@ -97,7 +97,7 @@ module RuboCop
97
97
  }
98
98
  PATTERN
99
99
 
100
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
100
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
101
101
  def on_send(node)
102
102
  return if target_ruby_version < 2.6 && node.method?(:filter)
103
103
  return unless (block_node = node.block_node)
@@ -113,7 +113,6 @@ module RuboCop
113
113
 
114
114
  register_offense(node, block_node, regexp, replacement)
115
115
  end
116
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
117
116
  alias on_csend on_send
118
117
 
119
118
  private
@@ -83,7 +83,7 @@ module RuboCop
83
83
  processed_source.tokens.group_by(&:line)
84
84
  end
85
85
 
86
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
86
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
87
87
  def semicolon_position(tokens)
88
88
  if tokens.last.semicolon?
89
89
  -1
@@ -100,7 +100,6 @@ module RuboCop
100
100
  -4
101
101
  end
102
102
  end
103
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
104
103
 
105
104
  def exist_semicolon_before_right_curly_brace?(tokens)
106
105
  tokens[-2]&.right_curly_brace? && tokens[-3]&.semicolon?
@@ -122,7 +121,7 @@ module RuboCop
122
121
  tokens[1]&.type == :tSTRING_DBEG && tokens[2]&.semicolon?
123
122
  end
124
123
 
125
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
124
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
126
125
  def register_semicolon(line, column, after_expression, token_before_semicolon = nil)
127
126
  range = source_range(processed_source.buffer, line, column)
128
127
 
@@ -146,7 +145,6 @@ module RuboCop
146
145
  end
147
146
  end
148
147
  end
149
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
150
148
 
151
149
  def replace_semicolon_with_line_break(corrector, range)
152
150
  # Replacing the semicolon with a newline would move the rest of the
@@ -36,7 +36,7 @@ module RuboCop
36
36
 
37
37
  MSG = 'Prefer multiline `do`...`end` block.'
38
38
 
39
- # rubocop:disable Metrics/AbcSize
39
+ # rubocop:disable-next Metrics/AbcSize
40
40
  def on_block(node)
41
41
  return if node.multiline? || node.braces?
42
42
  return if single_line_blocks_preferred? && suitable_as_single_line?(node)
@@ -55,7 +55,6 @@ module RuboCop
55
55
  end
56
56
  end
57
57
  end
58
- # rubocop:enable Metrics/AbcSize
59
58
  alias on_numblock on_block
60
59
  alias on_itblock on_block
61
60
 
@@ -48,6 +48,7 @@ module RuboCop
48
48
  # end if condition_a
49
49
  class SoleNestedConditional < Base
50
50
  include RangeHelp
51
+ include ReparsedEquivalence
51
52
  extend AutoCorrector
52
53
 
53
54
  MSG = 'Consider merging nested conditions into outer `%<conditional_type>s` conditions.'
@@ -57,11 +58,10 @@ module RuboCop
57
58
  end
58
59
 
59
60
  def on_if(node)
60
- return if node.ternary? || node.else? || node.elsif?
61
+ return unless offending_conditional?(node)
61
62
 
62
63
  if_branch = node.if_branch
63
- return if use_variable_assignment_in_condition?(node.condition, if_branch)
64
- return unless offending_branch?(node, if_branch)
64
+ return unless correction_parses?(node)
65
65
 
66
66
  message = format(MSG, conditional_type: node.keyword)
67
67
  add_offense(if_branch.loc.keyword, message: message) do |corrector|
@@ -74,6 +74,23 @@ module RuboCop
74
74
 
75
75
  private
76
76
 
77
+ def offending_conditional?(node)
78
+ return false if node.ternary? || node.else? || node.elsif?
79
+
80
+ if_branch = node.if_branch
81
+ return false if use_variable_assignment_in_condition?(node.condition, if_branch)
82
+
83
+ offending_branch?(node, if_branch)
84
+ end
85
+
86
+ # Merging the conditionals intentionally produces a different AST, so
87
+ # full equivalence cannot be checked, but the corrected source must at
88
+ # least remain parseable (see `ReparsedEquivalence#correction_parses?`),
89
+ # which suppresses the entire produces-invalid-code bug class.
90
+ def apply_reparse_correction(corrector, node)
91
+ autocorrect(corrector, node, node.if_branch)
92
+ end
93
+
77
94
  def use_variable_assignment_in_condition?(condition, if_branch)
78
95
  assigned_variables = assigned_variables(condition)
79
96
 
@@ -131,7 +148,7 @@ module RuboCop
131
148
  corrector.remove(range_with_surrounding_space(range, newlines: false))
132
149
  end
133
150
 
134
- # rubocop:disable Metrics/AbcSize
151
+ # rubocop:disable-next Metrics/AbcSize
135
152
  def correct_for_basic_condition_style(corrector, node, if_branch)
136
153
  range = range_between(
137
154
  node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
@@ -147,7 +164,6 @@ module RuboCop
147
164
  end
148
165
  corrector.remove(end_range)
149
166
  end
150
- # rubocop:enable Metrics/AbcSize
151
167
 
152
168
  def autocorrect_outer_condition_modify_form(corrector, node, if_branch)
153
169
  correct_node(corrector, if_branch)
@@ -25,6 +25,8 @@ module RuboCop
25
25
 
26
26
  MSG_REQUIRE = 'Wrap stabby lambda arguments with parentheses.'
27
27
  MSG_NO_REQUIRE = 'Do not wrap stabby lambda arguments with parentheses.'
28
+ RESTRICT_ON_SEND = %i[lambda].freeze
29
+
28
30
  def on_send(node)
29
31
  return unless stabby_lambda_with_args?(node)
30
32
  return unless redundant_parentheses?(node) || missing_parentheses?(node)
@@ -12,6 +12,11 @@ module RuboCop
12
12
  # for some other subclass, monkey-patched with instance methods or
13
13
  # a dummy instance is instantiated from it somewhere.
14
14
  #
15
+ # When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
16
+ # installed, classes that are subclassed anywhere in the project are
17
+ # not reported, since converting them to modules would break their
18
+ # subclasses.
19
+ #
15
20
  # @example
16
21
  # # bad
17
22
  # class SomeClass
@@ -44,6 +49,7 @@ module RuboCop
44
49
  # end
45
50
  #
46
51
  class StaticClass < Base
52
+ include ProjectIndexHelp
47
53
  include RangeHelp
48
54
  include VisibilityHelp
49
55
  extend AutoCorrector
@@ -53,6 +59,7 @@ module RuboCop
53
59
  def on_class(class_node)
54
60
  return if class_node.parent_class
55
61
  return unless class_convertible_to_module?(class_node)
62
+ return if subclassed_in_project?(class_node)
56
63
 
57
64
  add_offense(class_node) do |corrector|
58
65
  autocorrect(corrector, class_node)
@@ -61,6 +68,20 @@ module RuboCop
61
68
 
62
69
  private
63
70
 
71
+ # When `AllCops/UseProjectIndex` is enabled, a class with descendants
72
+ # anywhere in the project is not reported: converting it to a module
73
+ # would break its subclasses.
74
+ def subclassed_in_project?(class_node)
75
+ return false unless project_index
76
+
77
+ declaration = resolve_constant_in_index(class_node.identifier)
78
+ return false unless declaration.is_a?(Rubydex::Class)
79
+
80
+ declaration.descendants.any? { |descendant| descendant.name != declaration.name }
81
+ rescue StandardError
82
+ false
83
+ end
84
+
64
85
  def autocorrect(corrector, class_node)
65
86
  corrector.replace(class_node.loc.keyword, 'module')
66
87
  corrector.insert_after(class_node.loc.name, "\nmodule_function\n")
@@ -149,11 +149,7 @@ module RuboCop
149
149
  def adjust_str(part)
150
150
  case part.type
151
151
  when :str
152
- if single_quoted?(part)
153
- part.value.gsub(/(\\|"|#\{|#@|#\$)/, '\\\\\&')
154
- else
155
- part.value.inspect[1..-2]
156
- end
152
+ adjust_str_literal(part)
157
153
  when :dstr, :begin
158
154
  part.children.map do |child|
159
155
  adjust_str(child)
@@ -163,6 +159,19 @@ module RuboCop
163
159
  end
164
160
  end
165
161
 
162
+ def adjust_str_literal(part)
163
+ if single_quoted?(part)
164
+ part.value.gsub(/(\\|"|#\{|#@|#\$)/, '\\\\\&')
165
+ elsif double_quoted?(part)
166
+ # Reuse the source as written - rebuilding it from the value via
167
+ # `inspect` would rewrite the author's escape notation
168
+ # (e.g. `\x0a` into `\n`).
169
+ part.source[1..-2]
170
+ else
171
+ part.value.inspect[1..-2]
172
+ end
173
+ end
174
+
166
175
  def handle_quotes(parts)
167
176
  parts.map do |part|
168
177
  part == '"' ? '\"' : part
@@ -173,6 +182,10 @@ module RuboCop
173
182
  str_node.source.start_with?("'")
174
183
  end
175
184
 
185
+ def double_quoted?(str_node)
186
+ str_node.source.start_with?('"')
187
+ end
188
+
176
189
  def mode
177
190
  cop_config['Mode'].to_sym
178
191
  end
@@ -103,7 +103,7 @@ module RuboCop
103
103
  end
104
104
  end
105
105
 
106
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
106
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
107
107
  def arguments_identical?(def_node, super_node, def_args, super_args)
108
108
  return false if argument_list_size_differs?(def_args, super_args, super_node)
109
109
 
@@ -120,7 +120,6 @@ module RuboCop
120
120
 
121
121
  true
122
122
  end
123
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
124
123
 
125
124
  def argument_list_size_differs?(def_args, super_args, super_node)
126
125
  # If the def node has a block argument and the super node has an explicit block,
@@ -167,7 +167,7 @@ module RuboCop
167
167
  [Layout::SpaceBeforeBlockBraces]
168
168
  end
169
169
 
170
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
170
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
171
171
  def on_block(node)
172
172
  symbol_proc?(node) do |dispatch_node, arguments_node, method_name|
173
173
  if active_support_extensions_enabled?
@@ -184,7 +184,6 @@ module RuboCop
184
184
  register_offense(node, method_name, dispatch_node.method_name)
185
185
  end
186
186
  end
187
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
188
187
  alias on_numblock on_block
189
188
  alias on_itblock on_block
190
189