rubocop 1.25.1 → 1.27.0

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 (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