rubocop 1.18.1 → 1.22.3

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +116 -23
  4. data/lib/rubocop/cli.rb +18 -0
  5. data/lib/rubocop/config.rb +5 -0
  6. data/lib/rubocop/config_loader.rb +5 -3
  7. data/lib/rubocop/config_loader_resolver.rb +22 -7
  8. data/lib/rubocop/config_validator.rb +27 -6
  9. data/lib/rubocop/cop/base.rb +3 -3
  10. data/lib/rubocop/cop/bundler/gem_comment.rb +3 -3
  11. data/lib/rubocop/cop/bundler/gem_filename.rb +103 -0
  12. data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +45 -21
  13. data/lib/rubocop/cop/bundler/ordered_gems.rb +3 -12
  14. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +2 -2
  15. data/lib/rubocop/cop/correctors/line_break_corrector.rb +1 -1
  16. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +11 -10
  17. data/lib/rubocop/cop/correctors/require_library_corrector.rb +23 -0
  18. data/lib/rubocop/cop/documentation.rb +1 -1
  19. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +3 -12
  20. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +31 -24
  21. data/lib/rubocop/cop/generator.rb +14 -8
  22. data/lib/rubocop/cop/internal_affairs/inherit_deprecated_cop_class.rb +34 -0
  23. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +60 -0
  24. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
  25. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +71 -0
  26. data/lib/rubocop/cop/internal_affairs.rb +3 -0
  27. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  28. data/lib/rubocop/cop/layout/assignment_indentation.rb +1 -1
  29. data/lib/rubocop/cop/layout/block_alignment.rb +3 -3
  30. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  31. data/lib/rubocop/cop/layout/dot_position.rb +34 -5
  32. data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
  33. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +9 -0
  34. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +7 -4
  35. data/lib/rubocop/cop/layout/end_alignment.rb +9 -2
  36. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  37. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
  38. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -1
  39. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +1 -1
  40. data/lib/rubocop/cop/layout/hash_alignment.rb +22 -18
  41. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
  42. data/lib/rubocop/cop/layout/heredoc_indentation.rb +0 -7
  43. data/lib/rubocop/cop/layout/indentation_style.rb +2 -2
  44. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  45. data/lib/rubocop/cop/layout/leading_comment_space.rb +2 -2
  46. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +36 -17
  47. data/lib/rubocop/cop/layout/line_length.rb +9 -7
  48. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -1
  49. data/lib/rubocop/cop/layout/multiline_block_layout.rb +3 -3
  50. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +3 -0
  51. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +1 -1
  52. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -0
  53. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +23 -10
  54. data/lib/rubocop/cop/layout/single_line_block_chain.rb +15 -4
  55. data/lib/rubocop/cop/layout/space_after_not.rb +1 -0
  56. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +2 -1
  57. data/lib/rubocop/cop/layout/space_around_keyword.rb +2 -2
  58. data/lib/rubocop/cop/layout/space_around_operators.rb +12 -1
  59. data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -0
  60. data/lib/rubocop/cop/layout/space_before_comment.rb +2 -2
  61. data/lib/rubocop/cop/layout/space_inside_parens.rb +78 -32
  62. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -1
  63. data/lib/rubocop/cop/layout/trailing_whitespace.rb +24 -1
  64. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +111 -0
  65. data/lib/rubocop/cop/lint/ambiguous_range.rb +105 -0
  66. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -2
  67. data/lib/rubocop/cop/lint/assignment_in_condition.rb +7 -5
  68. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +18 -5
  69. data/lib/rubocop/cop/lint/boolean_symbol.rb +5 -0
  70. data/lib/rubocop/cop/lint/debugger.rb +2 -4
  71. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +4 -4
  72. data/lib/rubocop/cop/lint/deprecated_constants.rb +3 -2
  73. data/lib/rubocop/cop/lint/disjunctive_assignment_in_constructor.rb +24 -1
  74. data/lib/rubocop/cop/lint/duplicate_branch.rb +2 -1
  75. data/lib/rubocop/cop/lint/duplicate_methods.rb +8 -5
  76. data/lib/rubocop/cop/lint/else_layout.rb +10 -6
  77. data/lib/rubocop/cop/lint/empty_in_pattern.rb +1 -1
  78. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  79. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  80. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +12 -3
  81. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +67 -0
  82. data/lib/rubocop/cop/lint/interpolation_check.rb +5 -0
  83. data/lib/rubocop/cop/lint/loop.rb +4 -3
  84. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +5 -1
  85. data/lib/rubocop/cop/lint/number_conversion.rb +12 -1
  86. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
  87. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +4 -2
  88. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +17 -0
  89. data/lib/rubocop/cop/lint/percent_string_array.rb +10 -0
  90. data/lib/rubocop/cop/lint/raise_exception.rb +4 -0
  91. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +5 -4
  92. data/lib/rubocop/cop/lint/require_relative_self_path.rb +50 -0
  93. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -1
  94. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +1 -1
  95. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  96. data/lib/rubocop/cop/lint/triple_quotes.rb +1 -1
  97. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +8 -3
  98. data/lib/rubocop/cop/lint/unused_method_argument.rb +2 -3
  99. data/lib/rubocop/cop/lint/useless_method_definition.rb +3 -2
  100. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  101. data/lib/rubocop/cop/lint/useless_times.rb +5 -4
  102. data/lib/rubocop/cop/metrics/abc_size.rb +6 -0
  103. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  104. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  105. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -1
  106. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  107. data/lib/rubocop/cop/mixin/annotation_comment.rb +57 -34
  108. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  109. data/lib/rubocop/cop/mixin/code_length.rb +1 -1
  110. data/lib/rubocop/cop/mixin/documentation_comment.rb +5 -2
  111. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -2
  112. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +23 -1
  113. data/lib/rubocop/cop/mixin/hash_transform_method.rb +9 -4
  114. data/lib/rubocop/cop/mixin/heredoc.rb +5 -0
  115. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +1 -1
  116. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +2 -2
  117. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
  118. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +9 -1
  119. data/lib/rubocop/cop/mixin/percent_array.rb +18 -7
  120. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +9 -1
  121. data/lib/rubocop/cop/mixin/require_library.rb +59 -0
  122. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +1 -1
  123. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +2 -2
  124. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  125. data/lib/rubocop/cop/mixin/string_literals_help.rb +5 -1
  126. data/lib/rubocop/cop/mixin/trailing_body.rb +1 -1
  127. data/lib/rubocop/cop/naming/ascii_identifiers.rb +0 -3
  128. data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
  129. data/lib/rubocop/cop/naming/constant_name.rb +1 -1
  130. data/lib/rubocop/cop/naming/inclusive_language.rb +27 -10
  131. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +5 -4
  132. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +7 -0
  133. data/lib/rubocop/cop/security/io_methods.rb +49 -0
  134. data/lib/rubocop/cop/security/json_load.rb +8 -7
  135. data/lib/rubocop/cop/security/open.rb +4 -0
  136. data/lib/rubocop/cop/security/yaml_load.rb +4 -0
  137. data/lib/rubocop/cop/style/accessor_grouping.rb +2 -2
  138. data/lib/rubocop/cop/style/and_or.rb +5 -0
  139. data/lib/rubocop/cop/style/arguments_forwarding.rb +13 -2
  140. data/lib/rubocop/cop/style/array_coercion.rb +21 -3
  141. data/lib/rubocop/cop/style/ascii_comments.rb +0 -3
  142. data/lib/rubocop/cop/style/block_delimiters.rb +50 -2
  143. data/lib/rubocop/cop/style/case_equality.rb +6 -9
  144. data/lib/rubocop/cop/style/case_like_if.rb +5 -0
  145. data/lib/rubocop/cop/style/class_and_module_children.rb +9 -0
  146. data/lib/rubocop/cop/style/collection_compact.rb +7 -5
  147. data/lib/rubocop/cop/style/collection_methods.rb +8 -6
  148. data/lib/rubocop/cop/style/combinable_loops.rb +3 -2
  149. data/lib/rubocop/cop/style/comment_annotation.rb +55 -25
  150. data/lib/rubocop/cop/style/commented_keyword.rb +9 -3
  151. data/lib/rubocop/cop/style/conditional_assignment.rb +19 -5
  152. data/lib/rubocop/cop/style/date_time.rb +5 -0
  153. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  154. data/lib/rubocop/cop/style/documentation.rb +23 -8
  155. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +1 -7
  156. data/lib/rubocop/cop/style/double_negation.rb +27 -6
  157. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  158. data/lib/rubocop/cop/style/encoding.rb +26 -15
  159. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  160. data/lib/rubocop/cop/style/explicit_block_argument.rb +46 -11
  161. data/lib/rubocop/cop/style/float_division.rb +10 -2
  162. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +14 -3
  163. data/lib/rubocop/cop/style/global_std_stream.rb +4 -0
  164. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +11 -0
  165. data/lib/rubocop/cop/style/hash_each_methods.rb +5 -0
  166. data/lib/rubocop/cop/style/hash_except.rb +4 -3
  167. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  168. data/lib/rubocop/cop/style/hash_transform_keys.rb +4 -9
  169. data/lib/rubocop/cop/style/hash_transform_values.rb +4 -6
  170. data/lib/rubocop/cop/style/identical_conditional_branches.rb +32 -5
  171. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +18 -4
  172. data/lib/rubocop/cop/style/infinite_loop.rb +4 -3
  173. data/lib/rubocop/cop/style/inverse_methods.rb +9 -2
  174. data/lib/rubocop/cop/style/lambda_call.rb +1 -1
  175. data/lib/rubocop/cop/style/line_end_concatenation.rb +14 -1
  176. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -6
  177. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -1
  178. data/lib/rubocop/cop/style/missing_else.rb +7 -0
  179. data/lib/rubocop/cop/style/module_function.rb +8 -9
  180. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +1 -1
  181. data/lib/rubocop/cop/style/multiline_when_then.rb +1 -1
  182. data/lib/rubocop/cop/style/mutable_constant.rb +79 -14
  183. data/lib/rubocop/cop/style/negated_if.rb +1 -1
  184. data/lib/rubocop/cop/style/negated_unless.rb +1 -1
  185. data/lib/rubocop/cop/style/non_nil_check.rb +2 -2
  186. data/lib/rubocop/cop/style/not.rb +2 -2
  187. data/lib/rubocop/cop/style/numbered_parameters.rb +46 -0
  188. data/lib/rubocop/cop/style/numbered_parameters_limit.rb +50 -0
  189. data/lib/rubocop/cop/style/numeric_literals.rb +7 -8
  190. data/lib/rubocop/cop/style/numeric_predicate.rb +5 -0
  191. data/lib/rubocop/cop/style/optional_arguments.rb +4 -0
  192. data/lib/rubocop/cop/style/optional_boolean_parameter.rb +14 -4
  193. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -1
  194. data/lib/rubocop/cop/style/percent_q_literals.rb +2 -2
  195. data/lib/rubocop/cop/style/preferred_hash_methods.rb +9 -4
  196. data/lib/rubocop/cop/style/quoted_symbols.rb +10 -6
  197. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  198. data/lib/rubocop/cop/style/redundant_argument.rb +19 -9
  199. data/lib/rubocop/cop/style/redundant_begin.rb +25 -0
  200. data/lib/rubocop/cop/style/redundant_condition.rb +2 -3
  201. data/lib/rubocop/cop/style/redundant_fetch_block.rb +4 -0
  202. data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +12 -3
  203. data/lib/rubocop/cop/style/redundant_freeze.rb +4 -4
  204. data/lib/rubocop/cop/style/redundant_interpolation.rb +1 -1
  205. data/lib/rubocop/cop/style/redundant_percent_q.rb +2 -3
  206. data/lib/rubocop/cop/style/redundant_self.rb +10 -0
  207. data/lib/rubocop/cop/style/redundant_self_assignment.rb +4 -3
  208. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +83 -0
  209. data/lib/rubocop/cop/style/redundant_sort.rb +53 -20
  210. data/lib/rubocop/cop/style/regexp_literal.rb +3 -3
  211. data/lib/rubocop/cop/style/return_nil.rb +2 -1
  212. data/lib/rubocop/cop/style/safe_navigation.rb +13 -2
  213. data/lib/rubocop/cop/style/select_by_regexp.rb +139 -0
  214. data/lib/rubocop/cop/style/semicolon.rb +32 -24
  215. data/lib/rubocop/cop/style/single_argument_dig.rb +5 -0
  216. data/lib/rubocop/cop/style/single_line_block_params.rb +3 -1
  217. data/lib/rubocop/cop/style/single_line_methods.rb +25 -15
  218. data/lib/rubocop/cop/style/slicing_with_range.rb +13 -0
  219. data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -0
  220. data/lib/rubocop/cop/style/special_global_vars.rb +25 -0
  221. data/lib/rubocop/cop/style/static_class.rb +5 -5
  222. data/lib/rubocop/cop/style/string_chars.rb +4 -2
  223. data/lib/rubocop/cop/style/string_concatenation.rb +5 -1
  224. data/lib/rubocop/cop/style/string_hash_keys.rb +4 -0
  225. data/lib/rubocop/cop/style/struct_inheritance.rb +4 -0
  226. data/lib/rubocop/cop/style/swap_values.rb +4 -2
  227. data/lib/rubocop/cop/style/symbol_array.rb +3 -3
  228. data/lib/rubocop/cop/style/symbol_proc.rb +26 -0
  229. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +19 -0
  230. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  231. data/lib/rubocop/cop/style/word_array.rb +23 -5
  232. data/lib/rubocop/cop/style/yoda_condition.rb +24 -7
  233. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -0
  234. data/lib/rubocop/cop/util.rb +22 -6
  235. data/lib/rubocop/cops_documentation_generator.rb +17 -5
  236. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -1
  237. data/lib/rubocop/magic_comment.rb +44 -15
  238. data/lib/rubocop/options.rb +127 -113
  239. data/lib/rubocop/rake_task.rb +1 -1
  240. data/lib/rubocop/result_cache.rb +3 -3
  241. data/lib/rubocop/rspec/cop_helper.rb +1 -1
  242. data/lib/rubocop/rspec/expect_offense.rb +6 -2
  243. data/lib/rubocop/rspec/parallel_formatter.rb +90 -0
  244. data/lib/rubocop/rspec/support.rb +1 -0
  245. data/lib/rubocop/runner.rb +2 -3
  246. data/lib/rubocop/target_finder.rb +1 -1
  247. data/lib/rubocop/version.rb +1 -1
  248. data/lib/rubocop.rb +14 -2
  249. metadata +21 -5
