rubocop 0.93.1 → 1.6.1

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 (199) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -16
  3. data/config/default.yml +276 -82
  4. data/config/obsoletion.yml +196 -0
  5. data/exe/rubocop +1 -1
  6. data/lib/rubocop.rb +31 -2
  7. data/lib/rubocop/cli.rb +5 -1
  8. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  9. data/lib/rubocop/cli/command/execute_runner.rb +26 -11
  10. data/lib/rubocop/cli/command/suggest_extensions.rb +80 -0
  11. data/lib/rubocop/cli/command/version.rb +1 -1
  12. data/lib/rubocop/comment_config.rb +1 -1
  13. data/lib/rubocop/config.rb +4 -0
  14. data/lib/rubocop/config_loader.rb +34 -8
  15. data/lib/rubocop/config_loader_resolver.rb +12 -6
  16. data/lib/rubocop/config_obsoletion.rb +65 -247
  17. data/lib/rubocop/config_obsoletion/changed_enforced_styles.rb +33 -0
  18. data/lib/rubocop/config_obsoletion/changed_parameter.rb +21 -0
  19. data/lib/rubocop/config_obsoletion/cop_rule.rb +34 -0
  20. data/lib/rubocop/config_obsoletion/extracted_cop.rb +44 -0
  21. data/lib/rubocop/config_obsoletion/parameter_rule.rb +44 -0
  22. data/lib/rubocop/config_obsoletion/removed_cop.rb +41 -0
  23. data/lib/rubocop/config_obsoletion/renamed_cop.rb +34 -0
  24. data/lib/rubocop/config_obsoletion/rule.rb +41 -0
  25. data/lib/rubocop/config_obsoletion/split_cop.rb +27 -0
  26. data/lib/rubocop/config_regeneration.rb +1 -1
  27. data/lib/rubocop/config_validator.rb +25 -10
  28. data/lib/rubocop/cop/autocorrect_logic.rb +21 -6
  29. data/lib/rubocop/cop/badge.rb +9 -24
  30. data/lib/rubocop/cop/base.rb +33 -16
  31. data/lib/rubocop/cop/bundler/duplicated_gem.rb +26 -6
  32. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  33. data/lib/rubocop/cop/commissioner.rb +37 -23
  34. data/lib/rubocop/cop/cop.rb +2 -2
  35. data/lib/rubocop/cop/corrector.rb +3 -1
  36. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  37. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +6 -8
  38. data/lib/rubocop/cop/force.rb +1 -1
  39. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
  40. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
  41. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
  42. data/lib/rubocop/cop/generator.rb +3 -10
  43. data/lib/rubocop/cop/generator/configuration_injector.rb +1 -1
  44. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +1 -1
  45. data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
  46. data/lib/rubocop/cop/layout/class_structure.rb +22 -3
  47. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  48. data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
  49. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +80 -10
  50. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  51. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  52. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  53. data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
  54. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  55. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
  56. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  57. data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
  58. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +12 -0
  59. data/lib/rubocop/cop/layout/line_length.rb +10 -13
  60. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  61. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
  62. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  63. data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
  64. data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
  65. data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
  66. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +2 -1
  67. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +26 -2
  68. data/lib/rubocop/cop/lint/debugger.rb +17 -28
  69. data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
  70. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
  71. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
  72. data/lib/rubocop/cop/lint/else_layout.rb +29 -3
  73. data/lib/rubocop/cop/lint/empty_block.rb +82 -0
  74. data/lib/rubocop/cop/lint/empty_class.rb +93 -0
  75. data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
  76. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  77. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +39 -7
  78. data/lib/rubocop/cop/lint/loop.rb +4 -4
  79. data/lib/rubocop/cop/lint/missing_super.rb +7 -4
  80. data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
  81. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
  82. data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
  83. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
  84. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
  85. data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
  86. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  87. data/lib/rubocop/cop/lint/to_enum_arguments.rb +86 -0
  88. data/lib/rubocop/cop/lint/to_json.rb +1 -1
  89. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  90. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +199 -0
  91. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  92. data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
  93. data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
  94. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  95. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  96. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  97. data/lib/rubocop/cop/metrics/parameter_lists.rb +68 -2
  98. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  99. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  100. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  101. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  102. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  103. data/lib/rubocop/cop/mixin/configurable_numbering.rb +4 -3
  104. data/lib/rubocop/cop/mixin/enforce_superclass.rb +9 -1
  105. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  106. data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
  107. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  108. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  109. data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
  110. data/lib/rubocop/cop/mixin/string_help.rb +4 -1
  111. data/lib/rubocop/cop/mixin/visibility_help.rb +1 -3
  112. data/lib/rubocop/cop/naming/accessor_method_name.rb +15 -1
  113. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +12 -2
  114. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
  115. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
  116. data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
  117. data/lib/rubocop/cop/naming/variable_number.rb +100 -8
  118. data/lib/rubocop/cop/offense.rb +3 -3
  119. data/lib/rubocop/cop/security/open.rb +12 -10
  120. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -1
  121. data/lib/rubocop/cop/style/and_or.rb +11 -3
  122. data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
  123. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
  124. data/lib/rubocop/cop/style/case_like_if.rb +0 -4
  125. data/lib/rubocop/cop/style/character_literal.rb +10 -11
  126. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  127. data/lib/rubocop/cop/style/collection_compact.rb +91 -0
  128. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +169 -0
  129. data/lib/rubocop/cop/style/documentation.rb +12 -1
  130. data/lib/rubocop/cop/style/double_negation.rb +6 -1
  131. data/lib/rubocop/cop/style/float_division.rb +44 -1
  132. data/lib/rubocop/cop/style/format_string.rb +8 -3
  133. data/lib/rubocop/cop/style/format_string_token.rb +47 -2
  134. data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
  135. data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
  136. data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
  137. data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -3
  138. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  139. data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
  140. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  141. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
  142. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
  143. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +8 -13
  144. data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
  145. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  146. data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
  147. data/lib/rubocop/cop/style/multiple_comparison.rb +55 -7
  148. data/lib/rubocop/cop/style/negated_if_else_condition.rb +106 -0
  149. data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
  150. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  151. data/lib/rubocop/cop/style/perl_backrefs.rb +86 -9
  152. data/lib/rubocop/cop/style/raise_args.rb +21 -6
  153. data/lib/rubocop/cop/style/redundant_argument.rb +88 -0
  154. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  155. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  156. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
  157. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  158. data/lib/rubocop/cop/style/redundant_self.rb +3 -0
  159. data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
  160. data/lib/rubocop/cop/style/semicolon.rb +3 -0
  161. data/lib/rubocop/cop/style/single_line_block_params.rb +30 -7
  162. data/lib/rubocop/cop/style/sole_nested_conditional.rb +65 -3
  163. data/lib/rubocop/cop/style/special_global_vars.rb +1 -13
  164. data/lib/rubocop/cop/style/static_class.rb +97 -0
  165. data/lib/rubocop/cop/style/string_concatenation.rb +39 -2
  166. data/lib/rubocop/cop/style/string_literals.rb +14 -8
  167. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -3
  168. data/lib/rubocop/cop/style/swap_values.rb +108 -0
  169. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  170. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
  171. data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
  172. data/lib/rubocop/cop/team.rb +6 -1
  173. data/lib/rubocop/cop/util.rb +6 -2
  174. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  175. data/lib/rubocop/cop/variable_force/scope.rb +1 -1
  176. data/lib/rubocop/core_ext/hash.rb +20 -0
  177. data/lib/rubocop/ext/regexp_node.rb +36 -11
  178. data/lib/rubocop/ext/regexp_parser.rb +95 -0
  179. data/lib/rubocop/formatter/disabled_config_formatter.rb +21 -6
  180. data/lib/rubocop/formatter/emacs_style_formatter.rb +2 -0
  181. data/lib/rubocop/formatter/formatter_set.rb +2 -1
  182. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
  183. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  184. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  185. data/lib/rubocop/formatter/tap_formatter.rb +2 -0
  186. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  187. data/lib/rubocop/lockfile.rb +40 -0
  188. data/lib/rubocop/magic_comment.rb +2 -2
  189. data/lib/rubocop/options.rb +11 -1
  190. data/lib/rubocop/rake_task.rb +2 -2
  191. data/lib/rubocop/rspec/shared_contexts.rb +4 -0
  192. data/lib/rubocop/runner.rb +1 -1
  193. data/lib/rubocop/target_finder.rb +1 -1
  194. data/lib/rubocop/target_ruby.rb +65 -1
  195. data/lib/rubocop/version.rb +56 -6
  196. metadata +50 -9
  197. data/bin/console +0 -10
  198. data/bin/rubocop-profile +0 -32
  199. data/bin/setup +0 -7
