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.

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.

.builtin?

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

Cops (other than builtin) are encouraged to implement this.

.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, #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 140

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 343

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 495

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 347

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 185

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
      # 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 351

def add_parens_if_missing(node, corrector)
  return if parentheses?(node)

  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 212

def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
  return unless use_anonymous_forwarding?

  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 335

def arguments_range(node, first_node)
  arguments = node.arguments.reject { |arg| ADDITIONAL_ARG_TYPES.include?(arg.type) }

  start_node = first_node || arguments.first

  range_between(start_node.source_range.begin_pos, arguments.last.source_range.end_pos)
end

#classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args) (private)

[ GitHub ]

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

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 248

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 168

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 238

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 144

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).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 164

alias on_defs on_def

#only_forwards_all?(send_classifications) ⇒ Boolean (private)

[ GitHub ]

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

def only_forwards_all?(send_classifications)
  send_classifications.all? { |_, c, _, _| c == :all }
end

#outside_block?(node) ⇒ Boolean (private)

[ GitHub ]

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

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 172

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 282

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 325

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 298

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 314

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 306

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