class RuboCop::Cop::Style::ArgumentsForwarding

In Ruby 2.7, arguments forwarding has been added.

This cop identifies places where ‘do_something(*args, &block)` can be replaced by `do_something(…)`.

In Ruby 3.1, anonymous block forwarding has been added.

This cop identifies places where ‘do_something(&block)` can be replaced by `do_something(&)`; if desired, this functionality can be disabled by setting `UseAnonymousForwarding: false`.

In Ruby 3.2, anonymous args/kwargs forwarding has been added.

This cop also identifies places where ‘use_args(*args)`/`use_kwargs(**kwargs)` can be replaced by `use_args(*)`/`use_kwargs(**)`; if desired, this functionality can be disabled by setting `UseAnonymousForwarding: false`.

And this cop has ‘RedundantRestArgumentNames`, `RedundantKeywordRestArgumentNames`, and `RedundantBlockArgumentNames` options. This configuration is a list of redundant names that are sufficient for anonymizing meaningless naming.

Meaningless names that are commonly used can be anonymized by default: e.g., ‘*args`, `**options`, `&block`, and so on.

Names not on this list are likely to be meaningful and are allowed by default.

This cop handles not only method forwarding but also forwarding to ‘super`.

@example

# bad
def foo(*args, &block)
  bar(*args, &block)
end

# bad
def foo(*args, **kwargs, &block)
  bar(*args, **kwargs, &block)
end

# good
def foo(...)
  bar(...)
end

@example UseAnonymousForwarding: true (default, only relevant for Ruby >= 3.2)

# bad
def foo(*args, **kwargs, &block)
  args_only(*args)
  kwargs_only(**kwargs)
  block_only(&block)
end

# good
def foo(*, **, &)
  args_only(*)
  kwargs_only(**)
  block_only(&)
end

@example UseAnonymousForwarding: false (only relevant for Ruby >= 3.2)

# good
def foo(*args, **kwargs, &block)
  args_only(*args)
  kwargs_only(**kwargs)
  block_only(&block)
end

@example AllowOnlyRestArgument: true (default, only relevant for Ruby < 3.2)

# good
def foo(*args)
  bar(*args)
end

def foo(**kwargs)
  bar(**kwargs)
end

@example AllowOnlyRestArgument: false (only relevant for Ruby < 3.2)

# bad
# The following code can replace the arguments with `...`,
# but it will change the behavior. Because `...` forwards block also.
def foo(*args)
  bar(*args)
end

def foo(**kwargs)
  bar(**kwargs)
end

@example RedundantRestArgumentNames: [‘args’, ‘arguments’] (default)

# bad
def foo(*args)
  bar(*args)
end

# good
def foo(*)
  bar(*)
end

@example RedundantKeywordRestArgumentNames: [‘kwargs’, ‘options’, ‘opts’] (default)

# bad
def foo(**kwargs)
  bar(**kwargs)
end

# good
def foo(**)
  bar(**)
end

@example RedundantBlockArgumentNames: [‘blk’, ‘block’, ‘proc’] (default)

# bad - But it is good with `EnforcedStyle: explicit` set for `Naming/BlockForwarding`.
def foo(&block)
  bar(&block)
end

# good
def foo(&)
  bar(&)
end

Constants

ADDITIONAL_ARG_TYPES
ARGS_MSG
BLOCK_MSG
FORWARDING_LVAR_TYPES
FORWARDING_MSG
KWARGS_MSG

Public Class Methods

autocorrect_incompatible_with() click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 142
def self.autocorrect_incompatible_with
  [Naming::BlockForwarding]
end

Public Instance Methods

on_def(node) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 146
def on_def(node)
  return unless node.body

  restarg, kwrestarg, blockarg = extract_forwardable_args(node.arguments)
  forwardable_args = redundant_forwardable_named_args(restarg, kwrestarg, blockarg)
  send_nodes = node.each_descendant(:send, :csend, :super, :yield).to_a

  send_classifications = classify_send_nodes(
    node, send_nodes, non_splat_or_block_pass_lvar_references(node.body), forwardable_args
  )

  return if send_classifications.empty?

  if only_forwards_all?(send_classifications)
    add_forward_all_offenses(node, send_classifications, forwardable_args)
  elsif target_ruby_version >= 3.2
    add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
  end
