rubocop 1.25.1 → 1.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +29 -6
  4. data/lib/rubocop/cli.rb +1 -1
  5. data/lib/rubocop/config_obsoletion/extracted_cop.rb +3 -1
  6. data/lib/rubocop/cop/autocorrect_logic.rb +4 -0
  7. data/lib/rubocop/cop/badge.rb +7 -1
  8. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -5
  9. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -5
  10. data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -3
  11. data/lib/rubocop/cop/generator.rb +2 -7
  12. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +46 -0
  13. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  14. data/lib/rubocop/cop/layout/case_indentation.rb +1 -1
  15. data/lib/rubocop/cop/layout/indentation_width.rb +1 -2
  16. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +7 -8
  17. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -4
  18. data/lib/rubocop/cop/lint/ambiguous_operator.rb +6 -6
  19. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  20. data/lib/rubocop/cop/lint/empty_in_pattern.rb +3 -1
  21. data/lib/rubocop/cop/lint/empty_when.rb +3 -1
  22. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +11 -4
  23. data/lib/rubocop/cop/lint/inherit_exception.rb +19 -28
  24. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +8 -1
  25. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -2
  26. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +5 -0
  27. data/lib/rubocop/cop/lint/refinement_import_methods.rb +51 -0
  28. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +10 -0
  29. data/lib/rubocop/cop/lint/symbol_conversion.rb +3 -2
  30. data/lib/rubocop/cop/lint/syntax.rb +1 -2
  31. data/lib/rubocop/cop/lint/unused_method_argument.rb +1 -1
  32. data/lib/rubocop/cop/lint/useless_times.rb +13 -9
  33. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -2
  34. data/lib/rubocop/cop/mixin/comments_help.rb +22 -2
  35. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +2 -3
  36. data/lib/rubocop/cop/mixin/line_length_help.rb +17 -6
  37. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +4 -3
  38. data/lib/rubocop/cop/mixin/surrounding_space.rb +4 -2
  39. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  40. data/lib/rubocop/cop/security/yaml_load.rb +9 -3
  41. data/lib/rubocop/cop/style/def_with_parentheses.rb +16 -11
  42. data/lib/rubocop/cop/style/double_negation.rb +32 -1
  43. data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
  44. data/lib/rubocop/cop/style/file_write.rb +12 -0
  45. data/lib/rubocop/cop/style/for.rb +4 -0
  46. data/lib/rubocop/cop/style/lambda_call.rb +12 -20
  47. data/lib/rubocop/cop/style/nested_file_dirname.rb +66 -0
  48. data/lib/rubocop/cop/style/optional_boolean_parameter.rb +3 -2
  49. data/lib/rubocop/cop/style/raise_args.rb +5 -2
  50. data/lib/rubocop/cop/style/redundant_capital_w.rb +1 -2
  51. data/lib/rubocop/cop/style/redundant_initialize.rb +119 -0
  52. data/lib/rubocop/cop/style/safe_navigation.rb +12 -7
  53. data/lib/rubocop/cop/style/select_by_regexp.rb +6 -1
  54. data/lib/rubocop/cop/style/sole_nested_conditional.rb +50 -12
  55. data/lib/rubocop/cop/style/string_concatenation.rb +7 -1
  56. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -2
  57. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +1 -1
  58. data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +1 -1
  59. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -4
  60. data/lib/rubocop/cop/style/unless_else.rb +4 -0
  61. data/lib/rubocop/cop/variable_force.rb +1 -5
  62. data/lib/rubocop/cops_documentation_generator.rb +2 -2
  63. data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -2
  64. data/lib/rubocop/formatter/offense_count_formatter.rb +6 -2
  65. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
  66. data/lib/rubocop/options.rb +8 -2
  67. data/lib/rubocop/result_cache.rb +9 -1
  68. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  69. data/lib/rubocop/runner.rb +1 -1
  70. data/lib/rubocop/target_ruby.rb +1 -1
  71. data/lib/rubocop/version.rb +1 -1
  72. data/lib/rubocop.rb +3 -0
  73. metadata +9 -5
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks if `include` or `prepend` is called in `refine` block.
7
+ # These methods are deprecated and should be replaced with `Refinement#import_methods`.
8
+ #
9
+ # It emulates deprecation warnings in Ruby 3.1.
10
+ #
11
+ # @safety
12
+ # This cop's autocorrection is unsafe because `include M` will affect the included class
13
+ # if any changes are made to module `M`.
14
+ # On the other hand, `import_methods M` uses a snapshot of method definitions,
15
+ # thus it will not be affected if module `M` changes.
16
+ #
17
+ # @example
18
+ #
19
+ # # bad
20
+ # refine Foo do
21
+ # include Bar
22
+ # end
23
+ #
24
+ # # bad
25
+ # refine Foo do
26
+ # prepend Bar
27
+ # end
28
+ #
29
+ # # good
30
+ # refine Foo do
31
+ # import_methods Bar
32
+ # end
33
+ #
34
+ class RefinementImportMethods < Base
35
+ extend TargetRubyVersion
36
+
37
+ MSG = 'Use `import_methods` instead of `%<current>s` because it is deprecated in Ruby 3.1.'
38
+ RESTRICT_ON_SEND = %i[include prepend].freeze
39
+
40
+ minimum_target_ruby_version 3.1
41
+
42
+ def on_send(node)
43
+ return if node.receiver
44
+ return unless node.parent.block_type? && node.parent.method?(:refine)
45
+
46
+ add_offense(node.loc.selector, message: format(MSG, current: node.method_name))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -57,10 +57,20 @@ module RuboCop
57
57
 