@@ -65,9 +65,8 @@ module RuboCop
65
65
  end
66
66
 
67
67
  def allowed_percent_q?(node)
68
- node.source.start_with?(PERCENT_Q) && acceptable_q?(node) ||
69
- node.source.start_with?(PERCENT_CAPITAL_Q) &&
70
- acceptable_capital_q?(node)
68
+ (node.source.start_with?(PERCENT_Q) && acceptable_q?(node)) ||
69
+ (node.source.start_with?(PERCENT_CAPITAL_Q) && acceptable_capital_q?(node))
71
70
  end
72
71
 
73
72
  def message(node)
@@ -100,6 +100,10 @@ module RuboCop
100
100
  add_lhs_to_local_variables_scopes(rhs, lhs)
101
101
  end
102
102
 
103
+ def on_in_pattern(node)
104
+ add_match_var_scopes(node)
105
+ end
106
+
103
107
  def on_send(node)
104
108
  return unless node.self_receiver? && regular_method_call?(node)
105
109
  return if node.parent&.mlhs_type?
@@ -185,6 +189,12 @@ module RuboCop
185
189
  add_lhs_to_local_variables_scopes(rhs, child.to_a.first)
186
190
  end
187
191
  end
192
+
193
+ def add_match_var_scopes(in_pattern_node)
194
+ in_pattern_node.each_descendant(:match_var) do |match_var_node|
195
+ @local_variables_scopes[in_pattern_node] << match_var_node.children.first
196
+ end
197
+ end
188
198
  end
