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
@@ -73,7 +73,12 @@ module RuboCop
73
73
  end
74
74
 
75
75
  def find_pair_ancestor(node)
76
- node.each_ancestor.find(&:pair_type?)
76
+ node.each_ancestor do |ancestor|
77
+ return ancestor if ancestor.pair_type?
78
+ break if grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor)
79
+ end
80
+
81
+ nil
77
82
  end
78
83
 
79
84
  def unwrap_block_node(node)
@@ -204,10 +204,14 @@ module RuboCop
204
204
 
205
205
  def autocorrect(corrector, range, right_operand)
206
206
  range_source = range.source
207
+ # Match the operator exactly, not by substring, so compound assignments
208
+ # like `**=` and `/=` are not mistaken for `**` and `/` (which would drop
209
+ # the `=` and silently change the program's behavior).
210
+ operator = range_source.strip
207
211
 
208
- if range_source.include?('**') && !space_around_exponent_operator?
212
+ if operator == '**' && !space_around_exponent_operator?
209
213
  corrector.replace(range, '**')
210
- elsif range_source.include?('/') && !space_around_slash_operator?(right_operand)
214
+ elsif operator == '/' && !space_around_slash_operator?(right_operand)
211
215
  corrector.replace(range, '/')
212
216
  elsif range_source.end_with?("\n")
213
217
  corrector.replace(range, " #{range_source.strip}\n")
@@ -28,7 +28,7 @@ module RuboCop
28
28
  MISTAKES = { '=-' => '-=', '=+' => '+=', '=*' => '*=', '=!' => '!=' }.freeze
29
29
 
30
30
  def on_asgn(node)
31
- return unless (rhs = rhs(node))
31
+ return unless (rhs = node.expression)
32
32
 
33
33
  range = range_between(node.loc.operator.end_pos - 1, rhs.source_range.begin_pos + 1)
34
34
  source = range.source
@@ -38,16 +38,6 @@ module RuboCop
38
38
  end
39
39
 
40
40
  SIMPLE_ASSIGNMENT_TYPES.each { |asgn_type| alias_method :"on_#{asgn_type}", :on_asgn }
41
-
42
- private
43
-
44
- def rhs(node)
45
- if node.casgn_type?
46
- node.children[2]
47
- else
48
- node.children[1]
49
- end
50
- end
51
41
  end
52
42
  end
53
43
  end
@@ -44,13 +44,6 @@ module RuboCop
44
44
  RESTRICT_ON_SEND = PRECEDENCE.flatten.freeze
45
45
  MSG = 'Wrap expressions with varying precedence with parentheses to avoid ambiguity.'
46
46
 
47
- def on_new_investigation
48
- # Cache the precedence of each node being investigated
49
- # so that we only need to calculate it once
50
- @node_precedences = {}
51
- super
52
- end
53
-
54
47
  def on_and(node)
55
48
  return unless (parent = node.parent)
56
49
 
@@ -77,9 +70,7 @@ module RuboCop
77
70
  private
78
71
 
79
72
  def precedence(node)
80
- @node_precedences.fetch(node) do
81
- PRECEDENCE.index { |operators| operators.include?(operator_name(node)) }
82
- end
73
+ PRECEDENCE.index { |operators| operators.include?(operator_name(node)) }
83
74
  end
84
75
 
85
76
  def operator?(node)
@@ -78,13 +78,25 @@ module RuboCop
78
78
  end
79
79
 
80
80
  def allowed_construct?(asgn_node)
81
- asgn_node.begin_type? || conditional_assignment?(asgn_node)
81
+ return true if asgn_node.begin_type?
82
+
83
+ conditional_assignment?(asgn_node) || discarded_assignment?(asgn_node)
82
84
  end
83
85
 
84
86
  def conditional_assignment?(asgn_node)
85
87
  !asgn_node.loc.operator
86
88
  end
87
89
 
90
+ # An assignment that is a statement of a multi-statement `begin`
91
+ # (e.g. `(foo = bar; baz)`) has its value discarded, so it is not used
92
+ # as the condition. Wrapping it in parentheses would only conflict with
93
+ # `Style/RedundantParentheses`, so it is left alone.
94
+ def discarded_assignment?(asgn_node)
95
+ parent = asgn_node.parent
96
+
97
+ parent&.begin_type? && parent.children.size > 1
98
+ end
99
+
88
100
  def skip_children?(asgn_node)
89
101
  (asgn_node.call_type? && !asgn_node.assignment_method?) ||
