rubocop 1.0.0 → 1.4.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -16
  3. data/config/default.yml +165 -19
  4. data/exe/rubocop +1 -1
  5. data/lib/rubocop.rb +17 -0
  6. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  7. data/lib/rubocop/cli/command/execute_runner.rb +26 -11
  8. data/lib/rubocop/comment_config.rb +1 -1
  9. data/lib/rubocop/config_loader.rb +14 -5
  10. data/lib/rubocop/config_regeneration.rb +1 -1
  11. data/lib/rubocop/cop/bundler/duplicated_gem.rb +26 -6
  12. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  13. data/lib/rubocop/cop/commissioner.rb +10 -10
  14. data/lib/rubocop/cop/corrector.rb +3 -1
  15. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  16. data/lib/rubocop/cop/force.rb +1 -1
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  18. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  19. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  20. data/lib/rubocop/cop/generator.rb +2 -9
  21. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  22. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +1 -1
  23. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  24. data/lib/rubocop/cop/layout/class_structure.rb +15 -3
  25. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  26. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  27. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +77 -7
  28. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  29. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  30. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  32. data/lib/rubocop/cop/layout/line_length.rb +8 -1
  33. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  34. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  35. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  36. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  37. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +2 -1
  38. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +26 -2
  39. data/lib/rubocop/cop/lint/debugger.rb +17 -28
  40. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  41. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  42. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  43. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  44. data/lib/rubocop/cop/lint/empty_block.rb +82 -0
  45. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  46. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  47. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +39 -7
  48. data/lib/rubocop/cop/lint/loop.rb +4 -4
  49. data/lib/rubocop/cop/lint/missing_super.rb +7 -4
  50. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  51. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  52. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  53. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  54. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
  55. data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
  56. data/lib/rubocop/cop/lint/to_enum_arguments.rb +86 -0
  57. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +194 -0
  58. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  59. data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
  60. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  61. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  62. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  63. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
  64. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  65. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  66. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  67. data/lib/rubocop/cop/mixin/visibility_help.rb +1 -3
  68. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +11 -1
  69. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  70. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  71. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  72. data/lib/rubocop/cop/naming/variable_number.rb +98 -8
  73. data/lib/rubocop/cop/offense.rb +3 -3
  74. data/lib/rubocop/cop/style/and_or.rb +1 -3
  75. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  76. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  77. data/lib/rubocop/cop/style/case_like_if.rb +0 -4
  78. data/lib/rubocop/cop/style/collection_compact.rb +91 -0
  79. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +169 -0
  80. data/lib/rubocop/cop/style/documentation.rb +12 -1
  81. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  82. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  83. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  84. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  85. data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
  86. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  87. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  88. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  89. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  90. data/lib/rubocop/cop/style/multiple_comparison.rb +55 -7
  91. data/lib/rubocop/cop/style/negated_if_else_condition.rb +106 -0
  92. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  93. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  94. data/lib/rubocop/cop/style/redundant_argument.rb +73 -0
  95. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  96. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  97. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  98. data/lib/rubocop/cop/style/static_class.rb +97 -0
  99. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  100. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  101. data/lib/rubocop/cop/team.rb +6 -1
  102. data/lib/rubocop/cop/util.rb +6 -2
  103. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  104. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  105. data/lib/rubocop/ext/regexp_node.rb +17 -9
  106. data/lib/rubocop/ext/regexp_parser.rb +84 -0
  107. data/lib/rubocop/formatter/disabled_config_formatter.rb +21 -6
  108. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  109. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  110. data/lib/rubocop/magic_comment.rb +2 -2
  111. data/lib/rubocop/options.rb +7 -0
  112. data/lib/rubocop/rake_task.rb +2 -2
  113. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  114. data/lib/rubocop/runner.rb +1 -1
  115. data/lib/rubocop/target_finder.rb +1 -1
  116. data/lib/rubocop/target_ruby.rb +65 -1
  117. data/lib/rubocop/version.rb +1 -1
  118. metadata +22 -10
  119. data/assets/logo.png +0 -0
  120. data/assets/output.html.erb +0 -261
  121. data/bin/console +0 -10
  122. data/bin/rubocop-profile +0 -32
  123. data/bin/setup +0 -7
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # puts $1 # => foo
20
20
  #
21
21
  class OutOfRangeRegexpRef < Base