189
199
  end
190
200
  end
@@ -6,9 +6,10 @@ module RuboCop
6
6
  # This cop checks for places where redundant assignments are made for in place
7
7
  # modification methods.
8
8
  #
9
- # This cop is marked as unsafe, because it can produce false positives for
10
- # user defined methods having one of the expected names, but not modifying
11
- # its receiver in place.
9
+ # @safety
10
+ # This cop is unsafe, because it can produce false positives for
11
+ # user defined methods having one of the expected names, but not modifying
12
+ # its receiver in place.
12
13
  #
13
14
  # @example
14
15
  # # bad
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for places where conditional branch makes redundant self-assignment.
7
+ #
8
+ # It only detects local variable because it may replace state of instance variable,
9
+ # class variable, and global variable that have state across methods with `nil`.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # foo = condition ? bar : foo
15
+ #
16
+ # # good
17
+ # foo = bar if condition
18
+ #
19
+ # # bad
20
+ # foo = condition ? foo : bar
21
+ #
22
+ # # good
23
+ # foo = bar unless condition
24
+ #
25
+ class RedundantSelfAssignmentBranch < Base
26
+ include RangeHelp
27
+ extend AutoCorrector
28
+
29
+ MSG = 'Remove the self-assignment branch.'
30
+
31
+ # @!method bad_method?(node)
32
+ def_node_matcher :bad_method?, <<~PATTERN
33
+ (send nil? :bad_method ...)
34
+ PATTERN
35
+
36
+ def on_lvasgn(node)
37
+ variable, expression = *node
38
+ return unless use_if_and_else_branch?(expression)
39
+
40
+ if_branch = expression.if_branch
41
+ else_branch = expression.else_branch
42
+ return if inconvertible_to_modifier?(if_branch, else_branch)
43
+
44
+ if self_assign?(variable, if_branch)
45
+ register_offense(expression, if_branch, else_branch, 'unless')
46
+ elsif self_assign?(variable, else_branch)
47
+ register_offense(expression, else_branch, if_branch, 'if')
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def use_if_and_else_branch?(expression)
54
+ return false unless expression&.if_type?
55
+
56
+ !expression.ternary? || !expression.else?
57
+ end
58
+
59
+ def inconvertible_to_modifier?(if_branch, else_branch)
60
+ multiple_statements?(if_branch) || multiple_statements?(else_branch) ||
61
+ (else_branch.respond_to?(:elsif?) && else_branch.elsif?)
62
+ end
63
+
64
+ def multiple_statements?(branch)
65
+ branch && branch.children.compact.count > 1
66
+ end
67
+
68
+ def self_assign?(variable, branch)
69
+ variable.to_s == branch&.source
70
+ end
71
+
72
+ def register_offense(if_node, offense_branch, opposite_branch, keyword)
73
+ add_offense(offense_branch) do |corrector|
74
+ assignment_value = opposite_branch ? opposite_branch.source : 'nil'
75
+ replacement = "#{assignment_value} #{keyword} #{if_node.condition.source}"
76
+
77
+ corrector.replace(if_node, replacement)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -12,6 +12,33 @@ module RuboCop
12
12
  # `Enumerable#max_by` can replace `Enumerable#sort_by` calls
