rubocop 1.87.0 → 1.88.2

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +80 -72
  3. data/config/obsoletion.yml +21 -1
  4. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -0
  5. data/lib/rubocop/cop/base.rb +30 -10
  6. data/lib/rubocop/cop/bundler/gem_comment.rb +5 -3
  7. data/lib/rubocop/cop/commissioner.rb +13 -11
  8. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  9. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
  10. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
  11. data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
  12. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
  13. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
  14. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +5 -3
  15. data/lib/rubocop/cop/layout/block_alignment.rb +58 -4
  16. data/lib/rubocop/cop/layout/class_structure.rb +7 -3
  17. data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
  18. data/lib/rubocop/cop/layout/condition_position.rb +13 -3
  19. data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
  20. data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
  21. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
  22. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
  23. data/lib/rubocop/cop/layout/indentation_width.rb +28 -0
  24. data/lib/rubocop/cop/layout/line_length.rb +10 -5
  25. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +6 -1
  26. data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
  27. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +1 -11
  28. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +1 -10
  29. data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
  30. data/lib/rubocop/cop/lint/circular_argument_reference.rb +1 -3
  31. data/lib/rubocop/cop/lint/debugger.rb +13 -2
  32. data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -7
  33. data/lib/rubocop/cop/lint/empty_block.rb +3 -3
  34. data/lib/rubocop/cop/lint/ensure_return.rb +19 -1
  35. data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -1
  36. data/lib/rubocop/cop/lint/float_comparison.rb +1 -0
  37. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +5 -1
  38. data/lib/rubocop/cop/lint/interpolation_check.rb +18 -3
  39. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +1 -1
  40. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +11 -1
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -11
  42. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +4 -4
  43. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +16 -0
  44. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
  45. data/lib/rubocop/cop/lint/number_conversion.rb +13 -4
  46. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +10 -0
  47. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +7 -7
  48. data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
  49. data/lib/rubocop/cop/lint/rand_one.rb +1 -1
  50. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +4 -1
  51. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +4 -1
  52. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +15 -4
  53. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -7
  54. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +4 -0
  55. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +7 -0
  56. data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -1
  57. data/lib/rubocop/cop/lint/redundant_with_object.rb +5 -0
  58. data/lib/rubocop/cop/lint/refinement_import_methods.rb +8 -1
  59. data/lib/rubocop/cop/lint/regexp_as_condition.rb +9 -1
  60. data/lib/rubocop/cop/lint/require_parentheses.rb +13 -4
  61. data/lib/rubocop/cop/lint/require_range_parentheses.rb +2 -1
  62. data/lib/rubocop/cop/lint/require_relative_self_path.rb +5 -5
  63. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  64. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -0
  65. data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +1 -1
  66. data/lib/rubocop/cop/lint/script_permission.rb +5 -1
  67. data/lib/rubocop/cop/lint/self_assignment.rb +24 -1
  68. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -1
  69. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +14 -0
  70. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -1
  71. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +12 -0
  72. data/lib/rubocop/cop/lint/symbol_conversion.rb +21 -4
  73. data/lib/rubocop/cop/lint/to_enum_arguments.rb +35 -2
  74. data/lib/rubocop/cop/lint/to_json.rb +8 -1
  75. data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
  76. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +4 -1
  77. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +35 -9
  78. data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +10 -5
  80. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
  81. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +7 -3
  82. data/lib/rubocop/cop/lint/useless_setter_call.rb +4 -1
  83. data/lib/rubocop/cop/lint/useless_times.rb +22 -1
  84. data/lib/rubocop/cop/lint/void.rb +2 -2
  85. data/lib/rubocop/cop/metrics/collection_literal_length.rb +1 -1
  86. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  87. data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
  88. data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
  89. data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
  90. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
  91. data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
  92. data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
  93. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  94. data/lib/rubocop/cop/naming/file_name.rb +4 -3
  95. data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
  96. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
  97. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
  98. data/lib/rubocop/cop/security/io_methods.rb +1 -1
  99. data/lib/rubocop/cop/security/marshal_load.rb +1 -1
  100. data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
  101. data/lib/rubocop/cop/style/alias.rb +1 -1
  102. data/lib/rubocop/cop/style/and_or.rb +1 -1
  103. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
  104. data/lib/rubocop/cop/style/array_first_last.rb +12 -1
  105. data/lib/rubocop/cop/style/array_intersect.rb +8 -4
  106. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +3 -0
  107. data/lib/rubocop/cop/style/block_delimiters.rb +16 -2
  108. data/lib/rubocop/cop/style/case_equality.rb +14 -2
  109. data/lib/rubocop/cop/style/class_equality_comparison.rb +21 -13
  110. data/lib/rubocop/cop/style/class_methods_definitions.rb +11 -5
  111. data/lib/rubocop/cop/style/collection_compact.rb +1 -1
  112. data/lib/rubocop/cop/style/colon_method_call.rb +13 -6
  113. data/lib/rubocop/cop/style/combinable_loops.rb +5 -0
  114. data/lib/rubocop/cop/style/comparable_clamp.rb +12 -1
  115. data/lib/rubocop/cop/style/concat_array_literals.rb +5 -1
  116. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -1
  117. data/lib/rubocop/cop/style/constant_visibility.rb +4 -1
  118. data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
  119. data/lib/rubocop/cop/style/date_time.rb +2 -2
  120. data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
  121. data/lib/rubocop/cop/style/dig_chain.rb +5 -0
  122. data/lib/rubocop/cop/style/dir_empty.rb +4 -0
  123. data/lib/rubocop/cop/style/documentation_method.rb +5 -1
  124. data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
  125. data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
  126. data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
  127. data/lib/rubocop/cop/style/empty_literal.rb +7 -2
  128. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
  129. data/lib/rubocop/cop/style/env_home.rb +4 -0
  130. data/lib/rubocop/cop/style/even_odd.rb +11 -1
  131. data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
  132. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  133. data/lib/rubocop/cop/style/file_null.rb +4 -2
  134. data/lib/rubocop/cop/style/file_write.rb +17 -14
  135. data/lib/rubocop/cop/style/format_string.rb +13 -1
  136. data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
  137. data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
  138. data/lib/rubocop/cop/style/hash_slice.rb +16 -0
  139. data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
  140. data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
  141. data/lib/rubocop/cop/style/if_unless_modifier.rb +5 -5
  142. data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
  143. data/lib/rubocop/cop/style/inline_comment.rb +1 -1
  144. data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
  145. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
  146. data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
  147. data/lib/rubocop/cop/style/lambda.rb +7 -1
  148. data/lib/rubocop/cop/style/lambda_call.rb +11 -0
  149. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
  150. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  151. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -5
  152. data/lib/rubocop/cop/style/method_def_parentheses.rb +5 -1
  153. data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
  154. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +10 -7
  155. data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
  156. data/lib/rubocop/cop/style/module_function.rb +4 -2
  157. data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
  158. data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
  159. data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
  160. data/lib/rubocop/cop/style/mutable_constant.rb +105 -11
  161. data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
  162. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  163. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  164. data/lib/rubocop/cop/style/option_hash.rb +1 -1
  165. data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
  166. data/lib/rubocop/cop/style/parallel_assignment.rb +19 -3
  167. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
  168. data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
  169. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
  170. data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
  171. data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
  172. data/lib/rubocop/cop/style/redundant_format.rb +29 -0
  173. data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
  174. data/lib/rubocop/cop/style/redundant_line_continuation.rb +11 -3
  175. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
  176. data/lib/rubocop/cop/style/redundant_self.rb +9 -0
  177. data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
  178. data/lib/rubocop/cop/style/semicolon.rb +20 -5
  179. data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
  180. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  181. data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
  182. data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
  183. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
  184. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  185. data/lib/rubocop/cop/style/while_until_do.rb +7 -0
  186. data/lib/rubocop/cop/style/word_array.rb +1 -0
  187. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -3
  188. data/lib/rubocop/cop/team.rb +18 -4
  189. data/lib/rubocop/formatter/disabled_config_formatter.rb +14 -7
  190. data/lib/rubocop/runner.rb +55 -13
  191. data/lib/rubocop/server/core.rb +6 -0
  192. data/lib/rubocop/version.rb +1 -1
  193. metadata +3 -3