90
102
  empty_condition?(asgn_node) ||
@@ -80,7 +80,6 @@ module RuboCop
80
80
  check_assignment_chain(arg_name, arg_value)
81
81
  end
82
82
 
83
- # rubocop:disable Metrics/AbcSize
84
83
  def check_assignment_chain(arg_name, node)
85
84
  return unless node.lvasgn_type?
86
85
 
@@ -88,7 +87,7 @@ module RuboCop
88
87
  current_node = node
89
88
 
90
89
  while current_node.lvasgn_type?
91
- seen_variables << current_node.children.first if current_node.lvasgn_type?
90
+ seen_variables << current_node.children.first
92
91
  current_node = current_node.children.last
93
92
  end
94
93
 
@@ -99,7 +98,6 @@ module RuboCop
99
98
 
100
99
  add_offense(current_node, message: format(MSG, arg_name: arg_name))
101
100
  end
102
- # rubocop:enable Metrics/AbcSize
103
101
  end
104
102
  end
105
103
  end
@@ -73,12 +73,12 @@ module RuboCop
73
73
  # require 'my_debugger/start'
74
74
  class Debugger < Base
75
75
  MSG = 'Remove debugger entry point `%<source>s`.'
76
- BLOCK_TYPES = %i[block numblock itblock kwbegin].freeze
77
76
 
78
77
  def on_send(node)
78
+ return unless debugger_method?(node) || debugger_require?(node)
79
79
  return if assumed_usage_context?(node)
80
80
 
81
- add_offense(node) if debugger_method?(node) || debugger_require?(node)
81
+ add_offense(node)
82
82
  end
83
83
 
84
84
  private
@@ -102,9 +102,20 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def debugger_method?(send_node)
105
+ return false unless debugger_method_names.include?(send_node.method_name)
106
+
105
107
  debugger_methods.include?(chained_method_name(send_node))
106
108
  end
107
109
 
110
+ # The last segment of each configured debugger method, used to cheaply
111
+ # rule out the vast majority of `send` nodes before building the
112
+ # chained method name.
113
+ def debugger_method_names
114
+ @debugger_method_names ||= debugger_methods.to_set do |method|
115
+ method.to_s.split('.').last&.to_sym
116
+ end
117
+ end
118
+
108
119
  def debugger_require?(send_node)
109
120
  return false unless send_node.method?(:require) && send_node.arguments.one?
110
121
  return false unless (argument = send_node.first_argument).str_type?
@@ -49,7 +49,7 @@ module RuboCop
49
49
  # Maybe further investigation of RuboCop AST will lead to an essential solution.
50
50
  return unless node.loc
51
51
 
52
- constant = node.absolute? ? constant_name(node, node.short_name) : node.source
52
+ constant = node.source.delete_prefix('::')
53
53
  return unless (deprecated_constant = deprecated_constants[constant])
54
54
 
55
55
  alternative = deprecated_constant['Alternative']
@@ -63,12 +63,6 @@ module RuboCop
63
63
 
64
64
  private
65
65
 
66
- def constant_name(node, nested_constant_name)
67
- return nested_constant_name.to_s unless node.namespace.const_type?
68
-
69
- constant_name(node.namespace, "#{node.namespace.short_name}::#{nested_constant_name}")
70
- end
71
-
72
66
  def message(good, bad, deprecated_version)
73
67
  deprecated_message = ", deprecated since Ruby #{deprecated_version}" if deprecated_version
74
68
 
@@ -77,7 +77,7 @@ module RuboCop
77
77
  return false unless processed_source.contains_comment?(node.source_range)
78
78
 
79
79
  line_comment = processed_source.comment_at_line(node.source_range.line)
80
- !line_comment || !comment_disables_cop?(line_comment.source)
80
+ !line_comment || !comment_disables_cop?(line_comment)
81
81
  end
82
82
 
83
83
  def allow_empty_lambdas?
@@ -85,8 +85,8 @@ module RuboCop
85
85
  end
86
86
 
87
87
  def comment_disables_cop?(comment)
88
- regexp_pattern = "# rubocop : (disable|todo) ([^,],)* (all|#{cop_name})"
89
- Regexp.new(regexp_pattern.gsub(' ', '\s*')).match?(comment)
88
+ directive = DirectiveComment.new(comment)
89
+ directive.disabled? && directive.cop_names.include?(cop_name)
90
90
  end
91
91
  end
92
92
  end
@@ -43,7 +43,25 @@ module RuboCop
43
43
  MSG = 'Do not return from an `ensure` block.'
