rubocop 1.23.0 → 1.25.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -3
  4. data/config/default.yml +60 -12
  5. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  6. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  7. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  9. data/lib/rubocop/cli.rb +1 -0
  10. data/lib/rubocop/config_loader_resolver.rb +1 -1
  11. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  12. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  13. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  14. data/lib/rubocop/cop/documentation.rb +19 -2
  15. data/lib/rubocop/cop/gemspec/require_mfa.rb +8 -10
  16. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +10 -3
  17. data/lib/rubocop/cop/generator.rb +4 -3
  18. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +47 -0
  19. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  20. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  21. data/lib/rubocop/cop/layout/argument_alignment.rb +36 -9
  22. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  23. data/lib/rubocop/cop/layout/dot_position.rb +4 -0
  24. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +5 -1
  25. data/lib/rubocop/cop/layout/hash_alignment.rb +7 -2
  26. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  27. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  29. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +5 -1
  30. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  31. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  32. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  33. data/lib/rubocop/cop/lint/each_with_object_argument.rb +1 -1
  34. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +10 -5
  35. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +7 -4
  36. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  37. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  38. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  39. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  40. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  41. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +1 -1
  42. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  43. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  44. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +82 -0
  45. data/lib/rubocop/cop/naming/block_forwarding.rb +121 -0
  46. data/lib/rubocop/cop/naming/method_parameter_name.rb +1 -1
  47. data/lib/rubocop/cop/security/open.rb +11 -1
  48. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  49. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  50. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  51. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  52. data/lib/rubocop/cop/style/file_read.rb +112 -0
  53. data/lib/rubocop/cop/style/file_write.rb +124 -0
  54. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  55. data/lib/rubocop/cop/style/hash_syntax.rb +36 -0
  56. data/lib/rubocop/cop/style/hash_transform_keys.rb +6 -6
  57. data/lib/rubocop/cop/style/hash_transform_values.rb +6 -6
  58. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  59. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
  60. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  61. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -0
  62. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -1
  63. data/lib/rubocop/cop/style/method_def_parentheses.rb +17 -13
  64. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  65. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  66. data/lib/rubocop/cop/style/redundant_begin.rb +2 -6
  67. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  68. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  69. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  70. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  71. data/lib/rubocop/cop/style/sample.rb +5 -3
  72. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  73. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  74. data/lib/rubocop/cop/style/swap_values.rb +2 -0
  75. data/lib/rubocop/cop/style/ternary_parentheses.rb +16 -2
  76. data/lib/rubocop/cop/team.rb +1 -1
  77. data/lib/rubocop/cop/util.rb +9 -1
  78. data/lib/rubocop/formatter/disabled_config_formatter.rb +16 -2
  79. data/lib/rubocop/options.rb +6 -1
  80. data/lib/rubocop/remote_config.rb +1 -3
  81. data/lib/rubocop/result_cache.rb +1 -1
  82. data/lib/rubocop/version.rb +1 -1
  83. data/lib/rubocop.rb +7 -0
  84. metadata +15 -7
@@ -32,6 +32,19 @@ module RuboCop
32
32
  # true
33
33
  # end
34
34
  #
35
+ # @example AllowForAlignment: false (default)
36
+ # # bad
37
+ # a = 1 # A really long comment
38
+ # # spanning two lines.
39
+ #
40
+ # # good
41
+ # # A really long comment spanning one line.
42
+ # a = 1
43
+ #
44
+ # @example AllowForAlignment: true
45
+ # # good
46
+ # a = 1 # A really long comment
47
+ # # spanning two lines.
35
48
  class CommentIndentation < Base
36
49
  include Alignment
37
50
  extend AutoCorrector
@@ -40,7 +53,7 @@ module RuboCop
40
53
  'instead of %<correct_comment_indentation>d).'
41
54
 
42
55
  def on_new_investigation
43
- processed_source.comments.each { |comment| check(comment) }
56
+ processed_source.comments.each_with_index { |comment, ix| check(comment, ix) }
44
57
  end