@@ -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
@@ -8,6 +8,14 @@ module RuboCop
8
8
  # given by `ruby -cw` prior to Ruby 2.6:
9
9
  # "shadowing outer local variable - foo".
10
10
  #
11
+ # NOTE: Shadowing of variables in block passed to `Ractor.new` is allowed
12
+ # because `Ractor` should not access outer variables.
13
+ # eg. following syle is encouraged:
14
+ #
15
+ # worker_id, pipe = env
16
+ # Ractor.new(worker_id, pipe) do |worker_id, pipe|
17
+ # end
18
+ #
11
19
  # @example
12
20
  #
13
21
  # # bad
@@ -34,12 +42,17 @@ module RuboCop
34
42
  class ShadowingOuterLocalVariable < Base
35
43
  MSG = 'Shadowing outer local variable - `%<variable>s`.'
36
44
 
45
+ def_node_matcher :ractor_block?, <<~PATTERN
46
+ (block (send (const nil? :Ractor) :new ...) ...)
47
+ PATTERN
48
+
37
49
  def self.joining_forces
38
50
  VariableForce
39
51
  end
40
52
 
41
53
  def before_declaring_variable(variable, variable_table)
42
54
  return if variable.should_be_unused?
55
+ return if ractor_block?(variable.scope.node)
43
56
 