44
44
 
45
45
  def on_ensure(node)
46
- node.branch&.each_node(:return) { |return_node| add_offense(return_node) }
46
+ node.branch&.each_node(:return) do |return_node|
47
+ next if return_from_inner_scope?(return_node, node)
48
+
49
+ add_offense(return_node)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # A `return` inside a nested method definition or lambda within the
56
+ # `ensure` returns from that inner scope, not from the method whose
57
+ # `ensure` this is, so it is not an offense. A `return` inside a plain
58
+ # block (or `proc`) does propagate out, so it remains an offense.
59
+ def return_from_inner_scope?(return_node, ensure_node)
60
+ return_node.each_ancestor do |ancestor|
61
+ break if ancestor == ensure_node
62
+ return true if ancestor.any_def_type? || (ancestor.any_block_type? && ancestor.lambda?)
63
+ end
64
+ false
47
65
  end
48
66
  end
49
67
  end
@@ -148,7 +148,9 @@ module RuboCop
148
148
  arguments = node.arguments
149
149
  overridden_kwargs = kwargs.dup
150
150
 
151
- overridden_kwargs[0] = "trim_mode: #{arguments[2].source}" if arguments[2]
151
+ if arguments[2] && !arguments[2].hash_type?
152
+ overridden_kwargs[0] = "trim_mode: #{arguments[2].source}"
153
+ end
152
154
 
153
155
  if arguments[3] && !arguments[3].hash_type?
154
156
  overridden_kwargs[1] = "eoutvar: #{arguments[3].source}"
@@ -104,6 +104,7 @@ module RuboCop
104
104
 
105
105
  def literal_safe?(node)
106
106
  return false unless node
107
+ return literal_safe?(node.children.first) if node.begin_type?
107
108
 
108
109
  (node.numeric_type? && node.value.zero?) || node.nil_type?
109
110
  end
@@ -61,11 +61,15 @@ module RuboCop
61
61
  private
62
62
 
63
63
  def scheduler_compatible?(io1, io2)
64
- return false unless io1&.array_type? && io1.values.size == 1
64
+ return false unless single_io_array?(io1)
65
65
 
66
66
  io2&.array_type? ? io2.values.empty? : (io2.nil? || io2.nil_type?)
67
67
  end
68
68
 
69
+ def single_io_array?(node)
70
+ node&.array_type? && node.values.size == 1 && !node.values.first.splat_type?
71
+ end
72
+
69
73
  def preferred_method(read, write, timeout)
70
74
  timeout_argument = timeout.nil? ? '' : "(#{timeout.source})"
71
75
 
@@ -24,8 +24,25 @@ module RuboCop
24
24
  MSG = 'Interpolation in single quoted string detected. ' \
25
25
  'Use double quoted strings if you need interpolation.'
26
26
 
27
- # rubocop:disable Metrics/CyclomaticComplexity
28
27
  def on_str(node)
28
+ check(node)
29
+ end
30
+
31
+ # A multiline single-quoted string is parsed as a `dstr` of `str` segments, so it
32
+ # is not covered by `on_str`. Inspect single-quoted `dstr`s here; double-quoted
33
+ # interpolation is also a `dstr`, hence the delimiter check.
34
+ def on_dstr(node)
35
+ # A heredoc is also a `dstr`, but its `loc` is a `Parser::Source::Map::Heredoc`
36
+ # with no `begin`, so bail before touching it.
37
+ return if heredoc?(node)
38
+
39
+ check(node) if node.loc.begin&.source == "'"
40
+ end
41
+
42
+ private
43
+
44
+ # rubocop:disable Metrics/CyclomaticComplexity
45
+ def check(node)
29
46
  return if node.parent&.regexp_type?
30
47
  return unless /(?<!\\)#\{.*\}/.match?(node.source)
31
48
  return if heredoc?(node)
@@ -36,8 +53,6 @@ module RuboCop
36
53
  end
37
54
  # rubocop:enable Metrics/CyclomaticComplexity
38
55
 
39
- private
40
-
41
56
  def autocorrect(corrector, node)
42
57
  starting_token, ending_token = if node.source.include?('"')
43
58
  ['%{', '}']
@@ -42,7 +42,7 @@ module RuboCop
42
42
  end
43
43
 
44
44
  add_offense(node) do |corrector|
45
- corrector.replace(node, node.first_argument.source.delete('&'))
45
+ corrector.replace(node, node.first_argument.source.delete_prefix('&'))
46
46
  end