@@ -36,31 +36,31 @@ module RuboCop
36
36
  RESTRICT_ON_SEND = %i[+ - * / **].freeze
37
37
 
38
38
  # @!method useless_operation?(node)
39
- def_node_matcher :useless_operation?, '(call (call nil? $_) $_ (int $_))'
39
+ def_node_matcher :useless_operation?, <<~PATTERN
40
+ (call ${lvar ivar cvar gvar const (send nil? _)} $_ (int $_))
41
+ PATTERN
40
42
 
41
43
  # @!method useless_abbreviated_assignment?(node)
42
- def_node_matcher :useless_abbreviated_assignment?, '(op-asgn (lvasgn $_) $_ (int $_))'
44
+ def_node_matcher :useless_abbreviated_assignment?, <<~PATTERN
45
+ (op-asgn ${lvasgn ivasgn cvasgn gvasgn casgn} $_ (int $_))
46
+ PATTERN
43
47
 
44
48
  def on_send(node)
45
- return unless useless_operation?(node)
46
-
47
- variable, operation, number = useless_operation?(node)
49
+ return unless (receiver, operation, number = useless_operation?(node))
48
50
  return unless useless?(operation, number)
49
51
 
50
52
  add_offense(node) do |corrector|
51
- corrector.replace(node, variable)
53
+ corrector.replace(node, receiver.source)
52
54
  end