58
58
  outer_local_variable = variable_table.find_variable(variable.name)
59
59
  return unless outer_local_variable
60
+ return if same_conditions_node_different_branch?(variable, outer_local_variable)
60
61
 
61
62
  message = format(MSG, variable: variable.name)
62
63
  add_offense(variable.declaration_node, message: message)
63
64
  end
65
+
66
+ def same_conditions_node_different_branch?(variable, outer_local_variable)
67
+ variable_node = variable.scope.node.parent
68
+ return false unless variable_node.conditional?
69
+
70
+ outer_local_variable_node = outer_local_variable.scope.node
71
+
72
+ outer_local_variable_node.conditional? && variable_node == outer_local_variable_node
73
+ end
64
74
  end
65
75
  end
66
76
  end
@@ -147,13 +147,14 @@ module RuboCop
147
147
  # will be ignored.
148
148
  return unless node.value.to_s.match?(/\A[a-z0-9_]/i)
149
149
 
150
- correction = node.value.inspect.delete_prefix(':')
150
+ correction = node.value.inspect
151
+ correction = correction.delete_prefix(':') if node.parent.colon?
151
152
  return if properly_quoted?(node.source, correction)
152
153
 
153
154
  register_offense(
154
155
  node,
155
156
  correction: correction,
156
- message: format(MSG, correction: "#{correction}:")
157
+ message: format(MSG, correction: node.parent.colon? ? "#{correction}:" : correction)
157
158
  )
158
159
  end
159
160
 
@@ -9,8 +9,7 @@ module RuboCop
9
9
  def on_other_file
10
10
  add_offense_from_error(processed_source.parser_error) if processed_source.parser_error
11
11
  processed_source.diagnostics.each do |diagnostic|
12
- add_offense_from_diagnostic(diagnostic,
13
- processed_source.ruby_version)
12
+ add_offense_from_diagnostic(diagnostic, processed_source.ruby_version)
14
13
  end
15
14
  super
16
15
  end
@@ -64,7 +64,7 @@ module RuboCop
64
64
 
65
65
  # @!method not_implemented?(node)
66
66
  def_node_matcher :not_implemented?, <<~PATTERN