13
13
  # after which only the first or last element is used.
14
14
  #
15
+ # @safety
16
+ # This cop is unsafe, because `sort...last` and `max` may not return the
17
+ # same element in all cases.
18
+ #
19
+ # In an enumerable where there are multiple elements where `a <=> b == 0`,
20
+ # or where the transformation done by the `sort_by` block has the
21
+ # same result, `sort.last` and `max` (or `sort_by.last` and `max_by`)
22
+ # will return different elements. `sort.last` will return the last
23
+ # element but `max` will return the first element.
24
+ #
25
+ # For example:
26
+ #
27
+ # [source,ruby]
28
+ # ----
29
+ # class MyString < String; end
30
+ # strings = [MyString.new('test'), 'test']
31
+ # strings.sort.last.class #=> String
32
+ # strings.max.class #=> MyString
33
+ # ----
34
+ #
35
+ # [source,ruby]
36
+ # ----
37
+ # words = %w(dog horse mouse)
38
+ # words.sort_by { |word| word.length }.last #=> 'mouse'
39
+ # words.max_by { |word| word.length } #=> 'horse'
40
+ # ----
41
+ #
15
42
  # @example
16
43
  # # bad
17
44
  # [2, 1, 3].sort.first
@@ -60,8 +87,8 @@ module RuboCop
60
87
  # @!method redundant_sort?(node)