53
55
  end
54
56
  alias on_csend on_send
55
57
 
56
58
  def on_op_asgn(node)
57
- return unless useless_abbreviated_assignment?(node)
58
-
59
- variable, operation, number = useless_abbreviated_assignment?(node)
59
+ return unless (variable, operation, number = useless_abbreviated_assignment?(node))
60
60
  return unless useless?(operation, number)
61
61
 
62
62
  add_offense(node) do |corrector|
63
- corrector.replace(node, "#{variable} = #{variable}")
63
+ corrector.replace(node, "#{variable.source} = #{variable.source}")
64
64
  end
65
65
  end
66
66
 
@@ -107,11 +107,15 @@ module RuboCop
107
107
  end
108
108
 
109
109
  def find_method_definition(node, method_name)
110
- node.each_ancestor.lazy.map do |ancestor|
111
- ancestor.each_child_node(:def, :any_block).find do |child|
110
+ node.each_ancestor do |ancestor|
111
+ found = ancestor.each_child_node(:def, :any_block).find do |child|
112
112
  method_definition(child, method_name)
113
113
  end
114
- end.find(&:itself)
114
+ return found if found
115
+ # A method defined in an outer lexical scope does not define this scope's method,
116
+ # so stop searching once a class/module boundary is crossed without a match.
117
+ return nil if ancestor.type?(:class, :module, :sclass)
118
+ end
115
119
  end
116
120
 
117
121
  # `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
@@ -107,7 +107,10 @@ module RuboCop
107
107
  end
108
108
 
109
109
  def process_multiple_assignment(masgn_node)
110
- masgn_node.assignments.each_with_index do |lhs_node, index|
110
+ # Iterate the top-level destructuring slots so each maps to the right-hand side
111
+ # element at the same position. Using the flattened `assignments` would misalign
112
+ # the index when a slot is itself a nested destructuring (e.g. `(a, b), c = x, y`).
113
+ masgn_node.lhs.children.each_with_index do |lhs_node, index|
111
114
  next unless ASSIGNMENT_TYPES.include?(lhs_node.type)
112
115
 
113
116
  if masgn_node.rhs.array_type? && (rhs_node = masgn_node.rhs.children[index])
@@ -83,7 +83,7 @@ module RuboCop
83
83
 
84
84
  def autocorrect_block(corrector, node)
85
85
  block_arg = block_arg(node)
86
- return if block_reassigns_arg?(node, block_arg)
86
+ return unless reducible_to_body?(node, block_arg)
87
87
 
88
88
  source = node.body.source