45
58
 
46
59
  private
@@ -76,7 +89,7 @@ module RuboCop
76
89
  AlignmentCorrector.correct(corrector, processed_source, comment, @column_delta)
77
90
  end
78
91
 
79
- def check(comment)
92
+ def check(comment, comment_index)
80
93
  return unless own_line_comment?(comment)
81
94
 
82
95
  next_line = line_after_comment(comment)
@@ -94,11 +107,27 @@ module RuboCop
94
107
  return if column == correct_comment_indentation
95
108
  end
96
109
 
110
+ return if correctly_aligned_with_preceding_comment?(comment_index, column)
111
+
97
112
  add_offense(comment, message: message(column, correct_comment_indentation)) do |corrector|
98
113
  autocorrect(corrector, comment)
99
114
  end
100
115
  end
101
116
 
117
+ # Returns true if:
118
+ # a) the cop is configured to allow extra indentation for alignment, and
119
+ # b) the currently inspected comment is aligned with the nearest preceding end-of-line
120
+ # comment.
121
+ def correctly_aligned_with_preceding_comment?(comment_index, column)
122
+ return false unless cop_config['AllowForAlignment']
123
+
124
+ processed_source.comments[0...comment_index].reverse_each do |other_comment|
125
+ return other_comment.loc.column == column unless own_line_comment?(other_comment)
126
+ end
127
+
128
+ false
129
+ end
130
+
102
131
  def message(column, correct_comment_indentation)
103
132
  format(MSG, column: column, correct_comment_indentation: correct_comment_indentation)
104
133
  end
@@ -27,6 +27,10 @@ module RuboCop
27
27
  include RangeHelp
28
28
  extend AutoCorrector
29
29
 
30
+ def self.autocorrect_incompatible_with
31
+ [Style::RedundantSelf]
32
+ end
33
+
30
34
  def on_send(node)
31
35
  return unless node.dot? || ampersand_dot?(node)
32
36
 
@@ -81,7 +81,7 @@ module RuboCop
81
81
 
82
82
  locations.each do |loc|
83
83
  line = loc.line
84
- next if line == line_of_def_or_kwbegin
84
+ next if line == line_of_def_or_kwbegin || last_rescue_and_end_on_same_line(body)
85
85
 
86
86
  keyword = loc.source
87
87
  # below the keyword
@@ -91,6 +91,10 @@ module RuboCop
91
91
  end
92
92
  end
93
93
 
94
+ def last_rescue_and_end_on_same_line(body)
95
+ body.rescue_type? && body.resbody_branches.last.loc.line == body.parent.loc.end.line
96
+ end
97
+
94
98
  def message(location, keyword)
95
99
  format(MSG, location: location, keyword: keyword)
96
100
  end
@@ -222,11 +222,16 @@ module RuboCop
222
222
  node.pairs.any? &&
223
223
  node.parent&.call_type?
224
224
 
225
+ left_sibling = argument_before_hash(node)
225
226
  parent_loc = node.parent.loc
226
- selector = parent_loc.selector || parent_loc.expression
227
+ selector = left_sibling || parent_loc.selector || parent_loc.expression
227
228
  same_line?(selector, node.pairs.first)
228
229
  end
229
230
 
231
+ def argument_before_hash(hash_node)
232
+ hash_node.left_sibling.respond_to?(:loc) ? hash_node.left_sibling : nil
233
+ end
234
+
230
235
  def reset!
231
236
  self.offenses_by = {}
232
237
  self.column_deltas = Hash.new { |hash, key| hash[key] = {} }
@@ -313,7 +318,7 @@ module RuboCop
313
318
  # just give each lambda the same reference and they would all get the
314
319
  # last value of each. A local variable fixes the problem.
315
320
 
316
- if node.value
321
+ if node.value && node.respond_to?(:value_omission?) && !node.value_omission?
317
322
  correct_key_value(corrector, delta, node.key.source_range,
318
323
  node.value.source_range,
319
324
  node.loc.operator)
