class RubyLint::Analysis::UndefinedMethods

The UndefinedMethods class checks for the use of undefined methods/local variables and adds errors whenever needed. Based on the receiver of a method call the corresponding error message differs to make it easier to understand what is going on.

A simple example:

foobar        # => undefined method foobar
'test'.foobar # => undefined method foobar on an instance of String

Public Instance Methods

on_send(node) click to toggle source

@param [RubyLint::AST::Node] node

# File lib/ruby-lint/analysis/undefined_methods.rb, line 20
def on_send(node)
  receiver, name, _  = *node

  receiver = unpack_block(receiver)
  name     = name.to_s
  scope    = current_scope

  if receiver and vm.associations.key?(receiver)
    scope = vm.associations[receiver]

    # TODO: this should be handled in a more generic and especially in a
    # more nicer way.
    return if scope.parents.empty?
  end

  unless has_definition?(scope, name)
    message = error_for(name, receiver, scope)

    error(message, node)
  end
end

Private Instance Methods

class_names_for_object(object) click to toggle source

@param [RubyLint::Definition::RubyObject] object @return [String]

# File lib/ruby-lint/analysis/undefined_methods.rb, line 116
def class_names_for_object(object)
  if object.parents.empty?
    klass = object.ruby_class ? object.ruby_class : object.name
  else
    klass = name_for_parents(object.parents)
  end

  return klass
end
error_for(name, receiver, scope) click to toggle source

Determines what error message to use for a method call.

@param [String] name The name of the method. @param [RubyLint::AST::Node] receiver The receiver node, if any. @param [RubyLint::Definition::RubyObject] scope The scope the method

was called on.

@return [String]

# File lib/ruby-lint/analysis/undefined_methods.rb, line 78
def error_for(name, receiver, scope)
  return receiver ? receiver_error(name, scope) : method_error(name)
end
has_definition?(scope, name) click to toggle source

@param [RubyLint::Definition::RubyObject] scope @param [String] name

# File lib/ruby-lint/analysis/undefined_methods.rb, line 48
def has_definition?(scope, name)
  type   = scope.method_call_type
  exists = scope.has_definition?(type, name)

  # Due to the way `parser` wraps block nodes (`(block (send) ...)`
  # opposed to `(send ... (block))`) we'll try to find the method in the
  # previous scope if we can't find it in the current block scope.
  if !exists and scope.block?
    prev   = previous_scope
    exists = prev.has_definition?(prev.method_call_type, name)
  end

  # If method_missing is defined we'll assume the method calls are
  # handled gracefully and not add any errors for them.
  if !exists and scope.has_definition?(type, 'method_missing')
    exists = true
  end

  return exists
end
method_error(name) click to toggle source

@param [String] name @return [String]

# File lib/ruby-lint/analysis/undefined_methods.rb, line 86
def method_error(name)
  return "undefined method #{name}"
end
name_for_parents(parents) click to toggle source

@param [Array] parents @return [String]

# File lib/ruby-lint/analysis/undefined_methods.rb, line 130
def name_for_parents(parents)
  return parents[0].name if parents.length == 1

  segments = parents[0..-2].map(&:name)

  return segments.join(', ') + " or #{parents[-1].name}"
end
receiver_error(name, scope) click to toggle source

Returns a String containing the error message to use when calling an undefined method on a receiver.

@param [String] name @param [RubyLint::Definition::RubyObject] scope @return [String]

# File lib/ruby-lint/analysis/undefined_methods.rb, line 98
def receiver_error(name, scope)
  klass = class_names_for_object(scope)

  if scope.instance?
    error = "undefined method #{name} on an instance of #{klass}"
  else
    error = "undefined method #{name} on #{scope.name}"
  end

  return error
end