89
89
  source.gsub!(/\b#{block_arg}\b/, '0') if block_arg
@@ -91,6 +91,27 @@ module RuboCop
91
91
  corrector.replace(node, fix_indentation(source, node.loc.column...node.body.loc.column))
92
92
  end
93
93
 
94
+ def reducible_to_body?(node, block_arg)
95
+ # A block with multiple arguments can't be reduced to its body (the extra arguments
96
+ # would become undefined references), and `next`/`break`/`redo` bound to the block
97
+ # become orphaned (a syntax error) once the block is removed.
98
+ return false if node.arguments.size > 1 || orphans_loop_control_keyword?(node)
99
+
100
+ # A lone non-simple argument (destructuring `|(a, b)|` or a splat `|*a|`) can't be
101
+ # substituted either, so reducing to the body would leave it referencing an
102
+ # undefined variable.
103
+ return false if node.arguments.one? && block_arg.nil?
104
+
105
+ !block_reassigns_arg?(node, block_arg)
106
+ end
107
+
108
+ def orphans_loop_control_keyword?(node)
109
+ node.body&.each_node(:next, :break, :redo)&.any? do |control|
110
+ inner = control.each_ancestor.take_while { |ancestor| !ancestor.equal?(node) }
111
+ inner.none? { |ancestor| ancestor.type?(:any_block, :while, :until, :for) }
112
+ end
113
+ end
114
+
94
115
  def fix_indentation(source, range)
95
116
  # Cleanup indentation in a multiline block
96
117
  source_lines = source.split("\n")
@@ -212,7 +212,7 @@ module RuboCop
212
212
  end
213
213
 
214
214
  def check_nonmutating(node)
215
- return unless node.type?(:send, :any_block)
215
+ return unless node.type?(:call, :any_block)
216
216
 
217
217
  method_name = node.method_name
218
218
  return unless NONMUTATING_METHODS.include?(method_name)
@@ -265,7 +265,7 @@ module RuboCop
265
265
  end
266
266
 
267
267
  def autocorrect_nonmutating_send(corrector, node, suggestion)
268
- send_node = if node.send_type?
268
+ send_node = if node.call_type?
269
269
  node
270
270
  else
271
271
  node.send_node
@@ -75,7 +75,7 @@ module RuboCop
75
75
  private
76
76
 
77
77
  def collection_threshold
78
- cop_config.fetch('LengthThreshold', Float::INFINITY)
78
+ cop_config.fetch('Max', Float::INFINITY)
79
79
  end
80
80
  end
81
81
  end
@@ -58,7 +58,7 @@ module RuboCop
58
58
  return unless node.method?(:define_method)
59
59
 
60
60
  method_name = node.send_node.first_argument
61
- return if method_name.basic_literal? && allowed?(method_name.value)
61
+ return if method_name&.basic_literal? && allowed?(method_name.value)
62
62
 
63
63
  check_code_length(node)
64
64
  end
@@ -12,9 +12,15 @@ module RuboCop
12
12
  # nodes count. In contrast to the CyclomaticComplexity cop, this cop
13
13
  # considers `else` nodes as adding complexity.
14
14
  #
15
+ # A `case`/`in` branch whose pattern is a simple literal (e.g. `in 1`, `in "red"`, `in 1..10`)
16
+ # or a constant/type (e.g. `in Integer`) and has no guard is just as easy to read as a `when`
17
+ # branch, so it is discounted the same way. Branches with structural patterns (e.g. array,
18
+ # hash, or find patterns), bindings, alternatives, or a guard add the full complexity of
19
+ # a decision point.
20
+ #
15
21
  # @example
16
22
  #
17
- # def my_method # 1
23
+ # def example_1 # 1
18
24
  # if cond # 1
19
25
  # case var # 2 (0.8 + 4 * 0.2, rounded)
20
26
  # when 1 then func_one
@@ -26,33 +32,58 @@ module RuboCop
26
32
  # do_something until a && b # 2
27
33
  # end # ===
28
34
  # end # 7 complexity points
35
+ #
36
+ # def example_2 # 1
37
+ # case color # 1 (3 * 0.2, rounded)
38
+ # in "red" then func_red
39
+ # in "blue" then func_blue
40
+ # in "green" then func_green
41
+ # end # ===
42
+ # end # 2 complexity points
29
43
  class PerceivedComplexity < CyclomaticComplexity
30
44
  MSG = 'Perceived complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
31
45
 
32
- COUNTED_NODES = (CyclomaticComplexity::COUNTED_NODES - [:when] + [:case]).freeze
46
+ COUNTED_NODES = (
47
+ CyclomaticComplexity::COUNTED_NODES - %i[when in_pattern] + %i[case case_match]
48
+ ).freeze
33
49
 
34
50
  private
35
51
 
52
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
36
53
  def complexity_score_for(node)
37
54
  case node.type
38
55
  when :case
39
- # If cond is nil, that means each when has an expression that
40
- # evaluates to true or false. It's just an alternative to
41
- # if/elsif/elsif... so the when nodes count.
56
+ # If cond is nil, that means each when has an expression that evaluates to true or
57
+ # false. It's just an alternative to if/elsif/elsif... so the when nodes count.
42
58
  nb_branches = node.when_branches.length + (node.else_branch ? 1 : 0)
43
59
  if node.condition.nil?
44
60
  nb_branches
45
61
  else
46
- # Otherwise, the case node gets 0.8 complexity points and each
47
- # when gets 0.2.
62
+ # Otherwise, the case node gets 0.8 complexity points and each when gets 0.2.
48
63
  ((nb_branches * 0.2) + 0.8).round
49
64
  end
65
+ when :case_match
66
+ # Simple `in` branches are discounted like `when`, while structural patterns keep
67
+ # the full complexity of a decision point.
68
+ score = node.in_pattern_branches.sum { |branch| simple_in_pattern?(branch) ? 0.2 : 1 }
69
+ score += 0.2 if node.else_branch
70
+ score.round
50
71
  when :if
51
72
  node.else? && !node.elsif? ? 2 : 1
52
73
  else
53
74
  super
54
75
  end
55
76
  end
77
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
78
+
79
+ def simple_in_pattern?(in_pattern_node)
80
+ # `in_pattern_node.children[1]` is the guard (`if`/`unless`), or `nil`.
81
+ return false unless in_pattern_node.children[1].nil?
82
+
83
+ # A scalar literal, a literal range, or a constant/type is as easy to read as a `when`.
84
+ pattern = in_pattern_node.pattern
85
+ pattern.literal? || pattern.const_type?
86
+ end
56
87
  end
57
88
  end
58
89
  end
@@ -25,11 +25,11 @@ module RuboCop
25
25
 
26
26
  # @api public
27
27
  def allowed_methods
28
- if cop_config_deprecated_values.any?(Regexp)
29
- cop_config_allowed_methods
30
- else
31
- cop_config_allowed_methods + cop_config_deprecated_values
32
- end
28
+ @allowed_methods ||= if cop_config_deprecated_values.any?(Regexp)
29
+ cop_config_allowed_methods
30
+ else
31
+ cop_config_allowed_methods + cop_config_deprecated_values
32
+ end
33
33
  end
34
34
 
35
35
  def cop_config_allowed_methods
@@ -27,7 +27,7 @@ module RuboCop
27
27
  end
28
28
 
29
29
  def matches_allowed_pattern?(line)
30
- allowed_patterns.any? { |pattern| Regexp.new(pattern).match?(line) }
30
+ allowed_pattern_regexps.any? { |pattern| pattern.match?(line) }
31
31
  end
32
32
 
33
33
  # @deprecated Use matches_allowed_pattern? instead
@@ -49,6 +49,10 @@ module RuboCop
49
49
  end
50
50
  end
51
51
 
52
+ def allowed_pattern_regexps
53
+ @allowed_pattern_regexps ||= allowed_patterns.map { |pattern| Regexp.new(pattern) }
54
+ end
55
+
52
56
  def cop_config_patterns_values
53
57
  @cop_config_patterns_values ||=
54
58
  Array(cop_config.fetch('AllowedPatterns', [])) +
@@ -5,12 +5,16 @@ module RuboCop
5
5
  # This module encapsulates the ability to forbid certain patterns in a cop.
6
6
  module ForbiddenPattern
7
7
  def forbidden_pattern?(name)
8
- forbidden_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
8
+ forbidden_pattern_regexps.any? { |pattern| pattern.match?(name) }
9
9
  end
10
10
 
11
11
  def forbidden_patterns
12
12
  cop_config.fetch('ForbiddenPatterns', [])
13
13
  end
14
+
15
+ def forbidden_pattern_regexps
16
+ @forbidden_pattern_regexps ||= forbidden_patterns.map { |pattern| Regexp.new(pattern) }
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -182,10 +182,18 @@ module RuboCop
182
182
  return ":\"#{value.source}\"" if value.dsym_type?
183
183
  return "\"#{value.source}\"" if value.dstr_type?
184
184
  return ":#{value.source}" if value.sym_type?
185
+ # The element of a `%w` array can contain characters that are special
186
+ # inside a single-quoted string (e.g. a `'`), so escape them rather than
187
+ # wrapping the raw source.
188
+ return to_single_quoted(value.value) if value.str_type?
185
189
 
186
190
  "'#{value.source}'"
187
191
  end
188
192
 
193
+ def to_single_quoted(string)
194
+ "'#{string.gsub(/['\\]/) { |character| "\\#{character}" }}'"
195
+ end
196
+
189
197
  def except_key(node)
190
198
  key_arg = node.argument_list.first.source
191
199
  body, = extract_body_if_negated(node.body)
@@ -94,6 +94,10 @@ module RuboCop
94
94
 
95
95
  return unless captures.use_transformed_argname?
96
96
 
97
+ # A splat transforming expression (e.g. `[k, *v]`) can't be used as a
98
+ # standalone block return value, so the rewrite would produce invalid Ruby.
99
+ return if captures.transforming_body_expr.splat_type?
100
+
97
101
  message = "Prefer `#{new_method_name}` over `#{match_desc}`."
98
102
  add_offense(node, message: message) do |corrector|
99
103
  correction = prepare_correction(node)
@@ -75,7 +75,7 @@ module RuboCop
75
75
  end
76
76
 
77
77
  def first_line_comment(node)
78
- comment = processed_source.comments.find { |c| same_line?(c, node) }
78
+ comment = processed_source.comment_at_line(node.first_line)
79
79
  return unless comment
80
80
 
81
81
  comment_source = comment.source
@@ -203,9 +203,10 @@ module RuboCop
203
203
 
204
204
  def match_acronym?(expected, name)
205
205
  expected = expected.to_s
206
- name = name.to_s
207
-
208
- allowed_acronyms.any? { |acronym| expected.gsub(acronym.capitalize, acronym) == name }
206
+ name = allowed_acronyms.reduce(name.to_s) do |result, acronym|
207
+ result.gsub(acronym, acronym.capitalize)
208
+ end
209
+ expected == name
209
210
  end
210
211
 
211
212
  def to_namespace(path) # rubocop:disable Metrics/AbcSize
@@ -91,6 +91,8 @@ module RuboCop
91
91
  end
92
92
 
93
93
  def on_new_investigation
94
+ return unless @flagged_terms_regex
95
+
94
96
  investigate_filepath if cop_config['CheckFilepaths']
95
97
  investigate_tokens
96
98
  end
@@ -144,7 +146,7 @@ module RuboCop
144
146
  def preprocess_flagged_terms
145
147
  allowed_strings = []
146
148
  flagged_term_strings = []
147
- cop_config['FlaggedTerms'].each do |term, term_definition|
149
+ (cop_config['FlaggedTerms'] || {}).each do |term, term_definition|
148
150
  next if term_definition.nil?
149
151
 
150
152
  allowed_strings.concat(process_allowed_regex(term_definition['AllowedRegex']))
@@ -181,7 +183,11 @@ module RuboCop
181
183
  end
182
184
 
183
185
  def set_regexes(flagged_term_strings, allowed_strings)
184
- @flagged_terms_regex = array_to_ignorecase_regex(flagged_term_strings)
186
+ # With no flagged terms an empty regex would match everything, so leave the
187
+ # regex nil and let `on_new_investigation` treat the cop as a no-op.
188
+ unless flagged_term_strings.empty?
189
+ @flagged_terms_regex = array_to_ignorecase_regex(flagged_term_strings)
190
+ end
185
191
  @allowed_regex = array_to_ignorecase_regex(allowed_strings) unless allowed_strings.empty?
186
192
  end
187
193
 
@@ -174,6 +174,7 @@ module RuboCop
174
174
 
175
175
  method_node, method_name = find_definition(node)
176
176
  return unless method_node
177
+ return unless nameable_method?(method_name)
177
178
 
178
179
  body = method_node.body
179
180
  return unless body == node || body.children.last == node
@@ -209,6 +210,7 @@ module RuboCop
209
210
 
210
211
  method_node, method_name = find_definition(node)
211
212
  return false unless method_node
213
+ return false unless nameable_method?(method_name)
212
214
 
213
215
  defined_memoized?(method_node.body, arg.name) do |defined_ivar, return_ivar, ivar_assign|
214
216
  return false if matches?(method_name, ivar_assign)
@@ -250,6 +252,13 @@ module RuboCop
250
252
  nil
251
253
  end
252
254
 
255
+ # Operator and other non-word method names (e.g. `[]`, `+`, `<=>`) cannot form a
256
+ # valid instance variable name, so there is no matching ivar to enforce and a
257
+ # suggested correction like `@[]` would be invalid Ruby.
258
+ def nameable_method?(method_name)
259
+ /\A[a-zA-Z_]\w*\z/.match?(method_name.to_s.delete('!?='))
260
+ end
261
+
253
262
  def matches?(method_name, ivar_assign)
254
263
  return true if ivar_assign.nil? || INITIALIZE_METHODS.include?(method_name)
255
264
 
@@ -95,11 +95,14 @@ module RuboCop
95
95
 
96
96
  def autocorrect(corrector, node, range, offending_name, preferred_name)
97
97
  corrector.replace(range, preferred_name)
98
- correct_node(corrector, node.body, offending_name, preferred_name)
98
+ # Once the exception variable is reassigned, later references point to a
99
+ # different value, so stop correcting after the reassignment - both in the
100
+ # body and in the code following the `begin`/`rescue`.
101
+ return if correct_node(corrector, node.body, offending_name, preferred_name)
99
102
  return unless (kwbegin_node = node.parent.each_ancestor(:kwbegin).first)
100
103
 
101
104
  kwbegin_node.right_siblings.each do |child_node|
102
- correct_node(corrector, child_node, offending_name, preferred_name)
105
+ break if correct_node(corrector, child_node, offending_name, preferred_name)
103
106
  end
104
107
  end
105
108
 
@@ -113,6 +116,8 @@ module RuboCop
113
116
  end
114
117
  end
115
118
 
119
+ # Returns the reassignment node once the exception variable is reassigned (a truthy
120
+ # signal to stop correcting later references), or `nil` when no reassignment is found.
116
121
  # rubocop:disable Metrics/MethodLength
117
122
  def correct_node(corrector, node, offending_name, preferred_name)
118
123
  return unless node
@@ -131,9 +136,10 @@ module RuboCop
131
136
 
132
137
  if child_node.type?(:masgn, :lvasgn)
133
138
  correct_reassignment(corrector, child_node, offending_name, preferred_name)
134
- break
139
+ return child_node
135
140
  end
136
141
  end
142
+ nil
137
143
  end
138
144
  # rubocop:enable Metrics/MethodLength
139
145
 
@@ -37,7 +37,7 @@ module RuboCop
37
37
  return unless (receiver = node.receiver) && receiver.source == 'IO'
38
38
 
39
39
  argument = node.first_argument
40
- return if argument.respond_to?(:value) && argument.value.strip.start_with?('|')
40
+ return if argument&.str_type? && argument.value.strip.start_with?('|')
41
41
 
42
42
  add_offense(node, message: format(MSG, method_name: node.method_name)) do |corrector|
43
43
  corrector.replace(receiver, 'File')
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # @!method marshal_load(node)
26
26
  def_node_matcher :marshal_load, <<~PATTERN
27
27
  (send (const {nil? cbase} :Marshal) ${:load :restore}
28
- !(send (const {nil? cbase} :Marshal) :dump ...))
28
+ !(send (const {nil? cbase} :Marshal) :dump ...) _?)
29
29
  PATTERN