end
Also aliased as: on_defs
on_defs(node)
Alias for: on_def

Private Instance Methods

add_forward_all_offenses(node, send_classifications, forwardable_args) click to toggle source

rubocop:disable Metrics/MethodLength

# File lib/rubocop/cop/style/arguments_forwarding.rb, line 188
def add_forward_all_offenses(node, send_classifications, forwardable_args)
  _rest_arg, _kwrest_arg, block_arg = *forwardable_args
  registered_block_arg_offense = false

  send_classifications.each do |send_node, c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
    if !forward_rest && !forward_kwrest && c != :all_anonymous
      # Prevents `anonymous block parameter is also used within block (SyntaxError)` occurs
      # in Ruby 3.3.0.
      if outside_block?(forward_block_arg)
        register_forward_block_arg_offense(!forward_rest, node.arguments, block_arg)
        register_forward_block_arg_offense(!forward_rest, send_node, forward_block_arg)
      end
      registered_block_arg_offense = true
      break
    else
      register_forward_all_offense(send_node, send_node, forward_rest)
    end
  end

  return if registered_block_arg_offense

  rest_arg, _kwrest_arg, _block_arg = *forwardable_args
  register_forward_all_offense(node, node.arguments, rest_arg)
end
add_parens_if_missing(node, corrector) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 366
def add_parens_if_missing(node, corrector)
  return if parentheses?(node)
  return if node.send_type? && node.method?(:[])

  add_parentheses(node, corrector)
end
add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args) click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

# File lib/rubocop/cop/style/arguments_forwarding.rb, line 215
def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
  return unless use_anonymous_forwarding?
  return if send_inside_block?(send_classifications)

  rest_arg, kwrest_arg, block_arg = *forwardable_args

  send_classifications.each do |send_node, _c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
    if outside_block?(forward_rest)
      register_forward_args_offense(def_node.arguments, rest_arg)
      register_forward_args_offense(send_node, forward_rest)
    end

    if outside_block?(forward_kwrest)
      register_forward_kwargs_offense(!forward_rest, def_node.arguments, kwrest_arg)
      register_forward_kwargs_offense(!forward_rest, send_node, forward_kwrest)
    end

    # Prevents `anonymous block parameter is also used within block (SyntaxError)` occurs
    # in Ruby 3.3.0.
    if outside_block?(forward_block_arg)
      register_forward_block_arg_offense(!forward_rest, def_node.arguments, block_arg)
      register_forward_block_arg_offense(!forward_rest, send_node, forward_block_arg)
    end
  end
end
allow_only_rest_arguments?() click to toggle source

rubocop:enable Metrics/AbcSize

# File lib/rubocop/cop/style/arguments_forwarding.rb, line 352
def allow_only_rest_arguments?
  cop_config.fetch('AllowOnlyRestArgument', true)
end
arguments_range(node, first_node) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/rubocop/cop/style/arguments_forwarding.rb, line 340
def arguments_range(node, first_node)
  arguments = node.arguments.reject do |arg|
    next true if ADDITIONAL_ARG_TYPES.include?(arg.type) || arg.variable? || arg.call_type?

    arg.literal? && arg.each_descendant(:kwsplat).none?
  end

  start_node = first_node || arguments.first
  start_node.source_range.begin.join(arguments.last.source_range.end)
end
classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 267
def classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args)
  classifier = SendNodeClassifier.new(
    def_node, send_node, referenced_lvars, forwardable_args,
    target_ruby_version: target_ruby_version,
    allow_only_rest_arguments: allow_only_rest_arguments?
  )

  classification = classifier.classification

  return unless classification

  [
    classification,
    classifier.forwarded_rest_arg,
    classifier.forwarded_kwrest_arg,
    classifier.forwarded_block_arg
  ]
end
classify_send_nodes(def_node, send_nodes, referenced_lvars, forwardable_args) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 252
def classify_send_nodes(def_node, send_nodes, referenced_lvars, forwardable_args)
  send_nodes.filter_map do |send_node|
    classification_and_forwards = classification_and_forwards(
      def_node,
      send_node,
      referenced_lvars,
      forwardable_args
    )

    next unless classification_and_forwards

    [send_node, *classification_and_forwards]
  end