@@ -143,7 +143,7 @@ module RuboCop
143
143
  return true
144
144
  end
145
145
 
146
- do_keyword_line == selector.line && rescue_keyword_column == selector.column
146
+ do_keyword_line == selector&.line && rescue_keyword_column == selector.column
147
147
  end
148
148
 
149
149
  def aligned_with_leading_dot?(do_keyword_line, send_node_loc, rescue_keyword_column)
@@ -19,7 +19,7 @@ module RuboCop
19
19
  MSG = 'Space missing after colon.'
20
20
 
21
21
  def on_pair(node)
22
- return unless node.colon?
22
+ return if !node.colon? || node.value_omission?
23
23
 
24
24
  colon = node.loc.operator
25
25
 
@@ -28,6 +28,10 @@ module RuboCop
28
28
 
29
29
  MSG = 'Put one space between the method name and the first argument.'
30
30
 
31
+ def self.autocorrect_incompatible_with
32
+ [Style::MethodCallWithArgsParentheses]
33
+ end
34
+
31
35
  def on_send(node)
32
36
  return unless regular_method_call_with_arguments?(node)
33
37
 
@@ -30,7 +30,11 @@ module RuboCop
30
30
 
31
31
  def on_new_investigation
32
32
  processed_source.diagnostics.each do |diagnostic|
33
- next unless diagnostic.reason == :ambiguous_literal
33
+ if target_ruby_version >= 3.0
34
+ next unless diagnostic.reason == :ambiguous_regexp
35
+ else
36
+ next unless diagnostic.reason == :ambiguous_literal
37
+ end
34
38
 
35
39
  offense_node = find_offense_node_by(diagnostic)
36
40
 
@@ -92,7 +92,7 @@ module RuboCop
92
92
  private
93
93
 
94
94
  def method_name(node)
95
- node.ancestors.find(&:block_type?).send_node.method_name
95
+ node.ancestors.find(&:block_type?).method_name
96
96
  end
97
97
  end
98
98
  end
@@ -12,6 +12,7 @@ module RuboCop
12
12
  # File.exists?(some_path)
13
13
  # Dir.exists?(some_path)
14
14
  # iterator?
15
+ # ENV.freeze # Calling `Env.freeze` raises `TypeError` since Ruby 2.7.
15
16
  # Socket.gethostbyname(host)
16
17
  # Socket.gethostbyaddr(host)
17
18
  #
@@ -22,6 +23,7 @@ module RuboCop
22
23
  # File.exist?(some_path)
23
24
  # Dir.exist?(some_path)
24
25
  # block_given?
26
+ # ENV # `ENV.freeze` cannot prohibit changes to environment variables.
25
27
  # Addrinfo.getaddrinfo(nodename, service)
26
28
  # Addrinfo.tcp(host, port).getnameinfo
27
29
  class DeprecatedClassMethods < Base
@@ -106,6 +108,9 @@ module RuboCop
106
108
 
107
109
  DeprecatedClassMethod.new(:iterator?) => Replacement.new(:block_given?),
108
110
 
111
+ DeprecatedClassMethod.new(:freeze, class_constant: :ENV) =>
112
+ Replacement.new(nil, class_constant: :ENV),
113
+
109
114
  DeprecatedClassMethod.new(:gethostbyaddr, class_constant: :Socket, correctable: false) =>
110
115
  Replacement.new(:getnameinfo, class_constant: :Addrinfo, instance_method: true),
111
116
 
@@ -120,11 +125,18 @@ module RuboCop
120
125
 
121
126
  def on_send(node)
122
127
  check(node) do |deprecated|
123
- message = format(MSG, current: deprecated, prefer: replacement(deprecated))
128
+ prefer = replacement(deprecated)
129
+ message = format(MSG, current: deprecated, prefer: prefer)
130
+ current_method = node.loc.selector
131
+
132
+ add_offense(current_method, message: message) do |corrector|
133
+ next unless deprecated.correctable?
124
134
 
