rubocop 1.74.0 → 1.75.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +32 -6
  4. data/config/obsoletion.yml +3 -1
  5. data/lib/rubocop/cli.rb +1 -1
  6. data/lib/rubocop/config.rb +35 -6
  7. data/lib/rubocop/config_loader.rb +4 -0
  8. data/lib/rubocop/config_obsoletion/renamed_cop.rb +18 -3
  9. data/lib/rubocop/config_obsoletion.rb +46 -2
  10. data/lib/rubocop/config_validator.rb +1 -0
  11. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +2 -1
  12. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +6 -5
  13. data/lib/rubocop/cop/layout/block_alignment.rb +2 -2
  14. data/lib/rubocop/cop/layout/block_end_newline.rb +1 -0
  15. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  16. data/lib/rubocop/cop/layout/else_alignment.rb +1 -1
  17. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +3 -3
  18. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  19. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -0
  20. data/lib/rubocop/cop/layout/indentation_width.rb +1 -0
  21. data/lib/rubocop/cop/layout/line_length.rb +5 -1
  22. data/lib/rubocop/cop/layout/multiline_block_layout.rb +1 -0
  23. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +1 -0
  24. data/lib/rubocop/cop/layout/redundant_line_break.rb +9 -5
  25. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -5
  26. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  27. data/lib/rubocop/cop/layout/space_before_block_braces.rb +1 -0
  28. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -0
  29. data/lib/rubocop/cop/lint/debugger.rb +2 -2
  30. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +1 -1
  31. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -3
  32. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  33. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +2 -2
  34. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -1
  35. data/lib/rubocop/cop/lint/raise_exception.rb +29 -10
  36. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +7 -4
  37. data/lib/rubocop/cop/lint/redundant_with_index.rb +3 -0
  38. data/lib/rubocop/cop/lint/redundant_with_object.rb +3 -0
  39. data/lib/rubocop/cop/lint/return_in_void_context.rb +7 -2
  40. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +8 -1
  41. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  42. data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -1
  43. data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
  44. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +2 -0
  45. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -0
  46. data/lib/rubocop/cop/lint/unreachable_loop.rb +5 -5
  47. data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -0
  48. data/lib/rubocop/cop/lint/void.rb +1 -0
  49. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  50. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  51. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  52. data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
  53. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
  54. data/lib/rubocop/cop/mixin/def_node.rb +1 -1
  55. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -1
  56. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +20 -0
  57. data/lib/rubocop/cop/mixin/forbidden_pattern.rb +16 -0
  58. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +0 -1
  59. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -0
  60. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  61. data/lib/rubocop/cop/naming/method_name.rb +64 -8
  62. data/lib/rubocop/cop/naming/variable_name.rb +6 -19
  63. data/lib/rubocop/cop/registry.rb +9 -6
  64. data/lib/rubocop/cop/style/array_intersect.rb +39 -28
  65. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  66. data/lib/rubocop/cop/style/class_equality_comparison.rb +1 -1
  67. data/lib/rubocop/cop/style/collection_methods.rb +1 -0
  68. data/lib/rubocop/cop/style/combinable_loops.rb +1 -0
  69. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -0
  70. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  71. data/lib/rubocop/cop/style/empty_literal.rb +4 -0
  72. data/lib/rubocop/cop/style/explicit_block_argument.rb +2 -2
  73. data/lib/rubocop/cop/style/for.rb +1 -0
  74. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +3 -2
  75. data/lib/rubocop/cop/style/global_std_stream.rb +3 -0
  76. data/lib/rubocop/cop/style/guard_clause.rb +2 -1
  77. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -2
  78. data/lib/rubocop/cop/style/hash_fetch_chain.rb +105 -0
  79. data/lib/rubocop/cop/style/hash_syntax.rb +3 -0
  80. data/lib/rubocop/cop/style/if_inside_else.rb +10 -13
  81. data/lib/rubocop/cop/style/inverse_methods.rb +1 -0
  82. data/lib/rubocop/cop/style/invertible_unless_condition.rb +2 -2
  83. data/lib/rubocop/cop/style/ip_addresses.rb +2 -2
  84. data/lib/rubocop/cop/style/it_block_parameter.rb +100 -0
  85. data/lib/rubocop/cop/style/keyword_parameters_order.rb +1 -1
  86. data/lib/rubocop/cop/style/lambda.rb +1 -0
  87. data/lib/rubocop/cop/style/map_into_array.rb +1 -0
  88. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  89. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +1 -0
  90. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -0
  91. data/lib/rubocop/cop/style/next.rb +44 -0
  92. data/lib/rubocop/cop/style/object_then.rb +1 -0
  93. data/lib/rubocop/cop/style/proc.rb +1 -0
  94. data/lib/rubocop/cop/style/raise_args.rb +8 -8
  95. data/lib/rubocop/cop/style/redundant_begin.rb +1 -0
  96. data/lib/rubocop/cop/style/redundant_condition.rb +13 -1
  97. data/lib/rubocop/cop/style/redundant_format.rb +10 -3
  98. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
  99. data/lib/rubocop/cop/style/redundant_self.rb +1 -0
  100. data/lib/rubocop/cop/style/redundant_sort_by.rb +17 -1
  101. data/lib/rubocop/cop/style/return_nil.rb +2 -2
  102. data/lib/rubocop/cop/style/select_by_regexp.rb +4 -1
  103. data/lib/rubocop/cop/style/single_line_do_end_block.rb +3 -1
  104. data/lib/rubocop/cop/style/sole_nested_conditional.rb +41 -100
  105. data/lib/rubocop/cop/style/super_arguments.rb +1 -2
  106. data/lib/rubocop/cop/style/symbol_proc.rb +2 -0
  107. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -0
  108. data/lib/rubocop/cop/util.rb +1 -1
  109. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  110. data/lib/rubocop/cop/variable_force/variable.rb +2 -7
  111. data/lib/rubocop/cop/variable_force.rb +1 -1
  112. data/lib/rubocop/lsp/runtime.rb +4 -4
  113. data/lib/rubocop/lsp/stdin_runner.rb +3 -1
  114. data/lib/rubocop/magic_comment.rb +8 -0
  115. data/lib/rubocop/rspec/cop_helper.rb +4 -1
  116. data/lib/rubocop/rspec/shared_contexts.rb +20 -0
  117. data/lib/rubocop/rspec/support.rb +2 -0
  118. data/lib/rubocop/runner.rb +5 -1
  119. data/lib/rubocop/server/cache.rb +13 -10
  120. data/lib/rubocop/target_finder.rb +1 -1
  121. data/lib/rubocop/target_ruby.rb +1 -1
  122. data/lib/rubocop/version.rb +14 -7
  123. data/lib/rubocop.rb +4 -0
  124. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +20 -2
  125. metadata +10 -6