30
30
 
31
31
  def on_send(node)
@@ -203,13 +203,23 @@ module RuboCop
203
203
  end
204
204
 
205
205
  def range_with_trailing_argument_comment(node)
206
- comment = processed_source.ast_with_comments[node.last_argument].last
206
+ comment = trailing_argument_comment(node)
207
207
  if comment
208
208
  add_range(node.source_range, comment.source_range)
209
209
  else
210
210
  node
211
211
  end
212
212
  end
213
+
214
+ # For a single-line declaration the parser associates the trailing
215
+ # comment with the first argument, not `last_argument`, so look through
216
+ # all arguments for a comment that trails the whole node.
217
+ def trailing_argument_comment(node)
218
+ comments = node.arguments.filter_map do |argument|
219
+ processed_source.ast_with_comments[argument].last
220
+ end
221
+ comments.find { |comment| comment.source_range.begin_pos >= node.source_range.end_pos }
222
+ end
213
223
  end
214
224
  end
215
225
  end
@@ -113,7 +113,7 @@ module RuboCop
113
113
  return :lexical
114
114
  when :def, :defs
115
115
  return :dynamic
116
- when :block
116
+ when :block, :numblock, :itblock
117
117
  return :instance_eval if parent.method?(:instance_eval)
118
118
 
