123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::SymbolProc

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/symbol_proc.rb

Overview

Use symbols as procs when possible.

If you prefer a style that allows block for method with arguments, please set true to AllowMethodsWithArguments. define_method? methods are allowed by default. These are customizable with AllowedMethods option.

Examples:

# bad
something.map { |s| s.upcase }
something.map { _1.upcase }

# good
something.map(&:upcase)

AllowMethodsWithArguments: false (default)

# bad
something.do_something(foo) { |o| o.bar }

# good
something.do_something(foo, &:bar)

AllowMethodsWithArguments: true

# good
something.do_something(foo) { |o| o.bar }

AllowComments: false (default)

# bad
something.do_something do |s| # some comment
  # some comment
  s.upcase # some comment
  # some comment
end

AllowComments: true

# good  - if there are comment in either position
something.do_something do |s| # some comment
  # some comment
  s.upcase # some comment
  # some comment
end

AllowedMethods: [define_method] (default)

# good
define_method(:foo) { |foo| foo.bar }

AllowedPatterns: [] (default)

# bad
something.map { |s| s.upcase }

AllowedPatterns: ['map'] (default)

# good
something.map { |s| s.upcase }

AllCops:ActiveSupportExtensionsEnabled: false (default)

# bad
->(x) { x.foo }
proc { |x| x.foo }
Proc.new { |x| x.foo }

# good
lambda(&:foo)
proc(&:foo)
Proc.new(&:foo)

AllCops:ActiveSupportExtensionsEnabled: true

# good
->(x) { x.foo }
proc { |x| x.foo }
Proc.new { |x| x.foo }

Cop Safety Information:

  • This cop is unsafe because there is a difference that a Proc generated from Symbol#to_proc behaves as a lambda, while a Proc generated from a block does not. For example, a lambda will raise an ArgumentError if the number of arguments is wrong, but a non-lambda Proc will not.

    For example:

    class Foo
      def bar
        :bar
      end
    end
    
    def call(options = {}, &block)
      block.call(Foo.new, options)
    end
    
    call { |x| x.bar }
    #=> :bar
    call(&:bar)
    # ArgumentError: wrong number of arguments (given 1, expected 0)

    It is also unsafe because Symbol#to_proc does not work with protected methods which would otherwise be accessible.

    For example:

    class Box
      def initialize
        @secret = rand
      end
    
      def normal_matches?(*others)
        others.map { |other| other.secret }.any?(secret)
      end
    
      def symbol_to_proc_matches?(*others)
        others.map(&:secret).any?(secret)
      end
    
      protected
    
      attr_reader :secret
    end
    
    boxes = [Box.new, Box.new]
    Box.new.normal_matches?(*boxes)
    # => false
    boxes.first.normal_matches?(*boxes)
    # => true
    Box.new.symbol_to_proc_matches?(*boxes)
    # => NoMethodError: protected method `secret' called for #<Box...>
    boxes.first.symbol_to_proc_matches?(*boxes)
    # => NoMethodError: protected method `secret' called for #<Box...>

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::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 an 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::AllowedPattern - Included

::RuboCop::Cop::AllowedMethods - Included

::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::CommentsHelp - Included

#comments_contain_disables?, #comments_in_range, #contains_comments?, #source_range_with_comment, #begin_pos_with_comment, #buffer, #end_position_for,
#find_end_line

Returns the end line of a node, which might be a comment and not part of the AST End line is considered either the line at which another node starts, or the line at which the parent node ends.

#start_line_position

::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/symbol_proc.rb', line 165

def self.autocorrect_incompatible_with
  [Layout::SpaceBeforeBlockBraces]
end

Instance Attribute Details

#allow_comments?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 274

def allow_comments?
  cop_config.fetch('AllowComments', false)
end

Instance Method Details

#allow_if_method_has_argument?(send_node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 270

def allow_if_method_has_argument?(send_node)
  !!cop_config.fetch('AllowMethodsWithArguments', false) && !send_node.arguments.count.zero?
end

#allowed_method_name?(name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 204

def allowed_method_name?(name)
  allowed_method?(name) || matches_allowed_pattern?(name)
end

#autocorrect(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 217