end
explicit_block_name?() click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 542
def explicit_block_name?
  block_forwarding_config = config.for_cop('Naming/BlockForwarding')
  return false unless block_forwarding_config['Enabled']

  block_forwarding_config['EnforcedStyle'] == 'explicit'
end
extract_forwardable_args(args) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 170
def extract_forwardable_args(args)
  [args.find(&:restarg_type?), args.find(&:kwrestarg_type?), args.find(&:blockarg_type?)]
end
non_splat_or_block_pass_lvar_references(body) click to toggle source

rubocop:enable Metrics/AbcSize, Metrics/MethodLength

# File lib/rubocop/cop/style/arguments_forwarding.rb, line 242
def non_splat_or_block_pass_lvar_references(body)
  body.each_descendant(:lvar, :lvasgn).filter_map do |lvar|
    parent = lvar.parent

    next if lvar.lvar_type? && FORWARDING_LVAR_TYPES.include?(parent.type)

    lvar.children.first
  end.uniq
end
only_forwards_all?(send_classifications) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 182
def only_forwards_all?(send_classifications)
  all_classifications = %i[all all_anonymous].freeze
  send_classifications.all? { |_, c, _, _| all_classifications.include?(c) }
end
outside_block?(node) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 296
def outside_block?(node)
  return false unless node

  node.each_ancestor(:block, :numblock).none?
end
redundant_forwardable_named_args(restarg, kwrestarg, blockarg) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 174
def redundant_forwardable_named_args(restarg, kwrestarg, blockarg)
  restarg_node = redundant_named_arg(restarg, 'RedundantRestArgumentNames', '*')
  kwrestarg_node = redundant_named_arg(kwrestarg, 'RedundantKeywordRestArgumentNames', '**')
  blockarg_node = redundant_named_arg(blockarg, 'RedundantBlockArgumentNames', '&')

  [restarg_node, kwrestarg_node, blockarg_node]
end
redundant_named_arg(arg, config_name, keyword) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 286
def redundant_named_arg(arg, config_name, keyword)
  return nil unless arg

  redundant_arg_names = cop_config.fetch(config_name, []).map do |redundant_arg_name|
    "#{keyword}#{redundant_arg_name}"
  end << keyword

  redundant_arg_names.include?(arg.source) ? arg : nil
end
register_forward_all_offense(def_or_send, send_or_arguments, rest_or_splat) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 329
def register_forward_all_offense(def_or_send, send_or_arguments, rest_or_splat)
  arg_range = arguments_range(def_or_send, rest_or_splat)

  add_offense(arg_range, message: FORWARDING_MSG) do |corrector|
    add_parens_if_missing(send_or_arguments, corrector)

    corrector.replace(arg_range, '...')
  end
end
register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 302
def register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat)
  add_offense(rest_arg_or_splat, message: ARGS_MSG) do |corrector|
    add_parens_if_missing(def_arguments_or_send, corrector)

    corrector.replace(rest_arg_or_splat, '*')
  end
end
register_forward_block_arg_offense(add_parens, def_arguments_or_send, block_arg) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 318
def register_forward_block_arg_offense(add_parens, def_arguments_or_send, block_arg)
  return if target_ruby_version <= 3.0 ||
            block_arg.nil? || block_arg.source == '&' || explicit_block_name?

  add_offense(block_arg, message: BLOCK_MSG) do |corrector|
    add_parens_if_missing(def_arguments_or_send, corrector) if add_parens

    corrector.replace(block_arg, '&')
  end
end
register_forward_kwargs_offense(add_parens, def_arguments_or_send, kwrest_arg_or_splat) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 310
def register_forward_kwargs_offense(add_parens, def_arguments_or_send, kwrest_arg_or_splat)
  add_offense(kwrest_arg_or_splat, message: KWARGS_MSG) do |corrector|
    add_parens_if_missing(def_arguments_or_send, corrector) if add_parens

    corrector.replace(kwrest_arg_or_splat, '**')
  end
end
send_inside_block?(send_classifications) click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 360
def send_inside_block?(send_classifications)
  send_classifications.any? do |send_node, *|
    send_node.each_ancestor(:block, :numblock).any?
  end
end
use_anonymous_forwarding?() click to toggle source
# File lib/rubocop/cop/style/arguments_forwarding.rb, line 356
def use_anonymous_forwarding?
  cop_config.fetch('UseAnonymousForwarding', false)
end