class Mirrors::PackageInference::ClassToFileResolver

Public Class Methods

new() click to toggle source
# File lib/mirrors/package_inference/class_to_file_resolver.rb, line 6
def initialize
  @files = {}
end

Public Instance Methods

resolve(klass) click to toggle source
# File lib/mirrors/package_inference/class_to_file_resolver.rb, line 10
def resolve(klass)
  return nil if klass.nil?

  name = begin
    Mirrors.module_instance_invoke(klass, :name)
  rescue TypeError
    # klass is not a class/module, so we can't really determine its
    # origin.
    return nil
  end

  try_fast(klass, name)                   ||
    try_fast(klass.singleton_class, name) ||
    try_slow(klass)                       ||
    try_slow(klass.singleton_class)
end

Private Instance Methods

try_fast(klass, class_name) click to toggle source
# File lib/mirrors/package_inference/class_to_file_resolver.rb, line 29
def try_fast(klass, class_name)
  klass.instance_methods(false).each do |name|
    meth = klass.instance_method(name)

    file = begin
      sl = meth.source_location
      next unless sl
      sl[0]
    rescue MethodSource::SourceNotFoundError
      next
    end

    contents = (@files[file] ||= File.open(file, 'r') { |f| f.readpartial(4096) })
    n = class_name.sub(/.*::/, '') # last component of module name
    return file if contents =~ /^\s+(class|module) ([\S]+::)?#{Regexp.quote(n)}\s/
  end
  nil
end
try_slow(klass) click to toggle source
# File lib/mirrors/package_inference/class_to_file_resolver.rb, line 48
def try_slow(klass)
  methods = klass
    .instance_methods(false)
    .map { |n| klass.instance_method(n) }

  defined_directly_on_class = methods
    .select do |meth|
      # as a mostly-useful heuristic, we just eliminate everything that was
      # defined using a template eval or define_method.
      begin
        meth.source =~ /\A\s+def (self\.)?#{Regexp.quote(meth.name)}/
      rescue MethodSource::SourceNotFoundError
        false
      rescue NoMethodError => e
        STDERR.puts "\x1b[31mbug in method_source for #{meth}: #{e.inspect}\x1b[0m"
        false
      end
    end

  files = Hash.new(0)

  defined_directly_on_class.each do |meth|
    begin
      sl = meth.source_location[0]
      raise unless sl
      files[sl[0]] += 1
    rescue MethodSource::SourceNotFoundError
      raise
    end
  end

  file = files.max_by { |_k, v| v }
  file ? file[0] : nil
end