44
57
  outer_local_variable = variable_table.find_variable(variable.name)
45
58
  return unless outer_local_variable
@@ -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
@@ -31,7 +31,7 @@ module RuboCop
31
31
  class ToJSON < Base
32
32
  extend AutoCorrector
33
33
 
34
- MSG = ' `#to_json` requires an optional argument to be parsable ' \
34
+ MSG = '`#to_json` requires an optional argument to be parsable ' \
35
35
  'via JSON.generate(obj).'
36
36
 
37
37
  def on_def(node)
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # This cop checks for a block that is known to need more positional
7
+ # block arguments than are given (by default this is configured for
8
+ # `Enumerable` methods needing 2 arguments). Optional arguments are allowed,
9
+ # although they don't generally make sense as the default value will
10
+ # be used. Blocks that have no receiver, or take splatted arguments
11
+ # (ie. `*args`) are always accepted.
12
+ #
13
+ # Keyword arguments (including `**kwargs`) do not get counted towards
14
+ # this, as they are not used by the methods in question.
15
+ #
16
+ # NOTE: This cop matches for method names only and hence cannot tell apart
17
+ # methods with same name in different classes.
18
+ #
19
+ # Method names and their expected arity can be configured like this:
20
+ #
21
+ # Methods:
22
+ # inject: 2
23
+ # reduce: 2
24
+ #
25
+ # @example
26
+ # # bad
27
+ # values.reduce {}
28
+ # values.min { |a| a }
29
+ # values.sort { |a; b| a + b }
30
+ #
31
+ # # good
32
+ # values.reduce { |memo, obj| memo << obj }
33
+ # values.min { |a, b| a <=> b }
34
+ # values.sort { |*x| x[0] <=> x[1] }
35
+ #
36
+ class UnexpectedBlockArity < Base
37
+ MSG = '`%<method>s` expects at least %<expected>i positional arguments, got %<actual>i.'
38
+
39
+ def on_block(node)
40
+ return if acceptable?(node)
41
+
42
+ expected = expected_arity(node.method_name)
43
+ actual = arg_count(node)
44
+ return if actual >= expected
45
+
46
+ message = format(MSG, method: node.method_name, expected: expected, actual: actual)
47
+ add_offense(node, message: message)
48
+ end
49
+
50
+ alias on_numblock on_block
51
+
52
+ private
53
+
54
+ def methods
55
+ cop_config.fetch('Methods', [])
56
+ end
57
+
58
+ def acceptable?(node)
59
+ !(included_method?(node.method_name) && node.receiver)
60
+ end
61
+
62
+ def included_method?(name)
63
+ methods.key?(name.to_s)
64
+ end
65
+
66
+ def expected_arity(method)
67
+ cop_config['Methods'][method.to_s]
68
+ end
69
+
70
+ def arg_count(node)
71
+ return node.children[1] if node.numblock_type? # the maximum numbered param for the block
72
+
73
+ # Only `arg`, `optarg` and `mlhs` (destructuring) count as arguments that
74
+ # can be used. Keyword arguments are not used for these methods so are
75
+ # ignored.
76
+ node.arguments.count do |arg|
77
+ return Float::INFINITY if arg.restarg_type?
78
+
79
+ arg.arg_type? || arg.optarg_type? || arg.mlhs_type?
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,199 @@
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
+ {
71
+ (block (send _recv {:reduce :inject} ...) args ...)
72
+ (numblock (send _recv {:reduce :inject} ...) ...)
73
+ }
74
+ PATTERN
75
+
76
+ def_node_matcher :accumulator_index?, <<~PATTERN
77
+ (send (lvar %1) {:[] :[]=} ...)
78
+ PATTERN
79
+
80
+ def_node_search :element_modified?, <<~PATTERN
81
+ {
82
+ (send _receiver !{:[] :[]=} <`(lvar %1) `_ ...>) # method(el, ...)
83
+ (send (lvar %1) _message <{ivar gvar cvar lvar send} ...>) # el.method(...)
84
+ (lvasgn %1 _) # el = ...
85
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1) ... _) # el += ...
86
+ }
87
+ PATTERN
88
+
89
+ def_node_matcher :lvar_used?, <<~PATTERN
90
+ {
91
+ (lvar %1)
92
+ (lvasgn %1 ...)
93
+ (send (lvar %1) :<< ...)
94
+ (dstr (begin (lvar %1)))
95
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (lvasgn %1))
96
+ }
97
+ PATTERN
98
+
99
+ def_node_search :expression_values, <<~PATTERN
100
+ {
101
+ (%RuboCop::AST::Node::VARIABLES $_)
102
+ (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_ ...)
103
+ (send (%RuboCop::AST::Node::VARIABLES $_) :<< ...)
104
+ $(send _ _)
105
+ (dstr (begin {(%RuboCop::AST::Node::VARIABLES $_)}))
106
+ (%RuboCop::AST::Node::SHORTHAND_ASSIGNMENTS (%RuboCop::AST::Node::EQUALS_ASSIGNMENTS $_) ...)
107
+ }
108
+ PATTERN
109
+
110
+ def on_block(node)
111
+ return unless reduce_with_block?(node)
112
+ return unless node.argument_list.length >= 2
113
+
114
+ check_return_values(node)
115
+ end
116
+ alias on_numblock on_block
117
+
118
+ private
119
+
120
+ # Return values in a block are either the value given to next,
121
+ # the last line of a multiline block, or the only line of the block
122
+ def return_values(block_body_node)
123
+ nodes = [block_body_node.begin_type? ? block_body_node.child_nodes.last : block_body_node]
124
+
125
+ block_body_node.each_descendant(:next, :break) do |n|
126
+ # Ignore `next`/`break` inside an inner block
127
+ next if n.each_ancestor(:block).first != block_body_node.parent
128
+ next unless n.first_argument
129
+
130
+ nodes << n.first_argument
131
+ end
132
+
133
+ nodes
134
+ end
135
+
136
+ def check_return_values(block_node)
137
+ return_values = return_values(block_node.body)
138
+ accumulator_name = block_arg_name(block_node, 0)
139
+ element_name = block_arg_name(block_node, 1)
140
+ message_opts = { method: block_node.method_name, accum: accumulator_name }
141
+
142
+ if (node = returned_accumulator_index(return_values, accumulator_name, element_name))
143
+ add_offense(node, message: format(MSG_INDEX, message_opts))
144
+ elsif potential_offense?(return_values, block_node.body, element_name, accumulator_name)
145
+ return_values.each do |return_val|
146
+ unless acceptable_return?(return_val, element_name)
147
+ add_offense(return_val, message: format(MSG, message_opts))
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def block_arg_name(node, index)
154
+ node.argument_list[index].name
155
+ end
156
+
157
+ # Look for an index of the accumulator being returned, except where the index
158
+ # is the element.
159
+ # This is always an offense, in order to try to catch potential exceptions
160
+ # due to type mismatches
161
+ def returned_accumulator_index(return_values, accumulator_name, element_name)
162
+ return_values.detect do |val|
163
+ next unless accumulator_index?(val, accumulator_name)
164
+ next true if val.method?(:[]=)
165
+
166
+ val.arguments.none? { |arg| lvar_used?(arg, element_name) }
167
+ end
168
+ end
169
+
170
+ def potential_offense?(return_values, block_body, element_name, accumulator_name)
171
+ !(element_modified?(block_body, element_name) ||
172
+ returns_accumulator_anywhere?(return_values, accumulator_name))
173
+ end
174
+
175
+ # If the accumulator is used in any return value, the node is acceptable since
176
+ # the accumulator has a chance to change each iteration
177
+ def returns_accumulator_anywhere?(return_values, accumulator_name)
178
+ return_values.any? { |node| lvar_used?(node, accumulator_name) }
179
+ end
180
+
181
+ # Determine if a return value is acceptable for the purposes of this cop
182
+ # If it is an expression containing the accumulator, it is acceptable
183
+ # Otherwise, it is only unacceptable if it contains the iterated element, since we
184
+ # otherwise do not have enough information to prevent false positives.
185
+ def acceptable_return?(return_val, element_name)
186
+ vars = expression_values(return_val).uniq
187
+ return true if vars.none? || (vars - [element_name]).any?
188
+
189
+ false
190
+ end
191
+
192
+ # Exclude `begin` nodes inside a `dstr` from being collected by `return_values`
193
+ def allowed_type?(parent_node)
194
+ !parent_node.dstr_type?
195
+ end
196
+ end
197
+ end
198
+ end
199
+ 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