67
- {(send nil? :raise (const {nil? cbase} :NotImplementedError))
67
+ {(send nil? :raise (const {nil? cbase} :NotImplementedError) ...)
68
68
  (send nil? :fail ...)}
69
69
  PATTERN
70
70
 
@@ -51,20 +51,24 @@ module RuboCop
51
51
  node = node.block_node if node.block_literal?
52
52
 
53
53
  add_offense(node, message: format(MSG, count: count)) do |corrector|
54
- next unless own_line?(node)
55
-
56
- if never_process?(count, node)
57
- remove_node(corrector, node)
58
- elsif !proc_name.empty?
59
- autocorrect_block_pass(corrector, node, proc_name)
60
- else
61
- autocorrect_block(corrector, node)
62
- end
54
+ next if !own_line?(node) || node.parent&.send_type?
55
+
56
+ autocorrect(corrector, count, node, proc_name)
63
57
  end
64
58
  end
65
59
 
66
60
  private
67
61
 
62
+ def autocorrect(corrector, count, node, proc_name)
63
+ if never_process?(count, node)
64
+ remove_node(corrector, node)
65
+ elsif !proc_name.empty?
66
+ autocorrect_block_pass(corrector, node, proc_name)
67
+ else
68
+ autocorrect_block(corrector, node)
69
+ end
70
+ end
71
+
68
72
  def never_process?(count, node)
69
73
  count < 1 || (node.block_type? && node.body.nil?)
70
74
  end
@@ -101,8 +101,7 @@ module RuboCop
101
101
  children = node.masgn_type? ? node.children[0].children : node.children
102
102
 
103
103
  will_be_miscounted = children.count do |child|
104
- child.respond_to?(:setter_method?) &&
105
- !child.setter_method?
104
+ child.respond_to?(:setter_method?) && !child.setter_method?
106
105
  end
107
106
  @assignment += will_be_miscounted
108
107
 
@@ -4,8 +4,6 @@ module RuboCop
4
4
  module Cop
5
5
  # Help methods for working with nodes containing comments.
6
6
  module CommentsHelp
7
- include VisibilityHelp
8
-
9
7
  def source_range_with_comment(node)
10
8
  begin_pos = begin_pos_with_comment(node)
11
9
  end_pos = end_position_for(node)
@@ -13,6 +11,13 @@ module RuboCop
13
11
  Parser::Source::Range.new(buffer, begin_pos, end_pos)
14
12
  end
15
13
 
14
+ def contains_comments?(node)
15
+ start_line = node.source_range.line
16
+ end_line = find_end_line(node)
17
+
18
+ processed_source.each_comment_in_lines(start_line...end_line).any?
19
+ end
20
+
16
21
  private
17
22
 
18
23
  def end_position_for(node)
@@ -37,6 +42,21 @@ module RuboCop
37
42
  def buffer
38
43
  processed_source.buffer
39
44
  end
45
+
46
+ # Returns the end line of a node, which might be a comment and not part of the AST
47
+ # End line is considered either the line at which another node starts, or
48
+ # the line at which the parent node ends.
49
+ def find_end_line(node)
50
+ if node.if_type? && node.loc.else
51
+ node.loc.else.line
52
+ elsif (next_sibling = node.right_sibling)
53
+ next_sibling.loc.line
54
+ elsif (parent = node.parent)
55
+ parent.loc.end.line
56
+ else
57
+ node.loc.end.line
58
+ end
59
+ end
40
60
  end
41
61
  end
42
62
  end
@@ -41,7 +41,7 @@ module RuboCop
41
41
  end
42
42
 
43
43
  def require_hash_value?(hash_key_source, node)
44
- return true if require_hash_value_for_around_hash_literal?(node)
44
+ return true if !node.key.sym_type? || require_hash_value_for_around_hash_literal?(node)
45
45
 
46
46
  hash_value = node.value
47
47
  return true unless hash_value.send_type? || hash_value.lvar_type?
@@ -65,9 +65,8 @@ module RuboCop
65
65
 
66
66
  def use_modifier_form_without_parenthesized_method_call?(ancestor)
67
67
  return false if ancestor.respond_to?(:parenthesized?) && ancestor.parenthesized?
68
- return false unless (parent = ancestor.parent)
69
68
 
70
- parent.respond_to?(:modifier_form?) && parent.modifier_form?
69
+ ancestor.ancestors.any? { |node| node.respond_to?(:modifier_form?) && node.modifier_form? }
71
70
  end
72
71
 
73
72
  def without_parentheses_call_expr_follows?(ancestor)
@@ -39,12 +39,7 @@ module RuboCop
39
39
  pos + indentation_difference(line)
40
40
  end
41
41
 
42
- # Extend the end position until the start of the next word, if any.
43
- # This allows for URIs that are wrapped in quotes or parens to be handled properly
44
- # while not allowing additional words to be added after the URL.
45
- if (match = line[end_position..line_length(line)]&.match(/^\S+(?=\s|$)/))
46
- end_position += match.offset(0).last
47
- end
42
+ end_position = extend_uri_end_position(line, end_position)
48
43
 
49
44
  return nil if begin_position < max_line_length && end_position < max_line_length
50
45
 
@@ -65,6 +60,22 @@ module RuboCop
65
60
  (line.index(/[^\t]/) || 0) * (tab_indentation_width - 1)
66
61
  end
67
62
 
63
+ def extend_uri_end_position(line, end_position)
64
+ # Extend the end position YARD comments with linked URLs of the form {<uri> <title>}
65
+ if line&.match(/{(\s|\S)*}$/)
66
+ match = line[end_position..line_length(line)]&.match(/(\s|\S)*}/)
67
+ end_position += match.offset(0).last
68
+ end
69
+
70
+ # Extend the end position until the start of the next word, if any.
71
+ # This allows for URIs that are wrapped in quotes or parens to be handled properly
72
+ # while not allowing additional words to be added after the URL.
73
+ if (match = line[end_position..line_length(line)]&.match(/^\S+(?=\s|$)/))
74
+ end_position += match.offset(0).last
75
+ end
76
+ end_position
77
+ end
78
+
68
79
  def tab_indentation_width