125
- add_offense(node.loc.selector, message: message) do |corrector|
126
- if deprecated.correctable?
127
- corrector.replace(node.loc.selector, replacement(deprecated).method)
135
+ if (preferred_method = prefer.method)
136
+ corrector.replace(current_method, preferred_method)
137
+ else
138
+ corrector.remove(node.loc.dot)
139
+ corrector.remove(current_method)
128
140
  end
129
141
  end
130
142
  end
@@ -55,8 +55,14 @@ module RuboCop
55
55
  ...)
56
56
  PATTERN
57
57
 
58
+ # @!method digest_const?(node)
59
+ def_node_matcher :digest_const?, <<~PATTERN
60
+ (const _ :Digest)
61
+ PATTERN
62
+
58
63
  def on_send(node)
59
64
  return if node.arguments.any? { |arg| arg.variable? || arg.send_type? || arg.const_type? }
65
+ return if digest_const?(node.receiver)
60
66
  return unless algorithm_const(node)
61
67
 
62
68
  message = message(node)
@@ -27,7 +27,7 @@ module RuboCop
27
27
 
28
28
  # @!method each_with_object?(node)
29
29
  def_node_matcher :each_with_object?, <<~PATTERN
30
- ({send csend} _ :each_with_object $_)
30
+ (call _ :each_with_object $_)
31
31
  PATTERN
32
32
 
33
33
  def on_send(node)
@@ -20,6 +20,10 @@ module RuboCop
20
20
  # # good
21
21
  # io.wait_writable(timeout)
22
22
  #
23
+ # @safety
24
+ # This cop's autocorrection is unsafe because `NoMethodError` occurs
25
+ # if `require 'io/wait'` is not called.
26
+ #
23
27
  class IncompatibleIoSelectWithFiberScheduler < Base
24
28
  extend AutoCorrector
25
29
 
@@ -29,11 +33,12 @@ module RuboCop
29
33
  # @!method io_select(node)
30
34
  def_node_matcher :io_select, <<~PATTERN
31
35
  (send
32
- (const {nil? cbase} :IO) :select $_ $_ {(array) nil} $...)
36
+ (const {nil? cbase} :IO) :select $...)
33
37
  PATTERN
34
38
 
35
39
  def on_send(node)
36
- return unless (read, write, timeout = io_select(node))
40
+ read, write, _excepts, timeout = *io_select(node)
41
+ return unless read
37
42
  return unless scheduler_compatible?(read, write) || scheduler_compatible?(write, read)
38
43
 
39
44
  preferred = preferred_method(read, write, timeout)
@@ -47,13 +52,13 @@ module RuboCop
47
52
  private
48
53
 
49
54
  def scheduler_compatible?(io1, io2)
50
- return false unless io1.array_type? && io1.values.size == 1
55
+ return false unless io1&.array_type? && io1.values.size == 1
51
56
 
52
- io2.array_type? ? io2.values.empty? : io2.nil_type?
57
+ io2&.array_type? ? io2.values.empty? : (io2.nil? || io2.nil_type?)
53
58
  end
54
59
 
55
60
  def preferred_method(read, write, timeout)
56
- timeout_argument = timeout.empty? ? '' : "(#{timeout[0].source})"
61
+ timeout_argument = timeout.nil? ? '' : "(#{timeout.source})"
57
62
 
58
63
  if read.array_type? && read.values[0]
59
64
  "#{read.values[0].source}.wait_readable#{timeout_argument}"
@@ -41,7 +41,11 @@ module RuboCop
41
41
  end
42
42
 
43
43
  node.operator_method? || node.setter_method? || chained_calls?(node) ||
44
- operator_keyword?(node) || node.first_argument.hash_type?
44
+ valid_first_argument?(node.first_argument)
45
+ end
46
+
47
+ def valid_first_argument?(first_arg)
48
+ first_arg.operator_keyword? || first_arg.hash_type? || ternary_expression?(first_arg)
45
49
  end