@@ -23,8 +23,8 @@ module RuboCop
23
23
  global.without_department(:Test).cops
24
24
  end
25
25
 
26
- def self.qualified_cop_name(name, origin)
27
- global.qualified_cop_name(name, origin)
26
+ def self.qualified_cop_name(name, origin, warn: true)
27
+ global.qualified_cop_name(name, origin, warn: warn)
28
28
  end
29
29
 
30
30
  # Changes momentarily the global registry
@@ -139,7 +139,7 @@ module RuboCop
139
139
 
140
140
  case potential_badges.size
141
141
  when 0 then name # No namespace found. Deal with it later in caller.
142
- when 1 then resolve_badge(badge, potential_badges.first, path)
142
+ when 1 then resolve_badge(badge, potential_badges.first, path, warn: warn)
143
143
  else raise AmbiguousCopName.new(badge, path, potential_badges)
144
144
  end
145
145
  end
@@ -296,11 +296,14 @@ module RuboCop
296
296
  self.class.new(cops)
297
297
  end
298
298
 
299
- def resolve_badge(given_badge, real_badge, source_path)
299
+ def resolve_badge(given_badge, real_badge, source_path, warn: true)
300
300
  unless given_badge.match?(real_badge)
301
301
  path = PathUtil.smart_path(source_path)