69
80
  config.for_cop('Layout/IndentationStyle')['IndentationWidth'] ||
70
81
  config.for_cop('Layout/IndentationWidth')['Width']
@@ -29,7 +29,9 @@ module RuboCop
29
29
  # b c { block }. <-- b is indented relative to a
30
30
  # d <-- d is indented relative to a
31
31
  def left_hand_side(lhs)
32
- lhs = lhs.parent while lhs.parent&.send_type? && lhs.parent.loc.dot
32
+ while lhs.parent&.send_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method?
33
+ lhs = lhs.parent
34
+ end
33
35
  lhs
34
36
  end
35
37
 
@@ -194,8 +196,7 @@ module RuboCop
194
196
 
195
197
  def not_for_this_cop?(node)
196
198
  node.ancestors.any? do |ancestor|
197
- grouped_expression?(ancestor) ||
198
- inside_arg_list_parentheses?(node, ancestor)
199
+ grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor)
199
200
  end
200
201
  end
201
202
 
@@ -44,7 +44,8 @@ module RuboCop
44
44
  if extra_space?(left_token, :left) && !start_ok
45
45
  space_offense(node, left_token, :right, message, NO_SPACE_COMMAND)
46
46
  end
47
- return if !extra_space?(right_token, :right) || end_ok
47
+ return if (!extra_space?(right_token, :right) || end_ok) ||
48
+ (autocorrect_with_disable_uncorrectable? && !start_ok)
48
49
 
49
50
  space_offense(node, right_token, :left, message, NO_SPACE_COMMAND)
50
51
  end
@@ -58,7 +59,8 @@ module RuboCop
58
59
  unless extra_space?(left_token, :left) || start_ok
59
60
  space_offense(node, left_token, :none, message, SPACE_COMMAND)
60
61
  end
61
- return if extra_space?(right_token, :right) || end_ok
62
+ return if (extra_space?(right_token, :right) || end_ok) ||
63
+ (autocorrect_with_disable_uncorrectable? && !start_ok)
62
64
 
63
65
  space_offense(node, right_token, :none, message, SPACE_COMMAND)
64
66
  end
@@ -107,7 +107,7 @@ module RuboCop
107
107
  def use_block_argument_as_local_variable?(node, last_argument)
108
108
  return if node.body.nil?
109
109
 
110
- node.body.each_descendant(:lvar).any? do |lvar|
110
+ node.body.each_descendant(:lvar, :lvasgn).any? do |lvar|
111
111
  !lvar.parent.block_pass_type? && lvar.source == last_argument