46
50
 
47
51
  def first_argument_starts_with_left_parenthesis?(node)
@@ -53,9 +57,8 @@ module RuboCop
53
57
  first_argument.send_type? && (node.children.last&.children&.count || 0) > 1
54
58
  end
55
59
 
56
- def operator_keyword?(node)
57
- first_argument = node.first_argument
58
- first_argument.operator_keyword?
60
+ def ternary_expression?(node)
61
+ node.if_type? && node.ternary?
59
62
  end
60
63
 
61
64
  def spaces_before_left_parenthesis(node)
@@ -50,6 +50,7 @@ module RuboCop
50
50
 
51
51
  check_code_length(node)
52
52
  end
53
+ alias on_numblock on_block
53
54
 
54
55
  private
55
56
 
@@ -46,15 +46,6 @@ module RuboCop
46
46
  1
47
47
  end
48
48
 
49
- def block_method(node)
50
- case node.type
51
- when :block
52
- node.method_name
53
- when :block_pass
54
- node.parent.method_name
55
- end
56
- end
57
-
58
49
  def count_block?(block)
59
50
  KNOWN_ITERATING_METHODS.include? block.method_name
60
51
  end
@@ -52,6 +52,7 @@ module RuboCop
52
52
 
53
53
  check_code_length(node)
54
54
  end
55
+ alias on_numblock on_block
55
56
 
56
57
  private
57
58
 
@@ -44,7 +44,7 @@ module RuboCop
44
44
 
45
45
  # @!method module_definition?(node)
46
46
  def_node_matcher :module_definition?, <<~PATTERN
47
- (casgn nil? _ (block (send (const {nil? cbase} :Module) :new) ...))
47
+ (casgn nil? _ ({block numblock} (send (const {nil? cbase} :Module) :new) ...))
48
48
  PATTERN
49
49
 
50
50
  def message(length, max_length)
@@ -135,7 +135,7 @@ module RuboCop
135
135
 
136
136
  def extract_body(node)
137
137
  case node.type
138
- when :class, :module, :block, :def, :defs
138
+ when :class, :module, :block, :numblock, :def, :defs
139
139
  node.body
140
140
  when :casgn
141
141
  _scope, _name, value = *node
@@ -59,7 +59,7 @@ module RuboCop
59
59
 
60
60
  # @!method attribute_call?(node)
61
61
  def_node_matcher :attribute_call?, <<~PATTERN