302
- warn "#{path}: #{given_badge} has the wrong namespace - " \
303
- "replace it with #{given_badge.with_department(real_badge.department)}"
302
+
303
+ if warn
304
+ warn("#{path}: #{given_badge} has the wrong namespace - " \
305
+ "replace it with #{given_badge.with_department(real_badge.department)}")
306
+ end
304
307
  end
305
308
 
306
309
  real_badge.to_s
@@ -6,7 +6,8 @@ module RuboCop
6
6
  # In Ruby 3.1, `Array#intersect?` has been added.
7
7
  #
8
8
  # This cop identifies places where `(array1 & array2).any?`
9
- # can be replaced by `array1.intersect?(array2)`.
9
+ # or `(array1.intersection(array2)).any?` can be replaced by
10
+ # `array1.intersect?(array2)`.
10
11
  #
11
12
  # The `array1.intersect?(array2)` method is faster than
12
13
  # `(array1 & array2).any?` and is more readable.
@@ -20,6 +21,10 @@ module RuboCop
20
21
  # [1].intersect?([1,2]) { |x| false } # => true
21
22
  # ----
22
23
  #
24
+ # NOTE: Although `Array#intersection` can take zero or multiple arguments,
25
+ # only cases where exactly one argument is provided can be replaced with
26
+ # `Array#intersect?` and are handled by this cop.
27
+ #
23
28
  # @safety
24
29
  # This cop cannot guarantee that `array1` and `array2` are
25
30
  # actually arrays while method `intersect?` is for arrays only.
@@ -30,6 +35,11 @@ module RuboCop
30
35
  # (array1 & array2).empty?
31
36
  # (array1 & array2).none?
32
37
  #
38
+ # # bad
39
+ # array1.intersection(array2).any?
40
+ # array1.intersection(array2).empty?
41
+ # array1.intersection(array2).none?
42
+ #
33
43
  # # good
34
44
  # array1.intersect?(array2)
35
45
  # !array1.intersect?(array2)
@@ -53,65 +63,66 @@ module RuboCop
53
63
 
54
64
  minimum_target_ruby_version 3.1
55
65
 
56
- # @!method regular_bad_intersection_check?(node)
57
- def_node_matcher :regular_bad_intersection_check?, <<~PATTERN
58
- (send
59
- (begin
60
- (send $(...) :& $(...))
61
- ) ${:any? :empty? :none?}
62
- )
63
- PATTERN
66
+ PREDICATES = %i[any? empty? none?].to_set.freeze
67
+ ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze
64
68
 