119
119
  return :dynamic
@@ -71,7 +71,7 @@ module RuboCop
71
71
  node.each_child_node do |expr|
72
72
  if expr.send_type?
73
73
  correct_send(expr, corrector)
74
- elsif expr.return_type? || expr.assignment?
74
+ elsif expr.type?(:return, :next, :break, :yield) || expr.assignment?
75
75
  correct_other(expr, corrector)
76
76
  end
77
77
  end
@@ -153,7 +153,7 @@ module RuboCop
153
153
  BLOCK_MSG = 'Use anonymous block arguments forwarding (`&`).'
154
154
 
155
155
  def self.autocorrect_incompatible_with
156
- [Naming::BlockForwarding]
156
+ [Naming::BlockForwarding, Style::MethodDefParentheses]
157
157
  end
158
158
 
159
159
  def on_def(node)
@@ -40,8 +40,9 @@ module RuboCop
40
40
 
41
41
  node = innermost_braces_node(node)
42
42
  return if node.parent && brace_method?(node.parent)
43
+ return if compound_assignment_target?(node)
43
44
 
44
- preferred = (value.zero? ? 'first' : 'last')
45
+ preferred = preferred_name(value)
45
46
  offense_range = find_offense_range(node)
46
47
 
47
48
  add_offense(offense_range, message: format(MSG, preferred: preferred)) do |corrector|
