123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::SuperArguments

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::AutoCorrector, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/style/super_arguments.rb

Overview

Checks for redundant argument forwarding when calling super with arguments identical to the method definition.

Using zero arity super within a define_method block results in RuntimeError:

def m
  define_method(:foo) { super() } # => OK
end

def m
  define_method(:foo) { super }   # => RuntimeError
end

Furthermore, any arguments accompanied by a block may potentially be delegating to define_method, therefore, super used within these blocks will be allowed. This approach might result in false negatives, yet ensuring safe detection takes precedence.

Note
When forwarding the same arguments but replacing the block argument with a new inline block, it is not necessary to explicitly list the non-block arguments. As such, an offense will be registered in this case.

Examples:

# bad
def method(*args, **kwargs)
  super(*args, **kwargs)
end

# good - implicitly passing all arguments
def method(*args, **kwargs)
  super
end

# good - forwarding a subset of the arguments
def method(*args, **kwargs)
  super(*args)
end

# good - forwarding no arguments
def method(*args, **kwargs)
  super()
end

# bad - forwarding with overridden block
def method(*args, **kwargs, &block)
  super(*args, **kwargs) { do_something }
end

# good - implicitly passing all non-block arguments
def method(*args, **kwargs, &block)
  super { do_something }
end

# good - assigning to the block variable before calling super
def method(&block)
  # Assigning to the block variable would pass the old value to super,
  # under this circumstance the block must be referenced explicitly.
  block ||= proc { 'fallback behavior' }
  super(&block)
end

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

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::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::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_gem_version

Returns a gems locked versions (i.e.

#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

Instance Method Details

#argument_list_size_differs?(def_args, super_args, super_node) ⇒ Boolean (private)

Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 126

def argument_list_size_differs?(def_args, super_args, super_node)
  # If the def node has a block argument and the super node has an explicit block,
  # the number of arguments is the same, so ignore the def node block arg.
  def_args_size = def_args.size
  def_args_size -= 1 if def_args.any?(&:blockarg_type?) && block_sends_to_super?(super_node)

  def_args_size != super_args.size
end

#arguments_identical?(def_node, super_node, def_args, super_args) ⇒ Boolean (private)

Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 108

def arguments_identical?(def_node, super_node, def_args, super_args)
  return false if argument_list_size_differs?(def_args, super_args, super_node)

  def_args.zip(super_args).each do |def_arg, super_arg|
    next if positional_arg_same?(def_arg, super_arg)
    next if positional_rest_arg_same(def_arg, super_arg)
    next if keyword_arg_same?(def_arg, super_arg)
    next if keyword_rest_arg_same?(def_arg, super_arg)
    next if block_arg_same?(def_node, super_node, def_arg, super_arg)
    next if forward_arg_same?(def_arg, super_arg)

    return false
  end

  true
end

#block_arg_same?(def_node, super_node, def_arg, super_arg) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 181

def block_arg_same?(def_node, super_node, def_arg, super_arg)
  return false unless def_arg.blockarg_type?
  return true if block_sends_to_super?(super_node)
  return false unless super_arg.block_pass_type?

  # anonymous forwarding
  return true if (block_pass_child = super_arg.children.first).nil? && def_arg.name.nil?

  block_arg_name = block_pass_child.children.first
  def_arg.name == block_arg_name && !block_reassigned?(def_node, block_arg_name)
end

#block_reassigned?(def_node, block_arg_name) ⇒ Boolean (private)

Reassigning the block argument will still pass along the original block to super https://bugs.ruby-lang.org/issues/20505

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 195

def block_reassigned?(def_node, block_arg_name)
  def_node.each_node(*ASSIGN_TYPES).any? do |assign_node|
    # TODO: Since `Symbol#name` is supported from Ruby 3.0, the inheritance check for
    # `AST::Node` can be removed when requiring Ruby 3.0+.
    lhs = assign_node.node_parts[0]
    next if lhs.is_a?(AST::Node) && !lhs.respond_to?(:name)

    assign_node.name == block_arg_name
  end
end

#block_sends_to_super?(super_node, parent_node = super_node.parent) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 135

def block_sends_to_super?(super_node, parent_node = super_node.parent)
  # Checks if the send node of a block is the given super node,
  # or a method chain containing it.
  return false unless parent_node
  return false unless parent_node.type?(:block, :numblock)

  parent_node.send_node.each_node(:super).any?(super_node)
end

#find_def_node(super_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 96

def find_def_node(super_node)
  super_node.ancestors.find do |node|
    # When defining dynamic methods, implicitly calling `super` is not possible.
    # Since there is a possibility of delegation to `define_method`,
    # `super` used within the block is always allowed.
    break if node.block_type? && !block_sends_to_super?(super_node, node)

    break node if DEF_TYPES.include?(node.type)
  end
end

#forward_arg_same?(def_arg, super_arg) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 206

def forward_arg_same?(def_arg, super_arg)
  def_arg.forward_arg_type? && super_arg.forwarded_args_type?
end

#keyword_arg_same?(def_arg, super_arg) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 161

def keyword_arg_same?(def_arg, super_arg)
  return false unless def_arg.kwarg_type? || def_arg.kwoptarg_type?
  return false unless (pair_node = super_arg).pair_type?
  return false unless (sym_node = pair_node.key).sym_type?
  return false unless (lvar_node = pair_node.value).lvar_type?
  return false unless sym_node.source == lvar_node.source

  def_arg.name == sym_node.value
end

#keyword_rest_arg_same?(def_arg, super_arg) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 171

def keyword_rest_arg_same?(def_arg, super_arg)
  return false unless def_arg.kwrestarg_type?
  # anonymous forwarding
  return true if def_arg.name.nil? && super_arg.forwarded_kwrestarg_type?
  return false unless super_arg.kwsplat_type?
  return false unless (lvar_node = super_arg.children.first).lvar_type?

  def_arg.name == lvar_node.children.first
end

#on_super(super_node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 78

def on_super(super_node)
  return unless (def_node = find_def_node(super_node))

  def_node_args = def_node.arguments.argument_list
  super_args = preprocess_super_args(super_node.arguments)

  return unless arguments_identical?(def_node, super_node, def_node_args, super_args)

  # If the number of arguments to the def node and super node are different here,
  # it's because the block argument is not forwarded.
  message = def_node_args.size == super_args.size ? MSG : MSG_INLINE_BLOCK
  add_offense(super_node, message: message) do |corrector|
    corrector.replace(super_node, 'super')
  end
end

#positional_arg_same?(def_arg, super_arg) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 144

def positional_arg_same?(def_arg, super_arg)
  return false unless def_arg.arg_type? || def_arg.optarg_type?
  return false unless super_arg.lvar_type?

  def_arg.name == super_arg.children.first
end

#positional_rest_arg_same(def_arg, super_arg) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 151

def positional_rest_arg_same(def_arg, super_arg)
  return false unless def_arg.restarg_type?
  # anonymous forwarding
  return true if def_arg.name.nil? && super_arg.forwarded_restarg_type?
  return false unless super_arg.splat_type?
  return false unless (lvar_node = super_arg.children.first).lvar_type?

  def_arg.name == lvar_node.children.first
end

#preprocess_super_args(super_args) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/super_arguments.rb', line 210

def preprocess_super_args(super_args)
  super_args.flat_map do |node|
    if node.hash_type? && !node.braces?
      node.children
    else
      node
    end
  end
end