112
112
  end
113
113
  end
@@ -7,17 +7,21 @@ module RuboCop
7
7
  # potential security issues leading to remote code execution when
8
8
  # loading from an untrusted source.
9
9
  #
10
+ # NOTE: Ruby 3.1+ (Psych 4) uses `Psych.load` as `Psych.safe_load` by default.
11
+ #
10
12
  # @safety
11
13
  # The behaviour of the code might change depending on what was
12
14
  # in the YAML payload, since `YAML.safe_load` is more restrictive.
13
15
  #
14
16
  # @example
15
17
  # # bad
16
- # YAML.load("--- foo")
18
+ # YAML.load("--- !ruby/object:Foo {}") # Psych 3 is unsafe by default
17
19
  #
18
20
  # # good
19
- # YAML.safe_load("--- foo")
20
- # YAML.dump("foo")
21
+ # YAML.safe_load("--- !ruby/object:Foo {}", [Foo]) # Ruby 2.5 (Psych 3)
22
+ # YAML.safe_load("--- !ruby/object:Foo {}", permitted_classes: [Foo]) # Ruby 3.0- (Psych 3)
23
+ # YAML.load("--- !ruby/object:Foo {}", permitted_classes: [Foo]) # Ruby 3.1+ (Psych 4)
24
+ # YAML.dump(foo)
21
25
  #
22
26
  class YAMLLoad < Base
23
27
  extend AutoCorrector
@@ -31,6 +35,8 @@ module RuboCop
31
35
  PATTERN
32
36
 
33
37
  def on_send(node)
38
+ return if target_ruby_version >= 3.1
39
+
34
40
  yaml_load(node) do
35
41
  add_offense(node.loc.selector) do |corrector|
36
42
  corrector.replace(node.loc.selector, 'safe_load')
@@ -11,27 +11,33 @@ module RuboCop
11
11
  #
12
12
  # # bad
13
13
  # def foo()
14
- # # does a thing
14
+ # do_something
15
15
  # end
16
16
  #
17
17
  # # good
18
18
  # def foo
19
- # # does a thing
19
+ # do_something
20
20
  # end
21
21
  #
22
- # # also good
23
- # def foo() does_a_thing end
22
+ # # bad
23
+ # def foo() = do_something
24
+ #
25
+ # # good
26
+ # def foo = do_something
27
+ #
28
+ # # good (without parentheses it's a syntax error)
29
+ # def foo() do_something end
24
30
  #
25
31
  # @example
26
32
  #
27
33
  # # bad
28
34
  # def Baz.foo()
29
- # # does a thing
35
+ # do_something
30
36
  # end
31
37
  #
32
38
  # # good
33
39
  # def Baz.foo
34
- # # does a thing
40
+ # do_something
35
41
  # end
36
42
  class DefWithParentheses < Base
37
43
  extend AutoCorrector
@@ -39,12 +45,11 @@ module RuboCop
39
45
  MSG = "Omit the parentheses in defs when the method doesn't accept any arguments."
40
46
 
41
47
  def on_def(node)
42
- return if node.single_line?
43
- return unless !node.arguments? && (node_arguments_loc_begin = node.arguments.loc.begin)
48
+ return if node.single_line? && !node.endless?
49
+ return unless !node.arguments? && (node_arguments = node.arguments.source_range)
44
50
 
45
- add_offense(node_arguments_loc_begin) do |corrector|
46
- corrector.remove(node_arguments_loc_begin)
47
- corrector.remove(node.arguments.loc.end)
51
+ add_offense(node_arguments) do |corrector|
52
+ corrector.remove(node_arguments)
48
53
  end
49
54
  end
50
55
  alias on_defs on_def
@@ -72,9 +72,14 @@ module RuboCop
72
72
  def end_of_method_definition?(node)
73
73
  return false unless (def_node = find_def_node_from_ascendant(node))
74
74
 
75
+ conditional_node = find_conditional_node_from_ascendant(node)
75
76
  last_child = find_last_child(def_node.body)
76
77
 