47
47
  end
48
48
  end
@@ -56,7 +56,17 @@ module RuboCop
56
56
  def traverse_node(node, &block)
57
57
  yield node if node.equals_asgn?
58
58
 
59
- node.each_child_node { |child| traverse_node(child, &block) }
59
+ node.each_child_node do |child|
60
+ next if scope_body?(node, child)
61
+
62
+ traverse_node(child, &block)
63
+ end
64
+ end
65
+
66
+ # An assignment inside a block or method body within the condition belongs to
67
+ # that inner scope rather than the condition itself, so it is not inspected.
68
+ def scope_body?(node, child)
69
+ node.type?(:any_block, :any_def) && child == node.body
60
70
  end
61
71
 
62
72
  def all_literals?(node)
@@ -119,11 +119,13 @@ module RuboCop
119
119
  end
120
120
 
121
121
  def autocorrected_value_for_string(node)
122
- if node.source.start_with?("'", '%q')
123
- node.children.last.inspect[1..-2]
124
- else
125
- node.children.last
126
- end
122
+ return node.source.delete_prefix('"').delete_suffix('"') unless node.value.valid_encoding?
123
+
124
+ escape_string_content(node.children.last)
125
+ end
126
+
127
+ def escape_string_content(string)
128
+ string.gsub(/[\\"]|#(?=[@{$])/, '\\\\\&')
127
129
  end
128
130
 
129
131
  def autocorrected_value_for_symbol(node)
@@ -134,12 +136,7 @@ module RuboCop
134
136
  end
135
137
 
136
138
  def autocorrected_value_in_hash_for_symbol(node)
137
- # TODO: We need to detect symbol unacceptable names more correctly
138
- if / |"|'/.match?(node.value.to_s)
139
- ":\\\"#{node.value.to_s.gsub('"') { '\\\\\"' }}\\\""
140
- else
141
- ":#{node.value}"
142
- end
139
+ escape_string_content(node.value.inspect)
143
140
  end
144
141
 
145
142
  def autocorrected_value_for_array(node)
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # cop disables on wide ranges of code, that later contributors to
10
10
  # a file wouldn't be aware of.
11
11
  #
12
- # You can set `MaximumRangeSize` to define the maximum number of
12
+ # You can set `MaxRangeSize` to define the maximum number of
13
13
  # consecutive lines a cop can be disabled for.
14
14
  #
15
15
  # - `.inf` any size (default)
@@ -23,7 +23,7 @@ module RuboCop
23
23
  # # rubocop:enable SomeCop
24
24
  # ----
25
25
  #
26
- # @example MaximumRangeSize: .inf (default)
26
+ # @example MaxRangeSize: .inf (default)
27
27
  #
28
28
  # # good
29
29
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -37,7 +37,7 @@ module RuboCop
37
37
  # x= 0
38
38
  # # EOF
39
39
  #
40
- # @example MaximumRangeSize: 2
40
+ # @example MaxRangeSize: 2
41
41
  #
42
42
  # # good
43
43
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -94,7 +94,7 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def max_range
97
- @max_range ||= cop_config['MaximumRangeSize']
97
+ @max_range ||= cop_config['MaxRangeSize']
98
98
  end
99
99
 
100
100
  def message(cop, comment, type = 'cop')
@@ -41,6 +41,8 @@ module RuboCop
41
41
  def on_lvasgn(node)
42
42
  node.each_node(:kwbegin) do |kwbegin_node|
43
43
  kwbegin_node.each_node(:return) do |return_node|
44
+ next if return_from_inner_scope?(return_node, kwbegin_node)
45
+
44
46
  add_offense(return_node)
45
47
  end
46
48
  end
@@ -51,6 +53,20 @@ module RuboCop
51
53
  alias on_casgn on_lvasgn
52
54
  alias on_or_asgn on_lvasgn
53
55
  alias on_op_asgn on_lvasgn
56
+
57
+ private
58
+
59
+ # A `return` inside a nested method definition or lambda within the
60
+ # `begin..end` returns from that inner scope rather than the assignment
61
+ # context, so it is not an offense. A `return` inside a plain block (or
62
+ # `proc`) does propagate out, so it remains an offense.
63
+ def return_from_inner_scope?(return_node, kwbegin_node)
64
+ return_node.each_ancestor do |ancestor|
65
+ break if ancestor == kwbegin_node
66
+ return true if ancestor.any_def_type? || (ancestor.any_block_type? && ancestor.lambda?)
67
+ end
68
+ false
69
+ end
54
70
  end
55
71
  end
56
72
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
  end
75
75
 
76
76
  # @!method chained_send?(node)
77
- def_node_matcher :chained_send?, '(send !nil? ...)'
77
+ def_node_matcher :chained_send?, '(call !nil? ...)'
78
78
 
79
79
  # @!method define_method?(node)
80
80
  def_node_matcher :define_method?, <<~PATTERN
@@ -66,7 +66,7 @@ module RuboCop
66
66
  # # good
67
67
  # 10.minutes.to_i
68
68
  #
69
- # @example IgnoredClasses: [Time, DateTime] (default)
69
+ # @example AllowedClasses: [Time, DateTime] (default)
70
70
  #
71
71
  # # good
72
72
  # Time.now.to_datetime.to_i
@@ -117,11 +117,11 @@ module RuboCop
117
117
 
118
118
  message = format(
119
119
  MSG,
120
- current: "#{receiver.source}.#{to_method}",
120
+ current: current_method(node, receiver, to_method),
121
121
  corrected_method: correct_method(node, receiver)
122
122
  )
123
123
  add_offense(node, message: message) do |corrector|
124
- next if part_of_ignored_node?(node)
124
+ next if safe_navigation?(node) || part_of_ignored_node?(node)
125
125
 
126
126
  corrector.replace(node, correct_method(node, node.receiver))
127
127
 
@@ -156,11 +156,20 @@ module RuboCop
156
156
  "{ |i| #{body} }"
157
157
  end
158
158
 
159
+ def current_method(node, receiver, to_method)
160
+ operator = node.csend_type? ? '&.' : '.'
161
+ "#{receiver.source}#{operator}#{to_method}"
162
+ end
163
+
159
164
  def remove_parentheses(corrector, node)
160
165
  corrector.replace(node.loc.begin, ' ')
161
166
  corrector.remove(node.loc.end)
162
167
  end
163
168
 
169
+ def safe_navigation?(node)
170
+ node.csend_type? || node.each_descendant(:csend).any?
171
+ end
172
+
164
173
  def allow_receiver?(receiver)
165
174
  if receiver.numeric_type? || (receiver.call_type? &&
166
175
  (conversion_method?(receiver.method_name) ||
@@ -188,7 +197,7 @@ module RuboCop
188
197
  end
189
198
 
190
199
  def ignored_classes
191
- cop_config.fetch('IgnoredClasses', [])
200
+ cop_config.fetch('AllowedClasses', [])
192
201
  end
193
202
 
194
203
  def ignored_class?(name)
@@ -15,6 +15,13 @@ module RuboCop
15
15
  # can't determine the type of `x`. If `x` is an `Array` or `String`, it doesn't perform
16
16
  # a numeric operation.
17
17
  #
18
+ # @safety
19
+ # This cop is unsafe because the autocorrection drops the operands, which
20
+ # discards any side effects of evaluating them and can change behavior when
21
+ # the result is not actually constant. For example, `x / x` raises
22
+ # `ZeroDivisionError` when `x` is `0`, and returns `Float::NAN` (not `1`)
23
+ # when `x` is `0.0`; replacing it with `1` silences that.
24
+ #
18
25
  # @example
19
26
  #
20
27
  # # bad
@@ -58,6 +65,9 @@ module RuboCop
58
65
  '(op-asgn (lvasgn $_lhs) $_operation ({int lvar} $_rhs))'
59
66
 
60
67
  def on_send(node)
68
+ # Safe navigation short-circuits to `nil` when the receiver is `nil`, so the
69
+ # result is not constant and replacing it with `0`/`1` would change behavior.
70
+ return if node.csend_type?
61
71
  return unless (lhs, operation, rhs = operation_with_constant_result?(node))
62
72
  return unless (result = constant_result?(lhs, operation, rhs))
63
73
 
@@ -38,23 +38,23 @@ module RuboCop
38
38
  def on_new_investigation
39
39
  return if processed_source.buffer.source.empty?
40
40
 
41
- encoding_line, frozen_string_literal_line = magic_comment_lines
41
+ encoding_line, other_magic_comment_line = magic_comment_lines
42
42
 
43
- return unless encoding_line && frozen_string_literal_line
44
- return if encoding_line < frozen_string_literal_line
43
+ return unless encoding_line && other_magic_comment_line
44
+ return if encoding_line < other_magic_comment_line
45
45
 
46
46
  range = processed_source.buffer.line_range(encoding_line + 1)
47
47
 
48
48
  add_offense(range) do |corrector|
49
- autocorrect(corrector, encoding_line, frozen_string_literal_line)
49
+ autocorrect(corrector, encoding_line, other_magic_comment_line)
50
50
  end
51
51
  end
52
52
 
53
53
  private
54
54
 
55
- def autocorrect(corrector, encoding_line, frozen_string_literal_line)
55
+ def autocorrect(corrector, encoding_line, other_magic_comment_line)
56
56
  range1 = processed_source.buffer.line_range(encoding_line + 1)
57
- range2 = processed_source.buffer.line_range(frozen_string_literal_line + 1)
57
+ range2 = processed_source.buffer.line_range(other_magic_comment_line + 1)
58
58
 
59
59
  corrector.replace(range1, range2.source)
60
60
  corrector.replace(range2, range1.source)
@@ -66,7 +66,7 @@ module RuboCop
66
66
  leading_magic_comments.each.with_index do |comment, index|
67
67
  if comment.encoding_specified?
68
68
  lines[0] = index
69
- elsif comment.frozen_string_literal_specified?
69
+ elsif comment.valid?
70
70
  lines[1] = index
71
71
  end
72
72
 
@@ -95,7 +95,7 @@ module RuboCop
95
95
  if parent.module_type?
96
96
  namespace = parent.identifier.source
97
97
 
98
- return allow_implicit_namespaces.include?(namespace)
98
+ return true if allow_implicit_namespaces.include?(namespace)
99
99
  end
100
100
 
101
101
  implicit_namespace?(parent)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Checks for `rand(1)` calls.
7
- # Such calls always return `0`.
7
+ # Such calls always return `0`, and so do `rand(-1)`, `rand(1.0)`, and `rand(-1.0)`.
8
8
  #
9
9
  # @example
10
10
  #
@@ -283,7 +283,10 @@ module RuboCop
283
283
  end
284
284
 
285
285
  def matching_range(haystack, needle)
286
- offset = haystack.source.index(needle)
286
+ # Match the cop name as a whole token so a shorter name is not found inside a
287
+ # longer one that shares its prefix (e.g. `Lint/AmbiguousOperator` in
288
+ # `Lint/AmbiguousOperatorPrecedence`).
289
+ offset = haystack.source.index(/#{Regexp.escape(needle)}(?!\w)/)
287
290
  return unless offset
288
291
 
289
292
  offset += haystack.begin_pos
@@ -74,7 +74,10 @@ module RuboCop
74
74
  end
75
75
 
76
76
  def cop_name_indention(comment, name)
77
- comment.text.index(name)
77
+ # Match the cop name as a whole token so a shorter name is not found inside a
78
+ # longer one that shares its prefix (e.g. `Layout/EmptyLines` in
79
+ # `Layout/EmptyLinesAfterModuleInclusion`).
80
+ comment.text.index(/#{Regexp.escape(name)}(?!\w)/)
78
81
  end
79
82
 
80
83
  def range_with_comma(comment, name)
@@ -38,10 +38,10 @@ module RuboCop
38
38
  GLOB_METHODS = %i[glob []].freeze
39
39
 
40
40
  def on_send(node)
41
- return unless (receiver = node.receiver)
42
- return unless receiver.receiver&.const_type? && receiver.receiver.short_name == :Dir
43
- return unless GLOB_METHODS.include?(receiver.method_name)
44
- return if multiple_argument?(receiver)
41
+ return unless dir_glob?(node.receiver)
42
+ # `sort` with a comparator block or block-pass changes the order, so it is
43
+ # not redundant with the default sorting performed by `Dir.glob`/`Dir[]`.
44
+ return if sort_with_comparator?(node) || multiple_argument?(node.receiver)
45
45
 
46
46
  selector = node.loc.selector
47
47
 
@@ -53,9 +53,20 @@ module RuboCop
53
53
 
54
54
  private
55
55
 
56
+ def dir_glob?(receiver)
57
+ return false unless receiver&.receiver&.const_type?
58
+ return false unless receiver.receiver.short_name == :Dir
59
+
60
+ GLOB_METHODS.include?(receiver.method_name)
61
+ end
62
+
56
63
  def multiple_argument?(glob_method)
57
64
  glob_method.arguments.count >= 2 || glob_method.first_argument&.splat_type?
58
65
  end
66
+
67
+ def sort_with_comparator?(node)
68
+ node.parent&.any_block_type? || node.last_argument&.block_pass_type?
69
+ end
59
70
  end
60
71
  end
61
72
  end