22
- MSG = 'Do not use out of range reference for the Regexp.'
22
+ MSG = '$%<backref>s is out of range (%<count>s regexp capture %<group>s detected).'
23
23
 
24
24
  REGEXP_RECEIVER_METHODS = %i[=~ === match].to_set.freeze
25
25
  REGEXP_ARGUMENT_METHODS = %i[=~ match grep gsub gsub! sub sub! [] slice slice! index rindex
@@ -35,14 +35,13 @@ module RuboCop
35
35
  check_regexp(node.children.first)
36
36
  end
37
37
 
38
- def on_send(node)
38
+ def after_send(node)
39
39
  @valid_ref = nil
40
40
 
41
- if node.receiver&.regexp_type?
42
- check_regexp(node.receiver)
43
- elsif node.first_argument&.regexp_type? \
44
- && REGEXP_ARGUMENT_METHODS.include?(node.method_name)
41
+ if regexp_first_argument?(node)
45
42
  check_regexp(node.first_argument)
43
+ elsif regexp_receiver?(node)
44
+ check_regexp(node.receiver)
46
45
  end
47
46
  end
48
47
 
@@ -56,9 +55,16 @@ module RuboCop
56
55
 
57
56
  def on_nth_ref(node)
58
57
  backref, = *node
59
- return if @valid_ref.nil?
58
+ return if @valid_ref.nil? || backref <= @valid_ref
59
+
60
+ message = format(
61
+ MSG,
62
+ backref: backref,
63
+ count: @valid_ref.zero? ? 'no' : @valid_ref,
64
+ group: @valid_ref == 1 ? 'group' : 'groups'
65
+ )
60
66
 
61
- add_offense(node) if backref > @valid_ref
67
+ add_offense(node, message: message)
62
68
  end
63
69
 
64
70
  private
@@ -73,6 +79,19 @@ module RuboCop
73
79
  node.each_capture(named: false).count
74
80
  end
75
81
  end
82
+
83
+ def regexp_first_argument?(send_node)
84
+ send_node.first_argument&.regexp_type? \
85
+ && REGEXP_ARGUMENT_METHODS.include?(send_node.method_name)
86
+ end
87
+
88
+ def regexp_receiver?(send_node)
89
+ send_node.receiver&.regexp_type?
90
+ end
91
+
92
+ def nth_ref_receiver?(send_node)
93
+ send_node.receiver&.nth_ref_type?
94
+ end
76
95
  end
77
96
  end
78
97
  end
@@ -88,31 +88,34 @@ module RuboCop
88
88
  begin_pos = reposition(source, begin_pos, -1)
89
89
  end_pos = reposition(source, end_pos, 1)
90
90
 
91
- comma_pos =
92
- if source[begin_pos - 1] == ','
93
- :before
94
- elsif source[end_pos] == ','
95
- :after
96
- else
97
- :none
98
- end
99
-
100
- range_to_remove(begin_pos, end_pos, comma_pos, comment)
91
+ range_to_remove(begin_pos, end_pos, comment)
101
92
  end
102
93
 
103
- def range_to_remove(begin_pos, end_pos, comma_pos, comment)
94
+ def range_to_remove(begin_pos, end_pos, comment)
104
95
  start = comment_start(comment)
96
+ source = comment.loc.expression.source
105
97
 
106
- case comma_pos
107
- when :before
108
- range_between(start + begin_pos - 1, start + end_pos)
109
- when :after
110
- range_between(start + begin_pos, start + end_pos + 1)
98
+ if source[begin_pos - 1] == ','
99
+ range_with_comma_before(start, begin_pos, end_pos)
100
+ elsif source[end_pos] == ','
101
+ range_with_comma_after(comment, start, begin_pos, end_pos)
111
102
  else
112
103
  range_between(start, comment.loc.expression.end_pos)
113
104
  end
114
105
  end
115
106
 
107
+ def range_with_comma_before(start, begin_pos, end_pos)
108
+ range_between(start + begin_pos - 1, start + end_pos)
109
+ end
110
+
111
+ # If the list of cops is comma-separated, but without a empty space after the comma,
112
+ # we should **not** remove the prepending empty space, thus begin_pos += 1
113
+ def range_with_comma_after(comment, start, begin_pos, end_pos)
114
+ begin_pos += 1 if comment.loc.expression.source[end_pos + 1] != ' '
115
+
116
+ range_between(start + begin_pos, start + end_pos + 1)
117
+ end
118
+
116
119
  def all_or_name(name)
117
120
  name == 'all' ? 'all cops' : name
118
121
  end
@@ -140,11 +140,10 @@ module RuboCop
140
140
  rescued_groups.each_cons(2).all? do |x, y|
141
141
  if x.include?(Exception)
142
142
  false
143
- elsif y.include?(Exception)
144
- true
145
- elsif x.none? || y.none?
146
- # consider sorted if a group is empty or only contains
147
- # `nil`s
143
+ elsif y.include?(Exception) ||
144
+ # consider sorted if a group is empty or only contains
145
+ # `nil`s
146
+ x.none? || y.none?
148
147
  true
149
148
  else
150
149
  (x <=> y || 0) <= 0
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop ensures that `to_enum`/`enum_for`, called for the current method,
7
+ # has correct arguments.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def foo(x, y = 1)
12
+ # return to_enum(__callee__, x) # `y` is missing
13
+ # end
14
+ #
15
+ # # good
16
+ # def foo(x, y = 1)
17
+ # return to_enum(__callee__, x, y)
18
+ # # alternatives to `__callee__` are `__method__` and `:foo`
19
+ # end
20
+ #
21
+ class ToEnumArguments < Base
22
+ MSG = 'Ensure you correctly provided all the arguments.'
23
+
24
+ RESTRICT_ON_SEND = %i[to_enum enum_for].freeze
25
+
26
+ def_node_matcher :enum_conversion_call?, <<~PATTERN
27
+ (send {nil? self} {:to_enum :enum_for} $_ $...)
28
+ PATTERN
29
+
30
+ def_node_matcher :method_name?, <<~PATTERN
31
+ {(send nil? {:__method__ :__callee__}) (sym %1)}
32
+ PATTERN
33
+
34
+ def_node_matcher :passing_keyword_arg?, <<~PATTERN
35
+ (pair (sym %1) (lvar %1))
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ def_node = node.each_ancestor(:def, :defs).first
40
+ return unless def_node
41
+
42
+ enum_conversion_call?(node) do |method_node, arguments|
43
+ add_offense(node) unless method_name?(method_node, def_node.method_name) &&
44
+ arguments_match?(arguments, def_node)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def arguments_match?(arguments, def_node)
51
+ index = 0
52
+
53
+ def_node.arguments.reject(&:blockarg_type?).all? do |def_arg|
54
+ send_arg = arguments[index]
55
+ case def_arg.type
56
+ when :arg, :restarg, :optarg
57
+ index += 1
58
+ end
59
+
60
+ send_arg && argument_match?(send_arg, def_arg)
61
+ end
62
+ end
63
+
64
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
65
+ def argument_match?(send_arg, def_arg)
66
+ def_arg_name = def_arg.children[0]
67
+
68
+ case def_arg.type
69
+ when :arg, :restarg
70
+ send_arg.source == def_arg.source
71
+ when :optarg
72
+ send_arg.source == def_arg_name.to_s
73
+ when :kwoptarg, :kwarg
74
+ send_arg.hash_type? &&
75
+ send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) }
76
+ when :kwrestarg
77
+ send_arg.each_child_node(:kwsplat).any? { |child| child.source == def_arg.source }
78
+ when :forward_arg
79
+ send_arg.forwarded_args_type?
80
+ end
81
+ end
82
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Looks for `reduce` or `inject` blocks where the value returned (implicitly or
7
+ # explicitly) does not include the accumulator. A block is considered valid as
8
+ # long as at least one return value includes the accumulator.
9
+ #
10
+ # If the accumulator is not included in the return value, then the entire
11
+ # block will just return a transformation of the last element value, and
12
+ # could be rewritten as such without a loop.
13
+ #
14
+ # Also catches instances where an index of the accumulator is returned, as
15
+ # this may change the type of object being retained.
16
+ #
17
+ # NOTE: For the purpose of reducing false positives, this cop only flags
18
+ # returns in `reduce` blocks where the element is the only variable in
19
+ # the expression (since we will not be able to tell what other variables
20
+ # relate to via static analysis).
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # (1..4).reduce(0) do |acc, el|
26
+ # el * 2
27
+ # end
28
+ #
29
+ # # bad, may raise a NoMethodError after the first iteration
30
+ # %w(a b c).reduce({}) do |acc, letter|
31
+ # acc[letter] = true
32
+ # end
33
+ #
34
+ # # good
35
+ # (1..4).reduce(0) do |acc, el|
36
+ # acc + el * 2
37
+ # end
38
+ #
39
+ # # good, element is returned but modified using the accumulator
40
+ # values.reduce do |acc, el|
41
+ # el << acc
42
+ # el
43
+ # end
44
+ #
45
+ # # good, returns the accumulator instead of the index
46
+ # %w(a b c).reduce({}) do |acc, letter|
47
+ # acc[letter] = true
48
+ # acc
49
+ # end
50
+ #
51
+ # # good, at least one branch returns the accumulator
52
+ # values.reduce(nil) do |result, value|
53
+ # break result if something?
54
+ # value
55
+ # end
56
+ #
57
+ # # good, recursive
58
+ # keys.reduce(self) { |result, key| result[key] }
59
+ #
60
+ # # ignored as the return value cannot be determined
61
+ # enum.reduce do |acc, el|
62
+ # x = foo(acc, el)
63
+ # bar(x)
64
+ # end
65
+ class UnmodifiedReduceAccumulator < Base
66
+ MSG = 'Ensure the accumulator `%<accum>s` will be modified by `%<method>s`.'
67
+ MSG_INDEX = 'Do not return an element of the accumulator in `%<method>s`.'
68
+
69
+ def_node_matcher :reduce_with_block?, <<~PATTERN
70
+ (block (send _recv {:reduce :inject} ...) (args arg+) ...)
71
+ PATTERN
72
+
73
+ def_node_matcher :accumulator_index?, <<~PATTERN
74
+ (send (lvar %1) {:[] :[]=} ...)
75
+ PATTERN
76
+
77
+ def_node_search :element_modified?, <<~PATTERN
78
+ {
79
+ (send _receiver !{:[] :[]=} <`(lvar %1) `_ ...>) # method(el, ...)
80
+ (send (lvar %1) _message <{ivar gvar cvar lvar send} ...>) # el.method(...)
81
+ (lvasgn %1 _) # el = ...
82
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1) ... _) # el += ...
83
+ }
84
+ PATTERN
85
+
86
+ def_node_matcher :lvar_used?, <<~PATTERN
87
+ {
88
+ (lvar %1)
89
+ (lvasgn %1 ...)
90
+ (send (lvar %1) :<< ...)
91
+ (dstr (begin (lvar %1)))
92
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1))
93
+ }
94
+ PATTERN
95
+
96
+ def_node_search :expression_values, <<~PATTERN
97
+ {
98
+ (%RuboCop::AST::Node::VARIABLES $_)
99
+ (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_ ...)
100
+ (send (%RuboCop::AST::Node::VARIABLES $_) :<< ...)
101
+ $(send _ _)
102
+ (dstr (begin {(%RuboCop::AST::Node::VARIABLES $_)}))
103
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_) ...)
104
+ }
105
+ PATTERN
106
+
107
+ def on_block(node)
108
+ return unless reduce_with_block?(node)
109
+
110
+ check_return_values(node)
111
+ end
112
+
113
+ private
114
+
115
+ # Return values in a block are either the value given to next,
116
+ # the last line of a multiline block, or the only line of the block
117
+ def return_values(block_body_node)
118
+ nodes = [block_body_node.begin_type? ? block_body_node.child_nodes.last : block_body_node]
119
+
120
+ block_body_node.each_descendant(:next, :break) do |n|
121
+ # Ignore `next`/`break` inside an inner block
122
+ next if n.each_ancestor(:block).first != block_body_node.parent
123
+ next unless n.first_argument
124
+
125
+ nodes << n.first_argument
126
+ end
127
+
128
+ nodes
129
+ end
130
+
131
+ def check_return_values(block_node)
132
+ return_values = return_values(block_node.body)
133
+ accumulator_name = block_arg_name(block_node, 0)
134
+ element_name = block_arg_name(block_node, 1)
135
+ message_opts = { method: block_node.method_name, accum: accumulator_name }
136
+
137
+ if (node = returned_accumulator_index(return_values, accumulator_name, element_name))
138
+ add_offense(node, message: format(MSG_INDEX, message_opts))
139
+ elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
140
+ return_values.each do |return_val|
141
+ unless acceptable_return?(return_val, element_name)
142
+ add_offense(return_val, message: format(MSG, message_opts))
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def block_arg_name(node, index)
149
+ node.arguments[index].node_parts[0]
150
+ end
151
+
152
+ # Look for an index of the accumulator being returned, except where the index
153
+ # is the element.
154
+ # This is always an offense, in order to try to catch potential exceptions
155
+ # due to type mismatches
156
+ def returned_accumulator_index(return_values, accumulator_name, element_name)
157
+ return_values.detect do |val|
158
+ next unless accumulator_index?(val, accumulator_name)
159
+ next true if val.method?(:[]=)
160
+
161
+ val.arguments.none? { |arg| lvar_used?(arg, element_name) }
162
+ end
163
+ end
164
+
165
+ def potential_offense?(return_values, block_body, element_name, accumulator_name)
166
+ !(element_modified?(block_body, element_name) ||
167
+ returns_accumulator_anywhere?(return_values, accumulator_name))
168
+ end
169
+
170
+ # If the accumulator is used in any return value, the node is acceptable since
171
+ # the accumulator has a chance to change each iteration
172
+ def returns_accumulator_anywhere?(return_values, accumulator_name)
173
+ return_values.any? { |node| lvar_used?(node, accumulator_name) }
174
+ end
175
+
176
+ # Determine if a return value is acceptable for the purposes of this cop
177
+ # If it is an expression containing the accumulator, it is acceptable
178
+ # Otherwise, it is only unacceptable if it contains the iterated element, since we
179
+ # otherwise do not have enough information to prevent false positives.
180
+ def acceptable_return?(return_val, element_name)
181
+ vars = expression_values(return_val).uniq
182
+ return true if vars.none? || (vars - [element_name]).any?
183
+
184
+ false
185
+ end
186
+
187
+ # Exclude `begin` nodes inside a `dstr` from being collected by `return_values`
188
+ def allowed_type?(parent_node)
189
+ !parent_node.dstr_type?
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -255,7 +255,7 @@ module RuboCop
255
255
  PATTERN