77
- last_child.last_line == node.last_line
78
+ if conditional_node
79
+ double_negative_condition_return_value?(node, last_child, conditional_node)
80
+ else
81
+ last_child.last_line == node.last_line
82
+ end
78
83
  end
79
84
 
80
85
  def find_def_node_from_ascendant(node)
@@ -84,6 +89,13 @@ module RuboCop
84
89
  find_def_node_from_ascendant(node.parent)
85
90
  end
86
91
 
92
+ def find_conditional_node_from_ascendant(node)
93
+ return unless (parent = node.parent)
94
+ return parent if parent.conditional?
95
+
96
+ find_conditional_node_from_ascendant(parent)
97
+ end
98
+
87
99
  def find_last_child(node)
88
100
  case node.type
89
101
  when :rescue
@@ -94,6 +106,25 @@ module RuboCop
94
106
  node.child_nodes.last
95
107
  end
96
108
  end
109
+
110
+ def double_negative_condition_return_value?(node, last_child, conditional_node)
111
+ parent = find_parent_not_enumerable(node)
112
+ if parent.begin_type?
113
+ node.loc.line == parent.loc.last_line
114
+ else
115
+ last_child.last_line <= conditional_node.last_line
116
+ end
117
+ end
118
+
119
+ def find_parent_not_enumerable(node)
120
+ return unless (parent = node.parent)
121
+
122
+ if parent.pair_type? || parent.hash_type? || parent.array_type?
123
+ find_parent_not_enumerable(parent)
124
+ else
125
+ parent
126
+ end
127
+ end
97
128
  end
98
129
  end
99
130
  end
@@ -47,8 +47,7 @@ module RuboCop
47
47
  branch_bodies = [*case_node.when_branches.map(&:body), case_node.else_branch].compact
48
48
 
49
49
  return if branch_bodies.any? do |body|
50
- body.return_type? ||
51
- body.each_descendant.any?(&:return_type?)
50
+ body.return_type? || body.each_descendant.any?(&:return_type?)
52
51
  end
53
52
 
54
53
  add_offense(case_node.loc.keyword) { |corrector| autocorrect(corrector, case_node) }
@@ -5,6 +5,17 @@ module RuboCop
5
5
  module Style
6
6
  # Favor `File.(bin)write` convenience methods.
7
7
  #
8
+ # NOTE: There are different method signatures between `File.write` (class method)
9
+ # and `File#write` (instance method). The following case will be allowed because
10
+ # static analysis does not know the contents of the splat argument:
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # File.open(filename, 'w') do |f|
15
+ # f.write(*objects)
16
+ # end
17
+ # ----
18
+ #
8
19
  # @example
9
20
  # ## text mode
10
21
  # # bad
@@ -85,6 +96,7 @@ module RuboCop
85
96
  content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
86
97
  write_arg if block_arg == lvar
87
98
  end
99
+ return false if content&.splat_type?
88
100
 
89
101
  yield(content) if content
90
102
  end
@@ -38,6 +38,10 @@ module RuboCop
38
38
  # end
39
39
  # end
40
40
  #
41
+ # @safety
42
+ # This cop's autocorrection is unsafe because the scope of
43
+ # variables is different between `each` and `for`.
44
+ #
41
45
  class For < Base
42
46
  include ConfigurableEnforcedStyle
43
47
  include RangeHelp
@@ -22,45 +22,37 @@ module RuboCop
22
22
  include ConfigurableEnforcedStyle
23
23
  extend AutoCorrector
24
24
 
25
+ MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
25
26
  RESTRICT_ON_SEND = %i[call].freeze
26
27
 
27
28
  def on_send(node)
28
29
  return unless node.receiver
29
30
 
30
31
  if offense?(node)
31
- add_offense(node) do |corrector|
32
+ prefer = prefer(node)
33
+ current = node.source
34
+
35
+ add_offense(node, message: format(MSG, prefer: prefer, current: current)) do |corrector|
32
36
  opposite_style_detected
33
- autocorrect(corrector, node)
37
+ corrector.replace(node, prefer)
34
38
  end
35
39
  else
36
40
  correct_style_detected
37
41
  end
38
42
  end
39
43
 