def autocorrect(corrector, node)
  if node.send_node.arguments?
    autocorrect_with_args(corrector, node, node.send_node.arguments, node.body.method_name)
  else
    autocorrect_without_args(corrector, node)
  end
end

#autocorrect_lambda_block(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 247

def autocorrect_lambda_block(corrector, node)
  send_node_loc = node.send_node.loc
  corrector.replace(send_node_loc.selector, 'lambda')

  range = range_between(send_node_loc.selector.end_pos, node.loc.begin.end_pos - 2)
  corrector.remove(range)
end

#autocorrect_with_args(corrector, node, args, method_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 238

def autocorrect_with_args(corrector, node, args, method_name)
  arg_range = args.last.source_range
  arg_range = range_with_surrounding_comma(arg_range, :right)
  replacement = " &:#{method_name}"
  replacement = ",#{replacement}" unless arg_range.source.end_with?(',')
  corrector.insert_after(arg_range, replacement)
  corrector.remove(block_range_with_space(node))
end

#autocorrect_without_args(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 225

def autocorrect_without_args(corrector, node)
  if node.send_node.lambda_literal?
    if node.send_node.loc.selector.source == '->'
      corrector.replace(node, "lambda(&:#{node.body.method_name})")
      return
    else
      autocorrect_lambda_block(corrector, node)
    end
  end

  corrector.replace(block_range_with_space(node), "(&:#{node.body.method_name})")
end

#begin_pos_for_replacement(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 260

def begin_pos_for_replacement(node)
  expr = node.send_node.source_range

  if (paren_pos = (expr.source =~ /\(\s*\)$/))
    expr.begin_pos + paren_pos
  else
    node.loc.begin.begin_pos
  end
end

#block_range_with_space(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 255

def block_range_with_space(node)
  block_range = range_between(begin_pos_for_replacement(node), node.loc.end.end_pos)
  range_with_surrounding_space(block_range, side: :left)
end

#destructuring_block_argument?(argument_node) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 189

def destructuring_block_argument?(argument_node)
  argument_node.one? && argument_node.source.include?(',')
end

#on_block(node) Also known as: #on_numblock

Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 170

def on_block(node)
  symbol_proc?(node) do |dispatch_node, arguments_node, method_name|
    if active_support_extensions_enabled?
      return if proc_node?(dispatch_node)
      return if LAMBDA_OR_PROC.include?(dispatch_node.method_name)
    end
    return if unsafe_hash_usage?(dispatch_node)
    return if unsafe_array_usage?(dispatch_node)
    return if allowed_method_name?(dispatch_node.method_name)
    return if allow_if_method_has_argument?(node.send_node)
    return if node.block_type? && destructuring_block_argument?(arguments_node)
    return if allow_comments? && contains_comments?(node)

    register_offense(node, method_name, dispatch_node.method_name)
  end
end

#on_numblock(node)

Alias for #on_block.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 187

alias on_numblock on_block

#proc_node?(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 152

def_node_matcher :proc_node?, '(send (const {nil? cbase} :Proc) :new)'

#register_offense(node, method_name, block_method_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 208

def register_offense(node, method_name, block_method_name)
  block_start = node.loc.begin.begin_pos
  block_end = node.loc.end.end_pos
  range = range_between(block_start, block_end)
  message = format(MSG, method: method_name, block_method: block_method_name)

  add_offense(range, message: message) { |corrector| autocorrect(corrector, node) }
end

#symbol_proc?(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 158

def_node_matcher :symbol_proc?, <<~PATTERN
  {
    (block $#symbol_proc_receiver? $(args (arg _var)) (send (lvar _var) $_))
    (numblock $#symbol_proc_receiver? $1 (send (lvar :_1) $_))
  }
PATTERN

#symbol_proc_receiver?(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 155

def_node_matcher :symbol_proc_receiver?, '{(call ...) (super ...) zsuper}'

#unsafe_array_usage?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 200

def unsafe_array_usage?(node)
  node.receiver&.array_type? && %i[min max].include?(node.method_name)
end

#unsafe_hash_usage?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/symbol_proc.rb', line 196

def unsafe_hash_usage?(node)
  node.receiver&.hash_type? && %i[reject select].include?(node.method_name)
end