62
- ( {csend send} _receiver _method # and no parameters
62
+ (call _receiver _method # and no parameters
63
63
  )
64
64
  PATTERN
65
65
 
@@ -13,6 +13,11 @@ module RuboCop
13
13
  # @api private
14
14
  module EnforceSuperclass
15
15
  def self.included(base)
16
+ warn Rainbow(
17
+ '`RuboCop::Cop::EnforceSuperclass` is deprecated and will be removed in RuboCop 2.0. ' \
18
+ 'Please upgrade to RuboCop Rails 2.9 or newer to continue.'
19
+ ).yellow
20
+
16
21
  # @!method class_definition(node)
17
22
  base.def_node_matcher :class_definition, <<~PATTERN
18
23
  (class (const _ !:#{base::SUPERCLASS}) #{base::BASE_PATTERN} ...)
@@ -43,7 +43,7 @@ module RuboCop
43
43
  end
44
44
 
45
45
  def value_delta(pair)
46
- return 0 if pair.value_on_new_line?
46
+ return 0 if pair.value_on_new_line? || pair.value_omission?
47
47
 
48
48
  correct_value_column = pair.loc.operator.end.column + 1
49
49
  actual_value_column = pair.value.loc.column
@@ -111,7 +111,8 @@ module RuboCop
111
111
  correct_value_column = first_pair.key.loc.column +
112
112
  current_pair.delimiter(true).length +
113
113
  max_key_width
114
- correct_value_column - current_pair.value.loc.column
114
+
115
+ current_pair.value_omission? ? 0 : correct_value_column - current_pair.value.loc.column
115
116
  end
116
117
  end
117
118
 
@@ -134,7 +135,7 @@ module RuboCop
134
135
  end
135
136
 
136
137
  def value_delta(first_pair, current_pair)
137
- first_pair.value_delta(current_pair)
138
+ current_pair.value_omission? ? 0 : first_pair.value_delta(current_pair)
138
139
  end
139
140
  end
140
141
 
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # This module checks for Ruby 3.1's hash value omission syntax.
6
+ module HashShorthandSyntax
7
+ OMIT_HASH_VALUE_MSG = 'Omit the hash value.'
8
+ EXPLICIT_HASH_VALUE_MSG = 'Explicit the hash value.'
9
+
10
+ def on_pair(node)
11
+ return if ignore_hash_shorthand_syntax?(node)
12
+
13
+ hash_key_source = node.key.source
14
+
15
+ if enforced_shorthand_syntax == 'always'
16
+ return if node.value_omission? || require_hash_value?(hash_key_source, node)
17
+
18
+ message = OMIT_HASH_VALUE_MSG
19
+ replacement = "#{hash_key_source}:"
20
+ else
21
+ return unless node.value_omission?
22
+
23
+ message = EXPLICIT_HASH_VALUE_MSG
24
+ replacement = "#{hash_key_source}: #{hash_key_source}"
25
+ end
26
+
27
+ add_offense(node.value, message: message) do |corrector|
28
+ corrector.replace(node, replacement)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def ignore_hash_shorthand_syntax?(pair_node)
35
+ target_ruby_version <= 3.0 || enforced_shorthand_syntax == 'either' ||
36
+ !pair_node.parent.hash_type?
37
+ end
38
+
39
+ def enforced_shorthand_syntax
40
+ cop_config.fetch('EnforcedShorthandSyntax', 'always')
41
+ end
42
+
43
+ def require_hash_value?(hash_key_source, node)
44
+ return true if require_hash_value_for_around_hash_literal?(node)
45
+
46
+ hash_value = node.value
47
+ return true unless hash_value.send_type? || hash_value.lvar_type?
48
+
49
+ hash_key_source != hash_value.source || hash_key_source.end_with?('!', '?')
50
+ end
51
+
52
+ def require_hash_value_for_around_hash_literal?(node)
53
+ return false unless (ancestor = node.parent.parent)
54
+ return false if ancestor.send_type? && ancestor.method?(:[])
55
+
56
+ !node.parent.braces? && !use_element_of_hash_literal_as_receiver?(ancestor, node.parent) &&
57
+ (use_modifier_form_without_parenthesized_method_call?(ancestor) ||
58
+ without_parentheses_call_expr_follows?(ancestor))
59
+ end
60
+
61
+ def use_element_of_hash_literal_as_receiver?(ancestor, parent)
62
+ # `{value:}.do_something` is a valid syntax.
63
+ ancestor.send_type? && ancestor.receiver == parent
64
+ end
65
+
66
+ def use_modifier_form_without_parenthesized_method_call?(ancestor)
67
+ return false if ancestor.respond_to?(:parenthesized?) && ancestor.parenthesized?
68
+ return false unless (parent = ancestor.parent)
69
+
70
+ parent.respond_to?(:modifier_form?) && parent.modifier_form?
71
+ end
72
+
73
+ def without_parentheses_call_expr_follows?(ancestor)
74
+ right_sibling = ancestor.right_sibling
75
+ right_sibling ||= ancestor.each_ancestor.find(&:assignment?)&.right_sibling
76
+ return false unless right_sibling
77
+
78
+ ancestor.respond_to?(:parenthesized?) && !ancestor.parenthesized? && !!right_sibling
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Naming
6
+ # In Ruby 3.1, anonymous block forwarding has been added.
7
+ #
8
+ # This cop identifies places where `do_something(&block)` can be replaced
9
+ # by `do_something(&)`.
10
+ #
11
+ # It also supports the opposite style by alternative `explicit` option.
12
+ # You can specify the block variable name for auto-correction with `BlockForwardingName`.
13
+ # The default variable name is `block`. If the name is already in use, it will not be
14
+ # auto-corrected.
15
+ #
16
+ # @example EnforcedStyle: anonymous (default)
17
+ #
18
+ # # bad
19
+ # def foo(&block)
20
+ # bar(&block)
21
+ # end
22
+ #
23
+ # # good
24
+ # def foo(&)
25
+ # bar(&)
26
+ # end
27
+ #
28
+ # @example EnforcedStyle: explicit
29
+ #
30
+ # # bad
31
+ # def foo(&)
32
+ # bar(&)
33
+ # end
34
+ #
35
+ # # good
36
+ # def foo(&block)
37
+ # bar(&block)
38
+ # end
39
+ #
40
+ class BlockForwarding < Base
41
+ include ConfigurableEnforcedStyle
42
+ include RangeHelp
43
+ extend AutoCorrector
44
+ extend TargetRubyVersion
45
+
46
+ minimum_target_ruby_version 3.1
47
+
48
+ MSG = 'Use %<style>s block forwarding.'
49
+
50
+ def on_def(node)
51
+ return if node.arguments.empty?
52
+
53
+ last_argument = node.arguments.last
54
+ return if expected_block_forwarding_style?(node, last_argument)
55
+
56
+ register_offense(last_argument, node)
57
+
58
+ node.each_descendant(:block_pass) do |block_pass_node|
59
+ next if block_pass_node.children.first&.sym_type? ||
60
+ last_argument.source != block_pass_node.source
61
+
62
+ register_offense(block_pass_node, node)
63
+ end
64
+ end
65
+ alias on_defs on_def
66
+
67
+ private
68
+
69
+ def expected_block_forwarding_style?(node, last_argument)
70
+ if style == :anonymous
71
+ !explicit_block_argument?(last_argument) ||
72
+ use_kwarg_in_method_definition?(node) ||
73
+ use_block_argument_as_local_variable?(node, last_argument.source[1..-1])
74
+ else
75
+ !anonymous_block_argument?(last_argument)
76
+ end
77
+ end
78
+
79
+ def use_kwarg_in_method_definition?(node)
80
+ node.arguments.each_descendant(:kwarg, :kwoptarg).any?
81
+ end
82
+
83
+ def anonymous_block_argument?(node)
84
+ node.blockarg_type? && node.name.nil?
85
+ end
86
+
87
+ def explicit_block_argument?(node)
88
+ node.blockarg_type? && !node.name.nil?
89
+ end
90
+
91
+ def register_offense(block_argument, node)
92
+ add_offense(block_argument, message: format(MSG, style: style)) do |corrector|
93
+ if style == :anonymous
94
+ corrector.replace(block_argument, '&')
95
+
96
+ arguments = block_argument.parent
97
+
98
+ add_parentheses(arguments, corrector) unless arguments.parenthesized_call?
99
+ else
100
+ unless use_block_argument_as_local_variable?(node, block_forwarding_name)
101
+ corrector.replace(block_argument, "&#{block_forwarding_name}")
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def use_block_argument_as_local_variable?(node, last_argument)
108
+ return if node.body.nil?
109
+
110
+ node.body.each_descendant(:lvar).any? do |lvar|
111
+ !lvar.parent.block_pass_type? && lvar.source == last_argument
112
+ end
113
+ end
114
+
115
+ def block_forwarding_name
116
+ cop_config.fetch('BlockForwardingName', 'block')
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -26,7 +26,7 @@ module RuboCop
26
26
  # num1 * num2
27
27
  # end
28
28
  #
29
- # # With `MinArgNameLength` set to number greater than 1
29
+ # # With `MinNameLength` set to number greater than 1
30
30
  # def baz(a, b, c)
31
31
  # do_stuff(a, b, c)
32
32
  # end