40
- def autocorrect(corrector, node)
41
- if explicit_style?
42
- receiver = node.receiver.source
43
- replacement = node.source.sub("#{receiver}.", "#{receiver}.call")
44
-
45
- corrector.replace(node, replacement)
46
- else
47
- add_parentheses(node, corrector) unless node.parenthesized?
48
- corrector.remove(node.loc.selector)
49
- end
50
- end
51
-
52
44
  private
53
45
 
54
46
  def offense?(node)
55
47
  (explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
56
48
  end
57
49
 
58
- def message(_node)
59
- if explicit_style?
60
- 'Prefer the use of `lambda.call(...)` over `lambda.(...)`.'
61
- else
62
- 'Prefer the use of `lambda.(...)` over `lambda.call(...)`.'
63
- end
50
+ def prefer(node)
51
+ receiver = node.receiver.source
52
+ arguments = node.arguments.map(&:source).join(', ')
53
+ method = explicit_style? ? "call(#{arguments})" : "(#{arguments})"
54
+
55
+ "#{receiver}.#{method}"
64
56
  end
65
57
 
66
58
  def implicit_style?
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for nested `File.dirname`.
7
+ # It replaces nested `File.dirname` with the level argument introduced in Ruby 3.1.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # File.dirname(File.dirname(path))
13
+ #
14
+ # # good
15
+ # File.dirname(path, 2)
16
+ #
17
+ class NestedFileDirname < Base
18
+ include RangeHelp
19
+ extend AutoCorrector
20
+ extend TargetRubyVersion
21
+
22
+ MSG = 'Use `dirname(%<path>s, %<level>s)` instead.'
23
+ RESTRICT_ON_SEND = %i[dirname].freeze
24
+
25
+ minimum_target_ruby_version 3.1
26
+
27
+ # @!method file_dirname?(node)
28
+ def_node_matcher :file_dirname?, <<~PATTERN
29
+ (send
30
+ (const {cbase nil?} :File) :dirname ...)
31
+ PATTERN
32
+
33
+ def on_send(node)
34
+ return if file_dirname?(node.parent) || !file_dirname?(node.first_argument)
35
+
36
+ path, level = path_with_dir_level(node, 1)
37
+ return if level < 2
38
+
39
+ message = format(MSG, path: path, level: level)
40
+ range = offense_range(node)
41
+
42
+ add_offense(range, message: message) do |corrector|
43
+ corrector.replace(range, "dirname(#{path}, #{level})")
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def path_with_dir_level(node, level)
50
+ first_argument = node.first_argument
51
+
52
+ if file_dirname?(first_argument)
53
+ level += 1
54
+ path_with_dir_level(first_argument, level)
55
+ else
56
+ [first_argument.source, level]
57
+ end
58
+ end
59
+
60
+ def offense_range(node)
61
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -54,8 +54,9 @@ module RuboCop
54
54
  private
55
55
 
56
56
  def format_message(argument)
57
- source = argument.source
58
- format(MSG, original: source, replacement: source.sub(/\s+=/, ':'))
57
+ replacement = "#{argument.name}: #{argument.default_value.source}"
58
+
59
+ format(MSG, original: argument.source, replacement: replacement)
59
60
  end
60
61
  end
61
62
  end
@@ -107,8 +107,7 @@ module RuboCop
107
107
 
108
108
  first_arg = node.first_argument
109
109
 
110
- return unless first_arg.send_type? && first_arg.method?(:new)
111
- return if acceptable_exploded_args?(first_arg.arguments)
110
+ return if !use_new_method?(first_arg) || acceptable_exploded_args?(first_arg.arguments)
112
111
 
113
112
  return if allowed_non_exploded_type?(first_arg)
114
113
 
@@ -120,6 +119,10 @@ module RuboCop
120
119
  end
121
120
  end
122
121
 
122
+ def use_new_method?(first_arg)
123
+ first_arg.send_type? && first_arg.receiver && first_arg.method?(:new)
124
+ end
125
+
123
126
  def acceptable_exploded_args?(args)
124
127
  # Allow code like `raise Ex.new(arg1, arg2)`.
125
128
  return true if args.size > 1