@@ -53,6 +54,10 @@ module RuboCop
53
54
 
54
55
  private
55
56
 
57
+ def preferred_name(value)
58
+ value.zero? ? 'first' : 'last'
59
+ end
60
+
56
61
  def preferred_value(node, value)
57
62
  value = ".#{value}" unless node.loc.dot
58
63
  value
@@ -74,6 +79,12 @@ module RuboCop
74
79
  def brace_method?(node)
75
80
  node.send_type? && (node.method?(:[]) || node.method?(:[]=))
76
81
  end
82
+
83
+ # `arr[0] += 1` etc. would autocorrect to `arr.first += 1`, which calls the
84
+ # nonexistent `first=`/`last=` setter and raises `NoMethodError`.
85
+ def compound_assignment_target?(node)
86
+ node.parent&.type?(:op_asgn, :or_asgn, :and_asgn) && node.parent.children.first == node
87
+ end
77
88
  end
78
89
  end
79
90
  end
@@ -47,7 +47,7 @@ module RuboCop
47
47
  #
48
48
  # # bad
49
49
  # array1.any? { |elem| array2.member?(elem) }
50
- # array1.none? { |elem| array2.member?(elem) }
50
+ # array1.none? { |elem| array2.include?(elem) }
51
51
  #
52
52
  # # good