65
- # @!method active_support_bad_intersection_check?(node)
66
- def_node_matcher :active_support_bad_intersection_check?, <<~PATTERN
67
- (send
68
- (begin
69
- (send $(...) :& $(...))
70
- ) ${:present? :any? :blank? :empty? :none?}
69
+ # @!method bad_intersection_check?(node, predicates)
70
+ def_node_matcher :bad_intersection_check?, <<~PATTERN
71
+ (call
72
+ {
73
+ (begin (send $_ :& $_))
74
+ (call $_ :intersection $_)
75
+ }
76
+ $%1
71
77
  )
72
78
  PATTERN
73
79
 
74
- MSG = 'Use `%<negated>s%<receiver>s.intersect?(%<argument>s)` ' \
75
- 'instead of `(%<receiver>s & %<argument>s).%<method_name>s`.'
80
+ MSG = 'Use `%<negated>s%<receiver>s%<dot>sintersect?(%<argument>s)` ' \
81
+ 'instead of `%<existing>s`.'
76
82
  STRAIGHT_METHODS = %i[present? any?].freeze
77
83
  NEGATED_METHODS = %i[blank? empty? none?].freeze
78
84
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
79
85
 
80
86
  def on_send(node)
81
87
  return if node.block_literal?
82
- return unless (receiver, argument, method_name = bad_intersection_check?(node))
88
+ return unless (receiver, argument, method_name = bad_intersection?(node))
83
89
 
84
- message = message(receiver.source, argument.source, method_name)
90
+ dot = node.loc.dot.source
91
+ message = message(receiver.source, argument.source, method_name, dot, node.source)
85
92
 
86
93
  add_offense(node, message: message) do |corrector|
87
94
  bang = straight?(method_name) ? '' : '!'
88
95
 
89
- corrector.replace(node, "#{bang}#{receiver.source}.intersect?(#{argument.source})")
96
+ corrector.replace(node, "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})")
90
97
  end
91
98
  end
99
+ alias on_csend on_send
92
100
 
93
101
  private
94
102
 
95
- def bad_intersection_check?(node)
96
- if active_support_extensions_enabled?
97
- active_support_bad_intersection_check?(node)
98
- else
99
- regular_bad_intersection_check?(node)
100
- end
103
+ def bad_intersection?(node)
104
+ predicates = if active_support_extensions_enabled?
105
+ ACTIVE_SUPPORT_PREDICATES
106
+ else
107
+ PREDICATES
108
+ end
109
+
110
+ bad_intersection_check?(node, predicates)
101
111
  end
102
112
 
103
113
  def straight?(method_name)
104
114
  STRAIGHT_METHODS.include?(method_name.to_sym)
105
115
  end
106
116
 
107
- def message(receiver, argument, method_name)
117
+ def message(receiver, argument, method_name, dot, existing)
108
118
  negated = straight?(method_name) ? '' : '!'
109
119
  format(
110
120
  MSG,
111
121
  negated: negated,
112
122
  receiver: receiver,
113
123
  argument: argument,
114
- method_name: method_name
124
+ dot: dot,
125
+ existing: existing
115
126
  )
116
127
  end
117
128
  end
@@ -208,6 +208,7 @@ module RuboCop
208
208
  end
209
209
 
210
210
  alias on_numblock on_block
211
+ alias on_itblock on_block
211
212
 
212
213
  private
213
214
 
@@ -347,7 +348,7 @@ module RuboCop
347
348
  # rubocop:disable Metrics/CyclomaticComplexity
348
349
  def get_blocks(node, &block)
349
350
  case node.type
350
- when :block, :numblock
351
+ when :block, :numblock, :itblock
351
352
  yield node
352
353
  when :send, :csend
353
354
  # When a method has an argument which is another method with a block,
@@ -68,7 +68,7 @@ module RuboCop
68
68
  PATTERN
69
69
 
70
70
  def on_send(node)
71
- def_node = node.each_ancestor(:def, :defs).first
71
+ def_node = node.each_ancestor(:any_def).first
72
72
  return if def_node &&
73
73
  (allowed_method?(def_node.method_name) ||
74
74
  matches_allowed_pattern?(def_node.method_name))
@@ -50,6 +50,7 @@ module RuboCop
50
50
  check_method_node(node.send_node)
51
51
  end
52
52
  alias on_numblock on_block
53
+ alias on_itblock on_block
53
54
 
54
55
  def on_send(node)
55
56
  return unless implicit_block?(node)
@@ -80,6 +80,7 @@ module RuboCop
80
80
  # rubocop:enable Metrics/CyclomaticComplexity
81
81
 
82
82
  alias on_numblock on_block
83
+ alias on_itblock on_block
83
84
 
84
85
  def on_for(node)
85
86
  return unless node.parent&.begin_type?
@@ -436,9 +436,11 @@ module RuboCop
436
436
  # Helper module to provide common methods to ConditionalAssignment
437
437
  # correctors
438
438
  module ConditionalCorrectorHelper
439
+ # rubocop:disable Metrics/AbcSize
439
440
  def remove_whitespace_in_branches(corrector, branch, condition, column)
440
441
  branch.each_node do |child|
441
442
  next if child.source_range.nil?
443
+ next if child.parent.dstr_type?
442
444
 
443
445
  white_space = white_space_range(child, column)
444
446
  corrector.remove(white_space) if white_space.source.strip.empty?
@@ -450,6 +452,7 @@ module RuboCop
450
452
  corrector.remove_preceding(loc, loc.column - column)
451
453
  end
452
454
  end
455
+ # rubocop:enable Metrics/AbcSize
453
456
 
454
457
  def white_space_range(node, column)
455
458
  expression = node.source_range
@@ -102,7 +102,7 @@ module RuboCop
102
102
 
103
103
  def find_def_node_from_ascendant(node)
104
104
  return unless (parent = node.parent)
105
- return parent if parent.type?(:def, :defs)
105
+ return parent if parent.any_def_type?
106
106
  return node.parent.child_nodes.first if define_method?(parent)
107
107
 
108
108
  find_def_node_from_ascendant(node.parent)
@@ -6,6 +6,10 @@ module RuboCop
6
6
  # Checks for the use of a method, the result of which
7
7
  # would be a literal, like an empty array, hash, or string.
8
8
  #
9
+ # NOTE: When frozen string literals are enabled, `String.new`
10
+ # isn't corrected to an empty string since the former is
11
+ # mutable and the latter would be frozen.
12
+ #
9
13
  # @example
10
14
  # # bad
11
15
  # a = Array.new
@@ -65,7 +65,7 @@ module RuboCop
65
65
  yielding_block?(block_node) do |send_node, block_args, yield_args|
66
66
  return unless yielding_arguments?(block_args, yield_args)
67
67
 
68
- def_node = block_node.each_ancestor(:def, :defs).first
68
+ def_node = block_node.each_ancestor(:any_def).first
69
69
  # if `yield` is being called outside of a method context, ignore
70
70
  # this is not a valid ruby pattern, but can happen in haml or erb,
71
71
  # so this can cause crashes in haml_lint
@@ -151,7 +151,7 @@ module RuboCop
151
151
  end
152
152
 
153
153
  def build_new_arguments_for_zsuper(node)
154
- def_node = node.each_ancestor(:def, :defs).first
154
+ def_node = node.each_ancestor(:any_def).first
155
155
  def_node.arguments.map do |arg|
156
156
  arg.optarg_type? ? arg.node_parts[0] : arg.source
157
157
  end
@@ -77,6 +77,7 @@ module RuboCop
77
77
  end
78
78
 
79
79
  alias on_numblock on_block
80
+ alias on_itblock on_block
80
81
 
81
82
  private
82
83
 
@@ -151,7 +151,7 @@ module RuboCop
151
151
 
152
152
  def frozen_string_literal_comment(processed_source)
153
153
  processed_source.tokens.find do |token|
154
- token.text.start_with?(FROZEN_STRING_LITERAL_REGEXP)
154
+ MagicComment.parse(token.text).frozen_string_literal_specified?
155
155
  end
156
156
  end
157
157
 
@@ -189,8 +189,9 @@ module RuboCop
189
189
 
190
190
  def enable_comment(corrector)
191
191
  comment = frozen_string_literal_comment(processed_source)
192
+ replacement = MagicComment.parse(comment.text).new_frozen_string_literal(true)
192
193
 
193
- corrector.replace(line_range(comment.line), FROZEN_STRING_LITERAL_ENABLED)
194
+ corrector.replace(line_range(comment.line), replacement)
194
195
  end
195
196
 
196
197
  def insert_comment(corrector)
@@ -8,6 +8,9 @@ module RuboCop
8
8
  # reassign (possibly to redirect some stream) constants in Ruby, you'll get
9
9
  # an interpreter warning if you do so.
10
10
  #
11
+ # Additionally, `$stdout/$stderr/$stdin` can safely be accessed in a Ractor because they
12
+ # are ractor-local, while `STDOUT/STDERR/STDIN` will raise `Ractor::IsolationError`.
13
+ #
11
14
  # @safety
12
15
  # Autocorrection is unsafe because `STDOUT` and `$stdout` may point to different
13
16
  # objects, for example.
@@ -135,6 +135,7 @@ module RuboCop
135
135
  on_def(node)
136
136
  end
137
137
  alias on_numblock on_block
138
+ alias on_itblock on_block
138
139
 
139
140
  def on_if(node)
140
141
  return if accepted_form?(node)
@@ -213,7 +214,7 @@ module RuboCop
213
214
  if_branch = node.if_branch
214
215
  else_branch = node.else_branch
215
216
 
216
- corrector.replace(node.loc.begin, "\n") if node.loc.begin&.is?('then')
217
+ corrector.replace(node.loc.begin, "\n") if node.then?
217
218
 
218
219
  if if_branch&.send_type? && heredoc?(if_branch.last_argument)
219
220
  autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
@@ -74,6 +74,7 @@ module RuboCop
74
74
  check_unused_block_args(node, key, value)
75
75
  end
76
76
  alias on_numblock on_block
77
+ alias on_itblock on_block
77
78
 
78
79
  # rubocop:disable Metrics/AbcSize
79
80
  def check_unused_block_args(node, key, value)
@@ -128,8 +129,8 @@ module RuboCop
128
129
  lvar_sources = node.body.each_descendant(:lvar).map(&:source)
129
130
 
130
131
  if block_arg.mlhs_type?
131
- block_arg.each_descendant(:arg, :restarg).all? do |block_arg|
132
- lvar_sources.none?(block_arg.source.delete_prefix('*'))
132
+ block_arg.each_descendant(:arg, :restarg).all? do |descendant|
133
+ lvar_sources.none?(descendant.source.delete_prefix('*'))
133
134
  end
134
135
  else
135
136
  lvar_sources.none?(block_arg.source.delete_prefix('*'))
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Use `Hash#dig` instead of chaining potentially null `fetch` calls.
7
+ #
8
+ # When `fetch(identifier, nil)` calls are chained on a hash, the expectation
9
+ # is that each step in the chain returns either `nil` or another hash,
10
+ # and in both cases, these can be simplified with a single call to `dig` with
11
+ # multiple arguments.
12
+ #
13
+ # If the 2nd parameter is `{}` or `Hash.new`, an offense will also be registered,
14
+ # as long as the final call in the chain is a nil value. If a non-nil value is given,
15
+ # the chain will not be registered as an offense, as the default value cannot be safely
16
+ # given with `dig`.
17
+ #
18
+ # NOTE: See `Style/DigChain` for replacing chains of `dig` calls with
19
+ # a single method call.
20
+ #
21
+ # @safety
22
+ # This cop is unsafe because it cannot be guaranteed that the receiver
23
+ # is a `Hash` or that `fetch` or `dig` have the expected standard implementation.
24
+ #
25
+ # @example
26
+ # # bad
27
+ # hash.fetch('foo', nil)&.fetch('bar', nil)
28
+ #
29
+ # # bad
30
+ # # earlier members of the chain can return `{}` as long as the final `fetch`
31
+ # # has `nil` as a default value
32
+ # hash.fetch('foo', {}).fetch('bar', nil)
33
+ #
34
+ # # good
35
+ # hash.dig('foo', 'bar')
36
+ #
37
+ # # ok - not handled by the cop since the final `fetch` value is non-nil
38
+ # hash.fetch('foo', {}).fetch('bar', {})
39
+ #
40
+ class HashFetchChain < Base
41
+ extend AutoCorrector
42
+ extend TargetRubyVersion
43
+ include IgnoredNode
44
+
45
+ MSG = 'Use `%<replacement>s` instead.'
46
+ RESTRICT_ON_SEND = %i[fetch].freeze
47
+
48
+ minimum_target_ruby_version 2.3
49
+
50
+ # @!method diggable?(node)
51
+ def_node_matcher :diggable?, <<~PATTERN
52
+ (call _ :fetch $_arg {nil (hash) (send (const {nil? cbase} :Hash) :new)})
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ return if ignored_node?(node)
57
+ return if last_fetch_non_nil?(node)
58
+
59
+ last_replaceable_node, arguments = inspect_chain(node)
60
+ return unless last_replaceable_node
61
+ return unless arguments.size > 1
62
+
63
+ range = last_replaceable_node.selector.join(node.loc.end)
64
+ replacement = replacement(arguments)
65
+ message = format(MSG, replacement: replacement)
66
+
67
+ add_offense(range, message: message) do |corrector|
68
+ corrector.replace(range, replacement)
69
+ end
70
+ end
71
+ alias on_csend on_send
72
+
73
+ private
74
+
75
+ def last_fetch_non_nil?(node)
76
+ # When chaining `fetch` methods, `fetch(x, {})` is acceptable within
77
+ # the chain, as long as the last method in the chain has a `nil`
78
+ # default value.
79
+
80
+ return false unless node.method?(:fetch)
81
+
82
+ !node.last_argument&.nil_type?
83
+ end
84
+
85
+ def inspect_chain(node)
86
+ arguments = []
87
+
88
+ while (arg = diggable?(node))
89
+ arguments.unshift(arg)
90
+ ignore_node(node)
91
+ last_replaceable_node = node
92
+ node = node.receiver
93
+ end
94
+
95
+ [last_replaceable_node, arguments]
96
+ end
97
+
98
+ def replacement(arguments)
99
+ values = arguments.map(&:source).join(', ')
100
+ "dig(#{values})"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -76,6 +76,9 @@ module RuboCop
76
76
  # # good
77
77
  # {foo:, bar:}
78
78
  #
79
+ # # good - allowed to mix syntaxes
80
+ # {foo:, bar: baz}
81
+ #
79
82
  # @example EnforcedShorthandSyntax: never
80
83
  #
81
84
  # # bad
@@ -97,34 +97,31 @@ module RuboCop
97
97
  else
98
98
  correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
99
99
  end
100
-
101
- corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
102
- return unless (if_branch = node.if_branch)
103
-
104
- range = range_by_whole_lines(if_branch.source_range, include_final_newline: true)
105
- corrector.remove(range)
106
100
  end
107
101
 
108
102
  def correct_to_elsif_from_modifier_form(corrector, node)
109
- corrector.replace(node.parent.loc.else, <<~RUBY.chop)
110
- elsif #{node.condition.source}
111
- #{indent(node.if_branch)}#{node.if_branch.source}
112
- end
113
- RUBY
103
+ corrector.replace(node.parent.loc.else, "elsif #{node.condition.source}")
104
+
105
+ condition_range = range_between(
106
+ node.if_branch.source_range.end_pos, node.condition.source_range.end_pos
107
+ )
108
+ corrector.remove(condition_range)
114
109
  end
115
110
 
116
- def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
111
+ def correct_to_elsif_from_if_inside_else_form(corrector, node, condition) # rubocop:disable Metrics/AbcSize
117
112
  corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
118
113
 
119
114
  if_condition_range = if_condition_range(node, condition)
120
115
 
121
116
  if (if_branch = node.if_branch)
122
- corrector.replace(if_condition_range, if_branch.source)
117
+ corrector.replace(if_condition_range, range_with_comments(if_branch).source)
118
+ corrector.remove(range_with_comments_and_lines(if_branch))
123
119
  else
124
120
  corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
125
121
  end
126
122
 
127
123
  corrector.remove(condition)
124
+ corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
128
125
  end
129
126
 
130
127
  def then?(node)
@@ -106,6 +106,7 @@ module RuboCop
106
106
  end
107
107
 
108
108
  alias on_numblock on_block
109
+ alias on_itblock on_block
109
110
 
110
111
  private
111
112
 
@@ -89,8 +89,8 @@ module RuboCop
89
89
 
90
90
  def inheritance_check?(node)
91
91
  argument = node.first_argument
92
- node.method?(:<) &&
93
- (argument.const_type? && argument.short_name.to_s.upcase != argument.short_name.to_s)
92
+ node.method?(:<) && argument.const_type? &&
93
+ argument.short_name.to_s.upcase != argument.short_name.to_s
94
94
  end
95
95
 
96
96
  def preferred_condition(node)
@@ -32,7 +32,7 @@ module RuboCop
32
32
 
33
33
  # To try to avoid doing two regex checks on every string,
34
34
  # shortcut out if the string does not look like an IP address
35
- return false unless could_be_ip?(contents)
35
+ return false unless potential_ip?(contents)
36
36
 
37
37
  ::Resolv::IPv4::Regex.match?(contents) || ::Resolv::IPv6::Regex.match?(contents)
38
38
  end
@@ -52,7 +52,7 @@ module RuboCop
52
52
  Array(allowed_addresses).map(&:downcase)
53
53
  end
54
54
 
55
- def could_be_ip?(str)
55
+ def potential_ip?(str)
56
56
  # If the string is too long, it can't be an IP
57
57
  return false if too_long?(str)
58
58
 
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for blocks with one argument where `it` block parameter can be used.
7
+ #
8
+ # It provides three `EnforcedStyle` options:
9
+ #
10
+ # 1. `only_numbered_parameters` (default) ... Detects only numbered block parameters.
11
+ # 2. `always` ... Always uses the `it` block parameter.
12
+ # 3. `disallow` ... Disallows the `it` block parameter.
13
+ #
14
+ # A single numbered parameter is detected when `only_numbered_parameters` or `always`.
15
+ #
16
+ # @example EnforcedStyle: only_numbered_parameters (default)
17
+ # # bad
18
+ # block { do_something(_1) }
19
+ #
20
+ # # good
21
+ # block { do_something(it) }
22
+ # block { |named_param| do_something(named_param) }
23
+ #
24
+ # @example EnforcedStyle: always
25
+ # # bad
26
+ # block { do_something(_1) }
27
+ # block { |named_param| do_something(named_param) }
28
+ #
29
+ # # good
30
+ # block { do_something(it) }
31
+ #
32
+ # @example EnforcedStyle: disallow
33
+ # # bad
34
+ # block { do_something(it) }
35
+ #
36
+ # # good
37
+ # block { do_something(_1) }
38
+ # block { |named_param| do_something(named_param) }
39
+ #
40
+ class ItBlockParameter < Base
41
+ include ConfigurableEnforcedStyle
42
+ extend TargetRubyVersion
43
+ extend AutoCorrector
44
+
45
+ MSG_USE_IT_BLOCK_PARAMETER = 'Use `it` block parameter.'
46
+ MSG_AVOID_IT_BLOCK_PARAMETER = 'Avoid using `it` block parameter.'
47
+
48
+ minimum_target_ruby_version 3.4
49
+
50
+ def on_block(node)
51
+ return unless style == :always
52
+ return unless node.arguments.one?
53
+
54
+ # `restarg`, `kwrestarg`, `blockarg` nodes can return early.
55
+ return unless node.first_argument.arg_type?
56
+
57
+ variables = find_block_variables(node, node.first_argument.source)
58
+
59
+ variables.each do |variable|
60
+ add_offense(variable, message: MSG_USE_IT_BLOCK_PARAMETER) do |corrector|
61
+ corrector.remove(node.arguments)
62
+ corrector.replace(variable, 'it')
63
+ end
64
+ end
65
+ end
66
+
67
+ def on_numblock(node)
68
+ return if style == :disallow
69
+ return unless node.children[1] == 1
70
+
71
+ variables = find_block_variables(node, '_1')
72
+
73
+ variables.each do |variable|
74
+ add_offense(variable, message: MSG_USE_IT_BLOCK_PARAMETER) do |corrector|
75
+ corrector.replace(variable, 'it')
76
+ end
77
+ end
78
+ end
79
+
80
+ def on_itblock(node)
81
+ return unless style == :disallow
82
+
83
+ variables = find_block_variables(node, 'it')
84
+
85
+ variables.each do |variable|
86
+ add_offense(variable, message: MSG_AVOID_IT_BLOCK_PARAMETER)
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def find_block_variables(node, block_argument_name)
93
+ node.each_descendant(:lvar).select do |descendant|
94
+ descendant.source == block_argument_name
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -42,7 +42,7 @@ module RuboCop
42
42
  return if kwarg_nodes.empty?
43
43
 
44
44
  add_offense(node) do |corrector|
45
- defining_node = node.each_ancestor(:def, :defs, :block).first
45
+ defining_node = node.each_ancestor(:any_def, :block).first
46
46
  next if processed_source.contains_comment?(arguments_range(defining_node))
47
47
  next unless node.parent.find(&:kwoptarg_type?) == node
48
48
 
@@ -77,6 +77,7 @@ module RuboCop
77
77
  end
78
78
  end
79
79
  alias on_numblock on_block
80
+ alias on_itblock on_block
80
81
 
81
82
  private
82
83