61
88
  def_node_matcher :redundant_sort?, <<~MATCHER
62
89
  {
63
- (send $(send _ $:sort ...) ${:last :first})
64
- (send $(send _ $:sort ...) ${:[] :at :slice} {(int 0) (int -1)})
90
+ (send $(send _ $:sort) ${:last :first})
91
+ (send $(send _ $:sort) ${:[] :at :slice} {(int 0) (int -1)})
65
92
 
66
93
  (send $(send _ $:sort_by _) ${:last :first})
67
94
  (send $(send _ $:sort_by _) ${:[] :at :slice} {(int 0) (int -1)})
@@ -75,33 +102,39 @@ module RuboCop
75
102
  MATCHER
76
103
 
77
104
  def on_send(node)
78
- if (sort_node, sorter, accessor = redundant_sort?(node.parent))
79
- ancestor = node.parent
80
- elsif (sort_node, sorter, accessor = redundant_sort?(node.parent&.parent))
81
- ancestor = node.parent.parent
82
- else
83
- return
84
- end
105
+ ancestor, sort_node, sorter, accessor =
106
+ find_redundant_sort(node.parent, node.parent&.parent)
107
+ return unless ancestor
85
108
 
86
- message = message(ancestor, sorter, accessor)
109
+ register_offense(ancestor, sort_node, sorter, accessor)
110
+ end
111
+
112
+ private
87
113
 
88
- add_offense(offense_range(sort_node, ancestor), message: message) do |corrector|
89
- autocorrect(corrector, ancestor, sort_node, sorter, accessor)
114
+ def find_redundant_sort(*nodes)
115
+ nodes.each do |node|
116
+ if (sort_node, sorter, accessor = redundant_sort?(node))
117
+ return [node, sort_node, sorter, accessor]
118
+ end
90
119
  end
120
+
121
+ nil
91
122
  end
92
123
 
93
- private
124
+ def register_offense(node, sort_node, sorter, accessor)
125
+ message = message(node, sorter, accessor)
94
126
 
95
- def autocorrect(corrector, node, sort_node, sorter, accessor)
96
- # Remove accessor, e.g. `first` or `[-1]`.
97
- corrector.remove(range_between(accessor_start(node), node.loc.expression.end_pos))
127
+ add_offense(offense_range(sort_node, node), message: message) do |corrector|
128
+ # Remove accessor, e.g. `first` or `[-1]`.
129
+ corrector.remove(range_between(accessor_start(node), node.loc.expression.end_pos))
98
130
 
99
- # Replace "sort" or "sort_by" with the appropriate min/max method.
100
- corrector.replace(sort_node.loc.selector, suggestion(sorter, accessor, arg_value(node)))
131
+ # Replace "sort" or "sort_by" with the appropriate min/max method.
132
+ corrector.replace(sort_node.loc.selector, suggestion(sorter, accessor, arg_value(node)))
133
+ end
101
134
  end
102
135
 
103
- def offense_range(sort_node, ancestor)
104
- range_between(sort_node.loc.selector.begin_pos, ancestor.loc.expression.end_pos)
136
+ def offense_range(sort_node, node)
137
+ range_between(sort_node.loc.selector.begin_pos, node.loc.expression.end_pos)
105
138
  end
106
139
 
107
140
  def message(node, sorter, accessor)
@@ -107,7 +107,7 @@ module RuboCop
107
107
  private
108
108
 
109
109
  def allowed_slash_literal?(node)
110
- style == :slashes && !contains_disallowed_slash?(node) || allowed_mixed_slash?(node)
110
+ (style == :slashes && !contains_disallowed_slash?(node)) || allowed_mixed_slash?(node)
111
111
  end
112
112
 
113
113
  def allowed_mixed_slash?(node)
@@ -115,13 +115,13 @@ module RuboCop
115
115
  end
116
116
 
117
117
  def allowed_percent_r_literal?(node)
118
- style == :slashes && contains_disallowed_slash?(node) ||
118
+ (style == :slashes && contains_disallowed_slash?(node)) ||
119
119
  style == :percent_r ||
120
120
  allowed_mixed_percent_r?(node) || allowed_omit_parentheses_with_percent_r_literal?(node)
121
121
  end
122
122
 
123
123
  def allowed_mixed_percent_r?(node)
124
- style == :mixed && node.multiline? || contains_disallowed_slash?(node)
124
+ (style == :mixed && node.multiline?) || contains_disallowed_slash?(node)
125
125
  end
126
126
 
127
127
  def contains_disallowed_slash?(node)
@@ -74,7 +74,8 @@ module RuboCop
74
74
  end
75
75
 
76
76
  def correct_style?(node)
77
- style == :return && !return_nil_node?(node) || style == :return_nil && !return_node?(node)
77
+ (style == :return && !return_nil_node?(node)) ||
78
+ (style == :return_nil && !return_node?(node))
78
79
  end
79
80
 
80
81
  def scoped_node?(node)
@@ -10,14 +10,25 @@ module RuboCop
10
10
  # need to be changed to use safe navigation. We have limited the cop to
11
11
  # not register an offense for method chains that exceed 2 methods.
12
12
  #
13
- # Configuration option: ConvertCodeThatCanStartToReturnNil
14
- # The default for this is `false`. When configured to `true`, this will
13
+ # The default for `ConvertCodeThatCanStartToReturnNil` is `false`.
14
+ # When configured to `true`, this will
15
15
  # check for code in the format `!foo.nil? && foo.bar`. As it is written,
16
16
  # the return of this code is limited to `false` and whatever the return
17
17
  # of the method is. If this is converted to safe navigation,
18
18
  # `foo&.bar` can start returning `nil` as well as what the method
19
19
  # returns.
20
20
  #
21
+ # @safety
22
+ # Autocorrection is unsafe because if a value is `false`, the resulting
23
+ # code will have different behaviour or raise an error.
24
+ #
25
+ # [source,ruby]
26
+ # ----
27
+ # x = false
28
+ # x && x.foo # return false
29
+ # x&.foo # raises NoMethodError
30
+ # ----
31
+ #
21
32
  # @example
22
33
  # # bad
23
34
  # foo.bar if foo
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop looks for places where an subset of an Enumerable (array,
7
+ # range, set, etc.; see note below) is calculated based on a `Regexp`
8
+ # match, and suggests `grep` or `grep_v` instead.
9
+ #
10
+ # NOTE: Hashes do not behave as you may expect with `grep`, which
11
+ # means that `hash.grep` is not equivalent to `hash.select`. Although
12
+ # RuboCop is limited by static analysis, this cop attempts to avoid
13
+ # registering an offense when the receiver is a hash (hash literal,
14
+ # `Hash.new`, `Hash#[]`, or `to_h`/`to_hash`).
15
+ #
16
+ # NOTE: `grep` and `grep_v` were optimized when used without a block
17
+ # in Ruby 3.0, but may be slower in previous versions.
18
+ # See https://bugs.ruby-lang.org/issues/17030
19
+ #
20
+ # @safety
21
+ # Autocorrection is marked as unsafe because `MatchData` will
22
+ # not be created by `grep`, but may have previously been relied
23
+ # upon after the `match?` or `=~` call.
24
+ #
25
+ # Additionally, the cop cannot guarantee that the receiver of
26
+ # `select` or `reject` is actually an array by static analysis,
27
+ # so the correction may not be actually equivalent.
28
+ #
29
+ # @example
30
+ # # bad (select or find_all)
31
+ # array.select { |x| x.match? /regexp/ }
32
+ # array.select { |x| /regexp/.match?(x) }
33
+ # array.select { |x| x =~ /regexp/ }
34
+ # array.select { |x| /regexp/ =~ x }
35
+ #
36
+ # # bad (reject)
37
+ # array.reject { |x| x.match? /regexp/ }
38
+ # array.reject { |x| /regexp/.match?(x) }
39
+ # array.reject { |x| x =~ /regexp/ }
40
+ # array.reject { |x| /regexp/ =~ x }
41
+ #
42
+ # # good
43
+ # array.grep(regexp)
44
+ # array.grep_v(regexp)
45
+ class SelectByRegexp < Base
46
+ extend AutoCorrector
47
+ include RangeHelp
48
+
49
+ MSG = 'Prefer `%<replacement>s` to `%<original_method>s` with a regexp match.'
50
+ RESTRICT_ON_SEND = %i[select find_all reject].freeze
51
+ REPLACEMENTS = { select: 'grep', find_all: 'grep', reject: 'grep_v' }.freeze
52
+ REGEXP_METHODS = %i[match? =~].to_set.freeze
53
+
54
+ # @!method regexp_match?(node)
55
+ def_node_matcher :regexp_match?, <<~PATTERN
56
+ {
57
+ (block send (args (arg $_)) ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
58
+ (numblock send $1 ${(send _ %REGEXP_METHODS _) match-with-lvasgn})
59
+ }
60
+ PATTERN
61
+
62
+ # Returns true if a node appears to return a hash
63
+ # @!method creates_hash?(node)
64
+ def_node_matcher :creates_hash?, <<~PATTERN
65
+ {
66
+ (send (const _ :Hash) {:new :[]} ...)
67
+ (block (send (const _ :Hash) :new ...) ...)
68
+ (send _ { :to_h :to_hash } ...)
69
+ }
70
+ PATTERN
71
+
72
+ # @!method calls_lvar?(node, name)
73
+ def_node_matcher :calls_lvar?, <<~PATTERN
74
+ {
75
+ (send (lvar %1) ...)
76
+ (send ... (lvar %1))
77
+ (match-with-lvasgn regexp (lvar %1))
78
+ }
79
+ PATTERN
80
+
81
+ def on_send(node)
82
+ return unless (block_node = node.block_node)
83
+ return if block_node.body.begin_type?
84
+ return if receiver_allowed?(block_node.receiver)
85
+ return unless (regexp_method_send_node = extract_send_node(block_node))
86
+ return if match_predicate_without_receiver?(regexp_method_send_node)
87
+
88
+ regexp = find_regexp(regexp_method_send_node, block_node)
89
+ register_offense(node, block_node, regexp)
90
+ end
91
+
92
+ private
93
+
94
+ def receiver_allowed?(node)
95
+ return false unless node
96
+
97
+ node.hash_type? || creates_hash?(node)
98
+ end
99
+
100
+ def register_offense(node, block_node, regexp)
101
+ replacement = REPLACEMENTS[node.method_name.to_sym]
102
+ message = format(MSG, replacement: replacement, original_method: node.method_name)
103
+
104
+ add_offense(block_node, message: message) do |corrector|
105
+ # Only correct if it can be determined what the regexp is
106
+ if regexp
107
+ range = range_between(node.loc.selector.begin_pos, block_node.loc.end.end_pos)
108
+ corrector.replace(range, "#{replacement}(#{regexp.source})")
109
+ end
110
+ end
111
+ end
112
+
113
+ def extract_send_node(block_node)
114
+ return unless (block_arg_name, regexp_method_send_node = regexp_match?(block_node))
115
+
116
+ block_arg_name = :"_#{block_arg_name}" if block_node.numblock_type?
117
+ return unless calls_lvar?(regexp_method_send_node, block_arg_name)
118
+
119
+ regexp_method_send_node
120
+ end
121
+
122
+ def find_regexp(node, block)
123
+ return node.child_nodes.first if node.match_with_lvasgn_type?
124
+
125
+ if node.receiver.lvar_type? &&
126
+ (block.numblock_type? || node.receiver.source == block.arguments.first.source)
127
+ node.first_argument
128
+ elsif node.first_argument.lvar_type?
129
+ node.receiver
130
+ end
131
+ end
132
+
133
+ def match_predicate_without_receiver?(node)
134
+ node.send_type? && node.method?(:match?) && node.receiver.nil?
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -32,38 +32,29 @@ module RuboCop
32
32
 
33
33
  MSG = 'Do not use semicolons to terminate expressions.'
34
34
 
35
+ def self.autocorrect_incompatible_with
36
+ [Style::SingleLineMethods]
37
+ end
38
+
35
39
  def on_new_investigation
36
40
  return if processed_source.blank?
37
41
 
38
- @processed_source = processed_source
39
-
40
42
  check_for_line_terminator_or_opener
41
43
  end
42
44
 
43
- def on_begin(node) # rubocop:todo Metrics/CyclomaticComplexity
45
+ def on_begin(node)
44
46
  return if cop_config['AllowAsExpressionSeparator']
45
47
 
46
48
  exprs = node.children
47
49
 
48
50
  return if exprs.size < 2
49
51
 
50
- # create a map matching lines to the number of expressions on them
51
- exprs_lines = exprs.map(&:first_line)
52
- lines = exprs_lines.group_by(&:itself)
53
-
54
- lines.each do |line, expr_on_line|
52
+ expressions_per_line(exprs).each do |line, expr_on_line|
55
53
  # Every line with more than one expression on it is a
56
54
  # potential offense
57
55
  next unless expr_on_line.size > 1
58
56
 
59
- # TODO: Find the correct position of the semicolon. We don't know
60
- # if the first semicolon on the line is a separator of
61
- # expressions. It's just a guess.
62
- column = @processed_source[line - 1].index(';')
63
-
64
- next unless column
65
-
66
- convention_on(line, column, false)
57
+ find_semicolon_positions(line) { |pos| register_semicolon(line, pos, true) }
67
58
  end
68
59
  end
69
60
 
@@ -71,9 +62,9 @@ module RuboCop
71
62
 
72
63
  def check_for_line_terminator_or_opener
73
64
  # Make the obvious check first
74
- return unless @processed_source.raw_source.include?(';')
65
+ return unless processed_source.raw_source.include?(';')
75
66
 
76
- each_semicolon { |line, column| convention_on(line, column, true) }
67
+ each_semicolon { |line, column| register_semicolon(line, column, false) }
77
68
  end
78
69
 
79
70
  def each_semicolon
@@ -84,15 +75,32 @@ module RuboCop
84
75
  end
85
76
 
86
77
  def tokens_for_lines
87
- @processed_source.tokens.group_by(&:line)
78
+ processed_source.tokens.group_by(&:line)
88
79
  end
89
80
 
90
- def convention_on(line, column, autocorrect)
91
- range = source_range(@processed_source.buffer, line, column)
92
- # Don't attempt to autocorrect if semicolon is separating statements
93
- # on the same line
81
+ def register_semicolon(line, column, after_expression)
82
+ range = source_range(processed_source.buffer, line, column)
83
+
94
84
  add_offense(range) do |corrector|
95
- corrector.remove(range) if autocorrect
85
+ if after_expression
86
+ corrector.replace(range, "\n")
87
+ else
88
+ corrector.remove(range)
89
+ end
90
+ end
91
+ end
92
+
93
+ def expressions_per_line(exprs)
94
+ # create a map matching lines to the number of expressions on them
95
+ exprs_lines = exprs.map(&:first_line)
96
+ exprs_lines.group_by(&:itself)
97
+ end
98
+
99
+ def find_semicolon_positions(line)
100
+ # Scan for all the semicolons on the line
101
+ semicolons = processed_source[line - 1].enum_for(:scan, ';')
102
+ semicolons.each do
103
+ yield Regexp.last_match.begin(0)
96
104
  end
97
105
  end
98
106
  end
@@ -6,6 +6,11 @@ module RuboCop
6
6
  # Sometimes using dig method ends up with just a single
7
7
  # argument. In such cases, dig should be replaced with [].
8
8
  #
9
+ # @safety
10
+ # This cop is unsafe because it cannot be guaranteed that the receiver
11
+ # is an `Enumerable` or does not have a nonstandard implementation
12
+ # of `dig`.
13
+ #
9
14
  # @example
10
15
  # # bad
11
16
  # { key: 'value' }.dig(:key)
@@ -109,7 +109,9 @@ module RuboCop
109
109
  # we remove any leading underscores before comparing.
110
110
  actual_args_no_underscores = actual_args.map { |arg| arg.to_s.sub(/^_+/, '') }
111
111
 
112
- actual_args_no_underscores == target_args(method_name)
112
+ # Allow the arguments if the names match but not all are given
113
+ expected_args = target_args(method_name).first(actual_args_no_underscores.size)
114
+ actual_args_no_underscores == expected_args
113
115
  end
114
116
  end
115
117
  end
@@ -36,6 +36,7 @@ module RuboCop
36
36
  extend AutoCorrector
37
37
 
38
38
  MSG = 'Avoid single-line method definitions.'
39
+ NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES = %i[return break next].freeze
39
40
 
40
41
  def on_def(node)
41
42
  return unless node.single_line?
@@ -62,29 +63,24 @@ module RuboCop
62
63
 
63
64
  def correct_to_endless?(body_node)
64
65
  return false if target_ruby_version < 3.0
65
-
66
- endless_method_config = config.for_cop('Style/EndlessMethod')
67
-
68
- return false unless endless_method_config['Enabled']
69
- return false if endless_method_config['EnforcedStyle'] == 'disallow'
66
+ return false if disallow_endless_method_style?
70
67
  return false unless body_node
71
- return false if body_node.parent.assignment_method?
68
+ return false if body_node.parent.assignment_method? ||
69
+ NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
72
70
 
73
71
  !(body_node.begin_type? || body_node.kwbegin_type?)
74
72
  end
75
73
 
76
74
  def correct_to_multiline(corrector, node)
77
- each_part(node.body) do |part|
78
- LineBreakCorrector.break_line_before(
79
- range: part, node: node, corrector: corrector,
80
- configured_width: configured_indentation_width
81
- )
75
+ if (body = node.body) && body.begin_type? && body.parenthesized_call?
76
+ break_line_before(corrector, node, body)
77
+ else
78
+ each_part(body) do |part|
79
+ break_line_before(corrector, node, part)
80
+ end
82
81
  end
83
82
 
84
- LineBreakCorrector.break_line_before(
85
- range: node.loc.end, node: node, corrector: corrector,
86
- indent_steps: 0, configured_width: configured_indentation_width
87
- )
83
+ break_line_before(corrector, node, node.loc.end, indent_steps: 0)
88
84
 
89
85
  move_comment(node, corrector)
90
86
  end
@@ -98,6 +94,13 @@ module RuboCop
98
94
  corrector.replace(node, replacement)
99
95
  end
100
96
 
97
+ def break_line_before(corrector, node, range, indent_steps: 1)
98
+ LineBreakCorrector.break_line_before(
99
+ range: range, node: node, corrector: corrector,
100
+ configured_width: configured_indentation_width, indent_steps: indent_steps
101
+ )
102
+ end
103
+
101
104
  def each_part(body)
102
105
  return unless body
103
106
 
@@ -129,6 +132,13 @@ module RuboCop
129
132
  def require_parentheses?(method_body)
130
133
  method_body.send_type? && !method_body.arguments.empty? && !method_body.comparison_method?
131
134
  end
135
+
136
+ def disallow_endless_method_style?
137
+ endless_method_config = config.for_cop('Style/EndlessMethod')
138
+ return false unless endless_method_config['Enabled']
139
+
140
+ endless_method_config['EnforcedStyle'] == 'disallow'
141
+ end
132
142
  end
133
143
  end
134
144
  end
@@ -6,6 +6,19 @@ module RuboCop
6
6
  # This cop checks that arrays are sliced with endless ranges instead of
7
7
  # `ary[start..-1]` on Ruby 2.6+.
8
8
  #
9
+ # @safety
10
+ # This cop is unsafe because `x..-1` and `x..` are only guaranteed to
11
+ # be equivalent for `Array#[]`, and the cop cannot determine what class
12
+ # the receiver is.
13
+ #
14
+ # For example:
15
+ # [source,ruby]
16
+ # ----
17
+ # sum = proc { |ary| ary.sum }
18
+ # sum[-3..-1] # => -6
19
+ # sum[-3..] # Hangs forever
20
+ # ----
21
+ #
9
22
  # @example
10
23
  # # bad
11
24
  # items[1..-1]
@@ -38,6 +38,10 @@ module RuboCop
38
38
 
39
39
  MSG = 'Consider merging nested conditions into outer `%<conditional_type>s` conditions.'
40
40
 
41
+ def self.autocorrect_incompatible_with
42
+ [Style::NegatedIf, Style::NegatedUnless]
43
+ end
44
+
41
45
  def on_if(node)
42
46
  return if node.ternary? || node.else? || node.elsif?
43
47