53
53
  # array1.intersect?(array2)
@@ -121,15 +121,15 @@ module RuboCop
121
121
  (block
122
122
  (call $_receiver ${:any? :none?})
123
123
  (args (arg _key))
124
- (send $_argument :member? (lvar _key))
124
+ (send $_argument {:member? :include?} (lvar _key))
125
125
  )
126
126
  (numblock
127
127
  (call $_receiver ${:any? :none?}) 1
128
- (send $_argument :member? (lvar :_1))
128
+ (send $_argument {:member? :include?} (lvar :_1))
129
129
  )
130
130
  (itblock
131
131
  (call $_receiver ${:any? :none?}) :it
132
- (send $_argument :member? (lvar :it))
132
+ (send $_argument {:member? :include?} (lvar :it))
133
133
  )
134
134
  }
135
135
  PATTERN
@@ -145,6 +145,10 @@ module RuboCop
145
145
 
146
146
  dot = dot_node.loc.dot.source
147
147
  bang = straight?(method_name) ? '' : '!'
148
+ # `a&.intersection(b)&.none?` returns `nil` when `a` is `nil`, but the negated
149
+ # rewrite `!a&.intersect?(b)` returns `true` there, flipping the result.
150
+ return if bang == '!' && dot == '&.'
151
+
148
152
  replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
149
153
 
150
154
  register_offense(node, replacement)
@@ -29,6 +29,9 @@ module RuboCop
29
29
  def on_send(node)
30
30
  array, element = single_element(node)
31
31
  return unless array
32
+ # `[*foo]` is not a single element: the splat can expand to any number of
33
+ # elements, so `intersect?([*foo])` is not equivalent to `include?(*foo)`.
34
+ return if element.splat_type?
32
35
 
33
36
  add_offense(
34
37
  node.source_range.with(begin_pos: node.loc.selector.begin_pos)
@@ -389,9 +389,23 @@ module RuboCop
389
389
 
390
390
  def require_do_end?(node)
391
391
  return false if node.braces? || node.multiline?
392
- return false unless (resbody = node.each_descendant(:resbody).first)
393
392
 
394
- resbody.children.first&.array_type?
393
+ body = node.body
394
+ return false unless body
395
+ # `ensure` and a block-level `rescue` are illegal inside `{ }`; only a
396
+ # bare modifier rescue (`expr rescue expr`) can be written with braces.
397
+ return true if body.ensure_type?
398
+ return false unless body.rescue_type?
399
+
400
+ !modifier_rescue?(body)
401
+ end
402
+
403
+ def modifier_rescue?(rescue_node)
404
+ return false if rescue_node.body.nil? || rescue_node.else_branch
405
+ return false unless rescue_node.resbody_branches.one?
406
+
407
+ resbody = rescue_node.resbody_branches.first
408
+ resbody.exceptions.empty? && resbody.exception_variable.nil?
395
409
  end
396
410
 
397
411
  def braces_required_method?(method_name)