123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::ArgumentsForwarding

Relationships & Source Files
Namespace Children
Classes:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/style/arguments_forwarding.rb

Overview

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.

Examples:

# 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

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

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

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

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

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

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

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

RedundantRestArgumentNames: ['args', 'arguments'] (default)

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

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

RedundantKeywordRestArgumentNames: ['kwargs', 'options', 'opts'] (default)

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

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

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

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::TargetRubyVersion - Extended

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::RangeHelp - Included

#add_range, #column_offset_between,
#contents_range

A range containing only the contents of a literal with delimiters (e.g.

#directions,
#effective_column

Returns the column attribute of the range, except if the range is on the first line and there’s a byte order mark at the beginning of that line, in which case 1 is subtracted from the column value.

#final_pos, #move_pos, #move_pos_str, #range_between, #range_by_whole_lines, #range_with_comments, #range_with_comments_and_lines, #range_with_surrounding_comma, #range_with_surrounding_space, #source_range

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?, #target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Class Method Details

.autocorrect_incompatible_with

[ GitHub ]

  
# File 'lib/rubocop/cop/style/arguments_forwarding.rb', line 142

def self.autocorrect_incompatible_with
  [Naming::BlockForwarding]
end

Instance Attribute Details

#allow_only_rest_arguments?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/arguments_forwarding.rb', line 352

def allow_only_rest_arguments?
  cop_config.fetch('AllowOnlyRestArgument', true)
end

#explicit_block_name?Boolean (readonly)

[ GitHub ]

  
# 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

#use_anonymous_forwarding?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/arguments_forwarding.rb', line 356

def use_anonymous_forwarding?
  cop_config.fetch('UseAnonymousForwarding', false)
end

Instance Method Details

#add_forward_all_offenses(node, send_classifications, forwardable_args) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

Metrics/MethodLength

[ GitHub ]

  
# 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

#arguments_range(node, first_node) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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

#extract_forwardable_args(args) (private)

[ GitHub ]

  
# 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) (private)

Metrics/MethodLength

[ GitHub ]

  
# 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

#on_def(node) Also known as: #on_defs

[ GitHub ]

  
# 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

#on_defs(node)

Alias for #on_def.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/arguments_forwarding.rb', line 166

alias on_defs on_def

#only_forwards_all?(send_classifications) ⇒ Boolean (private)

[ GitHub ]

  
# 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) ⇒ Boolean (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) ⇒ Boolean (private)

[ GitHub ]

  
# 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