256
256
  end
257
257
 
258
- send(matcher_name, child)
258
+ public_send(matcher_name, child)
259
259
  end
260
260
  end
261
261
 
@@ -279,7 +279,7 @@ module RuboCop
279
279
  PATTERN
280
280
  end
281
281
 
282
- send(matcher_name, child)
282
+ public_send(matcher_name, child)
283
283
  end
284
284
  end
285
285
  end
@@ -54,11 +54,9 @@ module RuboCop
54
54
  end
55
55
 
56
56
  def delegating?(node, def_node)
57
- if node.nil?
58
- false
59
- elsif node.zsuper_type?
57
+ if node&.zsuper_type?
60
58
  true
61
- elsif node.super_type?
59
+ elsif node&.super_type?
62
60
  node.arguments.map(&:source) == def_node.arguments.map(&:source)
63
61
  else
64
62
  false
@@ -5,6 +5,7 @@ module RuboCop
5
5
  module Lint
6
6
  # This cop checks for setter call to local variable as the final
7
7
  # expression of a function definition.
8
+ # Its auto-correction is marked as unsafe because return value will be changed.
8
9
  #
9
10
  # NOTE: There are edge cases in which the local variable references a
10
11
  # value that is also accessible outside the local scope. This is not
@@ -29,6 +30,8 @@ module RuboCop
29
30
  # x
30
31
  # end
31
32
  class UselessSetterCall < Base
33
+ extend AutoCorrector
34
+
32
35
  MSG = 'Useless setter call to local variable `%<variable>s`.'
33
36
  ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn].freeze
34
37
 
@@ -45,7 +48,9 @@ module RuboCop
45
48
 
46
49
  loc_name = receiver.loc.name
47
50
 
48
- add_offense(loc_name, message: format(MSG, variable: loc_name.source))
51
+ add_offense(loc_name, message: format(MSG, variable: loc_name.source)) do |corrector|
52
+ corrector.insert_after(last_expr, "\n#{indent(last_expr)}#{loc_name.source}")
53
+ end
49
54
  end
50
55
  alias on_defs on_def
51
56